Как реализовать деление с округлением до бесконечности в Python - PullRequest
8 голосов
/ 25 августа 2011

Я хочу, чтобы 3/2 равнялся 2, а не 1,5

Я знаю, что есть математический термин для этой операции (не называется округлением), но сейчас я не могу вспомнить его. В любом случае, как мне это сделать без необходимости выполнять две функции?

за исключением того, чего я НЕ хочу:

answer = 3/2 then math.ceil(answer)=2 (why does math.ceil(3/2)=1?)  

от того, что я хочу:

 "function"(3/2) = 2

Ответы [ 8 ]

16 голосов
/ 25 августа 2011

Чтобы дать краткий ответ ...

Python предлагает только нативные операторы для двух типов деления: «истинное» деление и «округление вниз».То, что вы хотите, не доступно как отдельная функция.Тем не менее, можно легко реализовать несколько различных типов деления с округлением, используя некоторые короткие выражения.

По запросу заголовка: с учетом строго целочисленных входных данных деление «округление вверх» может быть реализовано с использованием (a+(-a%b))//b, а деление «округление от нуля» может быть реализовано с использованием более сложного a//b if a*b<0 else (a+(-a%b))//b.Один из них, вероятно, то, что вы хотите.Что касается того, почему ...


Чтобы дать более длинный ответ ...

Сначала позвольте мне ответить на вопрос о том, почему 3/2==1 и math.ceil(3/2)==1.0, чтобы объяснить, как работает оператор деления Python.В игре есть две основные проблемы ...

float против int деление: В Python 2 деление ведет себя по-разному в зависимости от типа входов.Если оба значения a и b являются целыми числами, a/b выполняет деление "округление вниз" или "целое число по полу" (например, 3/2==1, но -3/2==-2).Это эквивалентно int(math.floor(float(a)/b)).

Но если хотя бы один из a и b являются числами с плавающей запятой, Python выполняет "истинное" деление и выдает результат float (например, 3.0/2==1.5 и -3.0/2==-1.5).Вот почему вы иногда увидите конструкцию float(a)/b: она используется для принудительного деления, даже если оба входа являются целыми числами (например, float(3)/2==1.5).Вот почему ваш пример math.ceil(3/2) возвращает 1.0, тогда как math.ceil(float(3)/2) возвращает 2.0.Результат уже был округлен до того, как он достиг math.ceil().

«истинное деление» по умолчанию : в 2001 году было принято решение ( PEP 238 )что оператор деления Python должен быть изменен так, чтобы он всегда выполнял «истинное» деление, независимо от того, являются ли входные значения числами с плавающей запятой или целыми числами (например, это составит 3/2==1.5).Чтобы не нарушать существующие скрипты, изменение поведения по умолчанию было отложено до Python 3.0;чтобы получить такое поведение в Python 2.x, вы должны включить его для каждого файла, добавив from __future__ import division в начало файла.В противном случае используется старое поведение, зависящее от типа.

Но «округление» деление все еще часто необходимо, поэтому PEP не справился с этим полностью.Вместо этого он ввел новый оператор деления: a//b, который всегда выполняет деление с округлением вниз, даже если входные данные включают числа с плавающей запятой.Это можно использовать, не делая ничего особенного в Python 2.2+ и 3.x.


Таким образом, деление с округлением:

Для упрощения, все следующие выражения используют оператор a//b, когдаработает над целыми числами, так как он будет вести себя одинаково во всех версиях Python.Кроме того, я предполагаю, что 0<=a%b<b, если b положительно, и b<=a%b<=0, если b отрицательно.Вот как ведет себя Python, но другие языки могут иметь немного другие операторы модуля.

Четыре основных типа целочисленного деления с округлением:

  • "округление вниз" aka "целое число на полу" aka "округление до минус бесконечности"divsion: python предлагает это изначально через a//b.

  • округление вверх ака «целочисленное значение потолка» ака «округление до положительной бесконечности» деление: это может быть достигнуто с помощью int(math.ceil(float(a)/b)) или (a+(-a%b))//b.Последнее уравнение работает, потому что -a%b равно 0, если a кратно b, и в противном случае это сумма, которую мы должны добавить к a, чтобы добраться до следующего наивысшего кратного.

  • «округление до нуля» или «усеченное» деление - это может быть достигнуто с помощью int(float(a)/b).Делать это без использования чисел с плавающей запятой сложнее ... поскольку Python предлагает только целочисленное деление с округлением вниз, а оператор % имеет аналогичное смещение с округлением вниз, у нас нет операторов без плавающей запятой, которые округляются симметричноо 0. Таким образом, единственный способ, которым я могу думать, состоит в том, чтобы построить кусочное выражение из округления в меньшую сторону и округления в большую сторону: a//b if a*b>0 else (a+(-a%b))//b.

  • «округление от нуля» или «округление до (любой) бесконечности» - к сожалению, это даже сложнее, чем округление к нулю.Мы больше не можем использовать усечающее поведение оператора int, поэтому я не могу думать о простом выражении даже при включении операций с плавающей точкой.Поэтому я должен пойти с обратным выражением с округлением до нуля и использовать a//b if a*b<0 else (a+(-a%b))//b.

Обратите внимание, что если вы используете только натуральные числа, (a+b-1)//b обеспечиваетокруглять / убывать от нуля даже более эффективно, чем любое из вышеприведенных решений, но разваливается для негативов.

Надеюсь, что это поможет ... и с удовольствием внесу изменения, если кто-нибудь сможет предложить более подходящие уравнения для округления до / противс нуля.Я нахожу те, которые у меня есть, особенно неудовлетворительными.

5 голосов
/ 25 августа 2011

Интегральное деление в Python 3:

3 // 2 == 1

Неинтегральное деление в Python 3:

3 / 2 == 1.5

То, о чем вы говорите, вовсе не является делением.

4 голосов
/ 25 августа 2011

Смысл вопроса ОП: "Как реализовать деление с округлением до бесконечности в Python" (предлагаем изменить название).

Это вполне законное округлениережим в соответствии со стандартом IEEE-754 (см. в этом обзоре ), и термин для него "округляется до бесконечности" (или "округляется от нуля").Большинство из 9 провалов несправедливо избивали ФП.Да, в нативном Python нет единственного способа сделать это, но мы можем использовать round(float(a)/b) или подкласс numbers.Number и переопределить __div__().

ОП должен будет уточнить, хотят ли они-3/2, чтобы округлить до -2 или -1 (или не заботиться о отрицательных операндах).Поскольку они уже сказали, что не хотят округлять вверх, мы можем сделать вывод, что -3/2 следует округлить до -2.

Достаточно теории.Для реализаций:

  • Если вам просто нужно быстрое и грязное однострочное решение для округления до бесконечности, используйте round(float(a)/b)
  • math.ceil(float(a)/b)вы округляете вверх, что, как вы сказали, вам не нужно

  • Но если это ваша операция деления по умолчанию или вы выполняете большую часть этого, то выполните псевдокод ниже: наследуется от одного из подклассов numbers.Number Real, Rational или Integral (впервые в 2.6) , переопределяет __div__() или определяет альтернативную операцию __divra__(), отличную от заданной по умолчанию.Вы можете определить члена класса или метод класса rounding_mode и посмотреть его во время деления.Будьте осторожны с __rdiv__() и смешивайтесь с обычными поплавками.

.

import numbers

class NumberWithRounding(numbers.Integral):
    # Here you could implement a classmethod setRoundingMode() or member rounding_mode
    def __div__(self,other):
        # here you could consider value of rounding_mode, or else hardwire it like:
        return round(float(self)/other)
    # You also have to raise ImplementationError/ pass/ or implement the other 31
    # methods for Float: __abs__(),...,__xor__() Just shortcut that for now...
2 голосов
/ 25 августа 2011

Когда вы делите два целых числа, результатом является целое число.
3 / 2 равно 1, а не 1.5.
См. документацию , примечание 1:

Для целочисленного деления (простого или длинного) результатом является целое число. Результат всегда округляется до минус бесконечности: 1/2 - это 0, (-1) / 2 - это -1, 1 / (- 2) - это -1, а (-1) / (- 2) - это 0. Обратите внимание, что результатом является длинное целое число, если любой из операндов является длинным целым, независимо от числового значения.

Как только вы получите 1 из подразделения, нет способа превратить это в 2.

Чтобы получить 1.5, вам нужно деление с плавающей запятой: 3.0 / 2.
Затем вы можете позвонить math.ceil, чтобы получить 2.

Вы ошибаетесь; нет математической функции, которая делит, а затем округляет.
Лучшее, что вы можете сделать, это написать свою собственную функцию, которая принимает два числа с плавающей запятой и вызывает math.ceil.

1 голос
/ 25 августа 2011

Целочисленное деление с округлением потолка (до + Inf), округлением пола (до -Inf) и усечением (до 0) доступно в gmpy2.

>>> gmpy2.c_div(3,2)
mpz(2)
>>> help(gmpy2.c_div)
Help on built-in function c_div in module gmpy2:
c_div(...)
    c_div(x,y): returns the quotient of x divided by y. The quotient
    is rounded towards +Inf (ceiling rounding). x and y must be integers.
>>> help(gmpy2.f_div)
Help on built-in function f_div in module gmpy2:
f_div(...)
    f_div(x,y): returns the quotient of x divided by y. The quotient
    is rounded towards -Inf (floor rounding). x and y must be integers.
>>> help(gmpy2.t_div)
Help on built-in function t_div in module gmpy2:
t_div(...)
    t_div(x,y): returns the quotient of x divided by y. The quotient
    is rounded towards 0. x and y must be integers.
>>>

gmpy2 доступно в http://code.google.com/p/gmpy/

(Отказ от ответственности: я в настоящее время поддерживаю gmpy и gmpy2.)

1 голос
/ 25 августа 2011

Я думаю, что вы ищете это:

при условии, что у вас есть x (3) и y (2),

result = (x + y - 1) // y;

thisэквивалент потолка без использования плавающих точек.

Конечно, у не может быть 0.

1 голос
/ 25 августа 2011

Что вы, вероятно, хотите, это что-то вроде:

math.ceil(3.0/2.0)
# or
math.ceil(float(3)/float(2))

Вы также можете сделать импорт из будущего:

from __future__ import division
math.ceil(3/2) # == 2

Но, если вы сделаете это, чтобы получить текущее поведение целочисленного деления, вам нужно использовать двойную косую черту:

3 // 2 == 1 # True
0 голосов
/ 26 августа 2011

Во-первых, вы хотите использовать в аргументах деление с плавающей точкой.Используйте:

from __future__ import division

Если вы всегда хотите округлить, поэтому f(3/2)==2 и f(1.4)==2, тогда вы хотите, чтобы f было math.trunc(math.ceil(x)).

Если вы хотитеполучите ближайшее целое число, но округлите связи, тогда вы хотите math.trunc(x + 0.5).Таким образом, f(3/2)==2 и f(1.4)==1.

...