Поведение округления Python 3.x


176

Я только что перечитал Что нового в Python 3.0 и в нем говорится:

Функция округления () и стратегия возврата изменены. Точные полпути теперь округляются до ближайшего четного результата, а не от нуля. (Например, раунд (2.5) теперь возвращает 2, а не 3.)

и документация для тура :

Для встроенных типов, поддерживающих round (), значения округляются до ближайшего кратного 10 к степени минус n; если два множителя одинаково близки, округление выполняется до четного выбора

Итак, под v2.7.3 :

In [85]: round(2.5)
Out[85]: 3.0

In [86]: round(3.5)
Out[86]: 4.0

как я и ожидал. Однако теперь под v3.2.3 :

In [32]: round(2.5)
Out[32]: 2

In [33]: round(3.5)
Out[33]: 4

Это кажется нелогичным и противоречит тому, что я понимаю о округлении (и обманываю людей). Английский не является моим родным языком, но пока я не прочитал это, я думал, что знаю, что означает округление: - / Я уверен, что во время введения v3, должно быть, было некоторое обсуждение этого, но я не смог найти вескую причину в мой поиск.

  1. Кто-нибудь знает, почему это было изменено на это?
  2. Существуют ли другие основные языки программирования (например, C, C ++, Java, Perl, ...), которые делают такое (для меня непоследовательное) округление?

Что мне здесь не хватает?

ОБНОВЛЕНИЕ: @ Li-aungYip комментарий о «округлении банкира» дал мне правильный поисковый запрос / ключевые слова для поиска, и я нашел такой SO вопрос: Почему .NET использует округление банкира по умолчанию? так что я буду внимательно это читать.


28
У меня нет времени на это, но я считаю, что это называется «округлением банкира». Я считаю, что это распространено в финансовой индустрии.
Ли Аунг Ип

2
@ sberry хорошо, да, его поведение соответствует его собственному описанию. Так что, если бы он сказал, что «округление» удваивает его значение и делает это, это также будет последовательным :) ... но это кажется противоречащим тому, что округление обычно означает . Поэтому я ищу лучшего понимания.
Левон

1
@ Li-aungYip Спасибо за лидерство по поводу "округления Банкира" .. Я посмотрю.
Левон


3
Просто примечание: округление банкиров не распространено только в финансах. Так меня учили в начальной школе еще в 70-х годах :-)
Леннарт Регебро

Ответы:


160

В наши дни Python 3.0 считается стандартным методом округления, хотя некоторые языковые реализации пока не работают.

Простая техника «всегда вокруг 0,5 вверх» приводит к небольшому смещению в сторону большего числа. При большом количестве вычислений это может быть значительным. Подход Python 3.0 устраняет эту проблему.

Существует более одного метода округления в общем использовании. IEEE 754, международный стандарт математики с плавающей запятой, определяет пять различных методов округления (по умолчанию используется Python 3.0). И есть другие.

Такое поведение не так широко известно, как должно быть. AppleScript был, если я правильно помню, одним из первых, кто применил этот метод округления. Команда roundв AppleScript на самом деле предлагает несколько вариантов, но по умолчанию используется округление к четному, как в IEEE 754. Очевидно, что инженеру, который реализовал roundкоманду, надоели все запросы, чтобы «заставить ее работать, как я узнал в школа ", которую он реализовал именно так: round 2.5 rounding as taught in schoolэто действительная команда AppleScript. :-)


4
Я не знал об этом «стандартном методе округления по умолчанию в наши дни», знаете ли вы (или кто-либо еще), будут ли C / C ++ / Java / Perl или любые другие языки «основного потока» реализовывать округление таким же образом?
Левон

3
Руби делает это. Языки Microsoft .NET делают это. Ява не кажется, однако. Я не могу отследить это для каждого возможного языка, но я думаю, что это наиболее часто встречается в сравнительно недавно разработанных языках. Я полагаю, что C и C ++ достаточно стары, чтобы не делать этого.
любезно

5
Рубин возвращается 3за2.5.round
JFS

14
Я добавил немного об обработке AppleScript этого, потому что мне нравится саркастический способ, которым реализовано «старое» поведение.
добро пожаловать

2
@kindall Этот метод является режимом округления по умолчанию IEEE с 1985 года (когда был опубликован IEEE 754-1985). Он также был режимом округления по умолчанию в C, по крайней мере, с C89 (и, следовательно, также в C ++), однако , поскольку в C99 (и C ++ 11 со спорадической поддержкой до этого) была доступна функция round (), которая использует вместо этого связывается от нуля. Внутреннее округление с плавающей запятой и семейство функций rint () по-прежнему подчиняются настройке режима округления, которая по умолчанию округляется до четного.
Wlerin

41

Вы можете контролировать округление в Py3000, используя модуль Decimal :

>>> decimal.Decimal('3.5').quantize(decimal.Decimal('1'), 
    rounding=decimal.ROUND_HALF_UP)
>>> Decimal('4')

>>> decimal.Decimal('2.5').quantize(decimal.Decimal('1'),    
    rounding=decimal.ROUND_HALF_EVEN)
>>> Decimal('2')

>>> decimal.Decimal('3.5').quantize(decimal.Decimal('1'), 
    rounding=decimal.ROUND_HALF_DOWN)
>>> Decimal('3')

Спасибо .. Я не был знаком с этим модулем. Любая идея, как бы я получить поведение Python v 2.x? Примеры, которые вы показываете, похоже, не делают этого. Просто любопытно, если бы это было возможно.
Левон

1
@Levon: константа ROUND_HALF_UPтакая же, как старое поведение Python 2.X.
Dawg

2
Вы также можете установить контекст для десятичного модуля, который делает это для вас неявно. Смотрите setcontext()функцию.
добро пожаловать

Это именно то, что я искал сегодня. Работает как положено в Python 3.4.3. Также стоит отметить, что вы можете контролировать, сколько он округляет, переключившись quantize(decimal.Decimal('1')на, quantize(decimal.Decimal('0.00')если хотите округлить до ближайших 100, например, за деньги.
Игорь

Это решение работает как замена round(number, ndigits)до тех пор, пока ndigitsоно положительное, но, к сожалению, вы не можете использовать его для замены чего-то подобного round(5, -1).
Пекка Кларк

15

Просто добавлю сюда важное примечание из документации:

https://docs.python.org/dev/library/functions.html#round

Заметка

Поведение round () для чисел с плавающей точкой может быть удивительным: например, round (2.675, 2) дает 2,67 вместо ожидаемых 2,68. Это не ошибка: это результат того факта, что большинство десятичных дробей не могут быть представлены точно как число с плавающей точкой. Посмотрите Арифметику с плавающей запятой: Проблемы и Ограничения для получения дополнительной информации.

Так что не удивляйтесь, если получите следующие результаты в Python 3.2:

>>> round(0.25,1), round(0.35,1), round(0.45,1), round(0.55,1)
(0.2, 0.3, 0.5, 0.6)

>>> round(0.025,2), round(0.035,2), round(0.045,2), round(0.055,2)
(0.03, 0.04, 0.04, 0.06)

Я видел это. И моя первая реакция: кто использует 16-битный процессор, который не способен представить все перестановки «2,67x»? Сказать, что дроби не могут быть выражены с плавающей точкой, кажется здесь козлом отпущения: ни один современный процессор не является таким неточным, в ЛЮБОМ языке (кроме Python?)
Адам

9
@ Адам: Я думаю, что вы недоразумение. Бинарный формат (IEEE 754 binary64), используемый для хранения чисел с плавающей запятой, не может 2.675точно представлять : самый близкий компьютер может быть получен 2.67499999999999982236431605997495353221893310546875. Это довольно близко, но это не точно равно 2.675: это очень немного ближе , 2.67чем 2.68. Таким образом, roundфункция делает правильные вещи и округляет ее до более близкого значения, состоящего из двух цифр после точки, а именно 2.67. Это не имеет ничего общего с Python, и все, что связано с двоичной плавающей запятой.
Марк Дикинсон

3
Это не "правильная вещь", потому что ей дали константу исходного кода :), но я понимаю вашу точку зрения.
Адам

@Adam: Я сталкивался с той же причудой в JS раньше, поэтому она не зависит от языка.
Игорь

5

У меня недавно тоже были проблемы с этим. Поэтому я разработал модуль python 3, который имеет 2 функции trueround () и trueround_precision (), которые решают эту проблему и дают то же поведение при округлении, которое использовалось в начальной школе (не округление банкиров). Вот модуль. Просто сохраните код и скопируйте его или импортируйте. Примечание: модуль trueround_precision может изменять поведение округления в зависимости от потребностей в соответствии с флагами ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_UP и ROUND_05UP в десятичном модуле (см. Дополнительную информацию в документации для модулей). Для функций ниже, смотрите строки документации или используйте help (trueround) и help (trueround_precision), если они скопированы в интерпретатор для дальнейшей документации.

#! /usr/bin/env python3
# -*- coding: utf-8 -*-

def trueround(number, places=0):
    '''
    trueround(number, places)

    example:

        >>> trueround(2.55, 1) == 2.6
        True

    uses standard functions with no import to give "normal" behavior to 
    rounding so that trueround(2.5) == 3, trueround(3.5) == 4, 
    trueround(4.5) == 5, etc. Use with caution, however. This still has 
    the same problem with floating point math. The return object will 
    be type int if places=0 or a float if places=>1.

    number is the floating point number needed rounding

    places is the number of decimal places to round to with '0' as the
        default which will actually return our interger. Otherwise, a
        floating point will be returned to the given decimal place.

    Note:   Use trueround_precision() if true precision with
            floats is needed

    GPL 2.0
    copywrite by Narnie Harshoe <signupnarnie@gmail.com>
    '''
    place = 10**(places)
    rounded = (int(number*place + 0.5if number>=0 else -0.5))/place
    if rounded == int(rounded):
        rounded = int(rounded)
    return rounded

def trueround_precision(number, places=0, rounding=None):
    '''
    trueround_precision(number, places, rounding=ROUND_HALF_UP)

    Uses true precision for floating numbers using the 'decimal' module in
    python and assumes the module has already been imported before calling
    this function. The return object is of type Decimal.

    All rounding options are available from the decimal module including 
    ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, 
    ROUND_HALF_UP, ROUND_UP, and ROUND_05UP.

    examples:

        >>> trueround(2.5, 0) == Decimal('3')
        True
        >>> trueround(2.5, 0, ROUND_DOWN) == Decimal('2')
        True

    number is a floating point number or a string type containing a number on 
        on which to be acted.

    places is the number of decimal places to round to with '0' as the default.

    Note:   if type float is passed as the first argument to the function, it
            will first be converted to a str type for correct rounding.

    GPL 2.0
    copywrite by Narnie Harshoe <signupnarnie@gmail.com>
    '''
    from decimal import Decimal as dec
    from decimal import ROUND_HALF_UP
    from decimal import ROUND_CEILING
    from decimal import ROUND_DOWN
    from decimal import ROUND_FLOOR
    from decimal import ROUND_HALF_DOWN
    from decimal import ROUND_HALF_EVEN
    from decimal import ROUND_UP
    from decimal import ROUND_05UP

    if type(number) == type(float()):
        number = str(number)
    if rounding == None:
        rounding = ROUND_HALF_UP
    place = '1.'
    for i in range(places):
        place = ''.join([place, '0'])
    return dec(number).quantize(dec(place), rounding=rounding)

Надеюсь это поможет,

Narnie


5

Python 3.x округляет .5 значений до соседа, который является четным

assert round(0.5) == 0
assert round(1.5) == 2
assert round(2.5) == 2

import decimal

assert decimal.Decimal('0.5').to_integral_value() == 0
assert decimal.Decimal('1.5').to_integral_value() == 2
assert decimal.Decimal('2.5').to_integral_value() == 2

однако, можно изменить десятичное округление «назад», чтобы всегда округлять .5 вверх, если это необходимо:

decimal.getcontext().rounding = decimal.ROUND_HALF_UP

assert decimal.Decimal('0.5').to_integral_value() == 1
assert decimal.Decimal('1.5').to_integral_value() == 2
assert decimal.Decimal('2.5').to_integral_value() == 3

i = int(decimal.Decimal('2.5').to_integral_value()) # to get an int
assert i == 3
assert type(i) is int

1

Поведение округления в Python 2 в Python 3.

Добавляем 1 на 15 десятичных знаков. Точность до 15 цифр.

round2=lambda x,y=None: round(x+1e-15,y)

3
Не могли бы вы объяснить интуицию этой формулы?
Хади

2
Из того, что я понимаю, фракции, которые не могут быть точно представлены, будут иметь до 15 9, то неточность. Например, 2.675есть 2.67499999999999982236431605997495353221893310546875. Добавление 1e-15 перевернет его на 2.675 и получит правильное округление. если дробь уже превышает константу кода, добавление 1e-15 ничего не изменит для округления.
Бенуа Дюфрен

1

Некоторые случаи:

in: Decimal(75.29 / 2).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
in: round(75.29 / 2, 2)
out: 37.65 GOOD

in: Decimal(85.55 / 2).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
in: round(85.55 / 2, 2)
out: 42.77 BAD

Для исправления:

in: round(75.29 / 2 + 0.00001, 2)
out: 37.65 GOOD
in: round(85.55 / 2 + 0.00001, 2)
out: 42.78 GOOD

Если вы хотите больше десятичных знаков, например 4, вы должны добавить (+ 0,0000001).

Работа для меня


Это было единственное решение, которое сработало для меня, спасибо за публикацию. Кажется, что все стремятся к округлению 0,5 вверх / вниз, поэтому я не смог справиться с проблемами округления в несколько десятичных разрядов.
Гаятри

-1

Образец репродукции:

['{} => {}'.format(x+0.5, round(x+0.5)) for x in range(10)]

['0.5 => 0', '1.5 => 2', '2.5 => 2', '3.5 => 4', '4.5 => 4', '5.5 => 6', '6.5 => 6', '7.5 => 8', '8.5 => 8', '9.5 => 10']

API: https://docs.python.org/3/library/functions.html#round

Состояния:

Возвращаемое число, округленное до точности ndigits после десятичной точки. Если ndigits опущен или равен None, он возвращает ближайшее целое число на вход.

Для встроенных типов, поддерживающих round (), значения округляются до ближайшего кратного 10 к степени минус ndigits; если два мультипликатора одинаково близки, округление выполняется до четного выбора (например, как раунд (0.5), так и раунд (-0.5) равны 0, а раунд (1.5) равен 2). Любое целочисленное значение допустимо для ndigits (положительное, нулевое или отрицательное). Возвращаемое значение является целым числом, если ndigits опущено или отсутствует. В противном случае возвращаемое значение имеет тот же тип, что и число.

Для общего номера объекта Python округлите делегатов до числа. круглый .

Замечание Поведение round () для чисел с плавающей точкой может быть удивительным: например, round (2.675, 2) дает 2.67 вместо ожидаемых 2.68. Это не ошибка: это результат того факта, что большинство десятичных дробей не могут быть представлены точно как число с плавающей точкой. Посмотрите Арифметику с плавающей запятой: Проблемы и Ограничения для получения дополнительной информации.

Учитывая это понимание, вы можете использовать математику, чтобы решить его

import math
def my_round(i):
  f = math.floor(i)
  return f if i - f < 0.5 else f+1

теперь вы можете запустить тот же тест с my_round вместо round.

['{} => {}'.format(x + 0.5, my_round(x+0.5)) for x in range(10)]
['0.5 => 1', '1.5 => 2', '2.5 => 3', '3.5 => 4', '4.5 => 5', '5.5 => 6', '6.5 => 7', '7.5 => 8', '8.5 => 9', '9.5 => 10']

-2

Самый простой способ округления в Python 3.x, как преподается в школе, - это использование вспомогательной переменной:

n = 0.1 
round(2.5 + n)

И это будут результаты серии от 2,0 до 3,0 (с шагом 0,1):

>>> round(2 + n)
>>> 2

>>> round(2.1 + n)
>>> 2

>>> round(2.2 + n)
>>> 2

>>> round(2.3 + n)
>>> 2

>>> round(2.4 + n)
>>> 2

>>> round(2.5 + n)
>>> 3

>>> round(2.6 + n)
>>> 3

>>> round(2.7 + n)
>>> 3

>>> round(2.8 + n)
>>> 3

>>> round(2.9 + n)
>>> 3

>>> round(3 + n)
>>> 3

-2

Вы можете контролировать округление, используя модуль math.ceil:

import math
print(math.ceil(2.5))
> 3

Это всегда будет возвращать число без десятичной части, это не округление. ceil (2.5) = 2, ceil (2.99) = 2
krafter

1
в python3 +, если аргумент числа является положительным или отрицательным числом, функция ceil возвращает значение потолка.
Eds_k

В [14]: math.ceil (2.99) Из [14]: 3
Eds_k

Да, извините, я ошибся. Ceil () возвращает значение потолка, тогда как floor () возвращает значение, о котором я говорил. Но все же, на мой взгляд, это не совсем поведение округления (обе эти функции)
krafter

-4

Попробуйте этот код:

def roundup(input):   
   demo = input  if str(input)[-1] != "5" else str(input).replace("5","6")
   place = len(demo.split(".")[1])-1
   return(round(float(demo),place))

Результатом будет:

>>> x = roundup(2.5)
>>> x
3.0  
>>> x = roundup(2.05)
>>> x
2.1 
>>> x = roundup(2.005)
>>> x
2.01 

Выход вы можете проверить здесь: https://i.stack.imgur.com/QQUkS.png

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.