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


176

Я щойно перечитував, що нового в 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

як я і очікував. Однак зараз під версією 3.2.2 :

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 використовує округлення банкіра за замовчуванням? , тому я буду це уважно читати.


27
У мене немає часу на це, але я вважаю, що це називається "заокругленням банкіра". Я вважаю, що це поширене у фінансовій галузі.
Лі-Аун Іп

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

1
@ Li-aungYip Дякую за головний переклад "Округлення банкіра" .. Я буду шукати це.
Левон


3
Лише зауваження: округлення банкірів не часто зустрічається лише у фінансах. Ось як мене навчили
турувати

Відповіді:


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
Рубі це робить. Мови .NET Microsoft це роблять. Java, однак, не здається. Я не можу відстежити її для всіх можливих мов, але, мабуть, це найчастіше зустрічається у досить нещодавно розроблених мовах. Я думаю, що C і C ++ є досить старими, що їх немає.
kindall

5
ruby повертається 3за2.5.round
jfs

14
Я додав трохи про поводження з цим AppleScript, тому що мені подобається саркастичний спосіб реалізації "старої" поведінки.
kindall

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
Ви також можете встановити контекст для модуля Decimal, який робить це для вас неявно. Дивіться setcontext()функцію.
kindall

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

Це рішення працює як заміна до round(number, ndigits)тих пір, поки ndigitsє позитивним, але дратує, ви не можете використовувати його для заміни чогось подібного round(5, -1).
Pekka Klärck

15

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

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)

Я побачив, що. І моя перша реакція: Хто використовує 16-бітний процесор, який не здатний представляти всі перестановки "2.67x"? Сказати, що дроби не можуть бути виражені плаваючою, здається, тут є козлом відпущення: жоден сучасний процесор не є таким неточним, у БУДЬ-якому мовленні (крім Python?)
Адам

9
@Adam: Я думаю, ти нерозумієш. Бінарний формат (IEEE 754 binary64), що використовується для зберігання плавців, не може 2.675точно відображати: найближчий комп'ютер може отримати 2.67499999999999982236431605997495353221893310546875. Це досить близько, але це точно не дорівнює2.675 : це дуже небагато ближче , 2.67ніж 2.68. Таким чином, roundфункція робить правильно, і округляє її до ближчого 2-розрядного значення після точки, а саме 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 (додаткова інформація про модулі (дефіцит інформації про модуль подається у десяткових модулях), де подано прапорці модуля (десяткові відомості про модулі див. У десяткових модулях, де подається інформація про модуль (десяткові відомості про модулі див. У десяткових модулях). Функції нижче див. У документах або скористайтеся довідкою (trueround) та довідкою (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)

Сподіваюся, це допомагає,

Нарні


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 пропущено або немає, воно повертає найближче ціле число до його вводу.

Для вбудованих типів, що підтримують round (), значення округляються до найближчого кратного 10 до потужності мінус ndigits; якщо два кратні однаково близькі, проводиться округлення до рівномірного вибору (так, наприклад, і круглі (0,5), і круглі (-0,5) дорівнюють 0, а круглі (1,5) - 2). Будь-яке ціле значення є дійсним для ndigits (додатне, нульове або від’ємне). Повернене значення є цілим числом, якщо ndigits пропущено або None. Інакше значення повернення має той самий тип, що і число.

Для загального номера об’єкта Python округлете делегати на номер. круглий .

Примітка Поведінка 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 замість круглого.

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

Це завжди поверне число без його десяткової частини, це не округлення. стеля (2,5) = 2, стеля (2,99) = 2
крафтер

1
у python3 +, Якщо аргумент числа є додатним чи від’ємним числом, функція ceil повертає значення стелі.
Eds_k

У [14]: math.ceil (2.99) Вихід [14]: 3
Eds_k

Так, вибачте, що помилився. Ceil () повертає значення стелі, тоді як floor () повертає той, про який я говорив. Але все ж, на мій погляд, це не зовсім
округла

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