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

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

Стратегия округления функции round() и тип возврата изменены. Точные полпути теперь округляются до ближайшего четного результата, а не от нуля. (Например, раунд (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 комментарий о "округлении банкира" дал мне правильный поисковый запрос / ключевые слова для поиска, и я нашел такой вопрос: почему.NET использует округление банкира по умолчанию? так что я буду читать это внимательно.

14 ответов

Решение

В наши дни 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.:-)

Вы можете контролировать округление в 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')

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

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

Заметка

Поведение 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)

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

У меня недавно тоже были проблемы с этим. Поэтому я разработал модуль 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

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

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

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

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

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).

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

Я предлагаю пользовательскую функцию, которая будет работать для DataFrame:

      def dfCustomRound(df, dec):
    d = 1 / 10 ** dec
    df = round(df, dec + 2)
    return (((df % (1 * d)) == 0.5 * d).astype(int) * 0.1 * d * np.sign(df) + df).round(dec)

Оператор округления округляет значение до ближайшего целого числа.

Например:

Если значение больше o.5, то оно округляется до 1

print(round(211.5554, 2)) // output is 211.56

Если значение меньше 0,5, оно округляется до 0

print(round(211.5544, 2)) // output is 211.55

Редактировать:

Ранее упомянутый оператор // не используется для округления, он используется, например, для обработки плавающего вывода деления.

 print(10//3) // output is 3 instead of 3.3333333333333335

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

['{} => {}'.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

Состояния:

Возвращаемое число, округленное до точности 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']
      # round module within numpy when decimal is X.5 will give desired (X+1)

import numpy as np
example_of_some_variable = 3.5
rounded_result_of_variable = np.round(example_of_some_variable,0)
print (rounded_result_of_variable)

Самый простой способ округления в 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

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

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

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

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 

Выходные данные вы можете проверить здесь:

Другие вопросы по тегам