Подтвердить что ты не робот

Как правильно округлить половину чисел с плавающей точкой в ​​Python?

Я столкнулся с странным поведением функции round():

for i in range(1, 15, 2):
    n = i / 2
    print(n, "=>", round(n))

Этот код печатает:

0.5 => 0
1.5 => 2
2.5 => 2
3.5 => 4
4.5 => 4
5.5 => 6
6.5 => 6

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

Почему такое поведение и какой лучший способ получить правильный результат?

Я попытался использовать fractions, но результат тот же.

4b9b3361

Ответ 1

Раздел " Числовые типы" явно описывает это поведение:

round(x[, n])
х округляется до n цифр, округляя половину до четности. Если n опущено, по умолчанию оно равно 0.

Обратите внимание на округление от половины до вечера. Это также называется округлением банкиров; вместо того, чтобы всегда округлять вверх или вниз (составляя ошибки округления), округляя до ближайшего четного числа, вы усредняете ошибки округления.

Если вам нужен больший контроль над поведением округления, используйте decimal модуль, который позволяет вам точно указать, какую стратегию округления следует использовать.

Например, чтобы округлить с половины:

>>> from decimal import localcontext, Decimal, ROUND_HALF_UP
>>> with localcontext() as ctx:
...     ctx.rounding = ROUND_HALF_UP
...     for i in range(1, 15, 2):
...         n = Decimal(i) / 2
...         print(n, '=>', n.to_integral_value())
...
0.5 => 1
1.5 => 2
2.5 => 3
3.5 => 4
4.5 => 5
5.5 => 6
6.5 => 7

Ответ 2

Например:

from decimal import Decimal, ROUND_HALF_UP

Decimal(1.5).quantize(0, ROUND_HALF_UP)

# This also works for rounding to the integer part:
Decimal(1.5).to_integral_value(rounding=ROUND_HALF_UP)

Ответ 3

round() округляется вверх или вниз, в зависимости от того, является ли число четным или нечетным. Простой способ только округлить это:

int(num + 0.5)

Если вы хотите, чтобы это работало правильно для отрицательных чисел, используйте:

((num > 0) - (num < 0)) * int(abs(num) + 0.5)

Обратите внимание, что это может испортить большие числа или действительно точные числа, такие как 5000000000000001.0 и 0.49999999999999994.

Ответ 4

Вы можете использовать это:

import math
def normal_round(n):
    if n - math.floor(n) < 0.5:
        return math.floor(n)
    return math.ceil(n)

Он будет округлить число вверх или вниз должным образом.

Ответ 5

Поведение, которое вы видите, - типичное поведение округления IEEE 754. Если он должен выбирать между двумя номерами, которые одинаково отличаются от ввода, он всегда выбирает один. Преимущество такого поведения заключается в том, что средний эффект округления равен нулю - одинаково много чисел округляется вверх и вниз. Если вы округлите половину номеров в последовательном направлении, округление повлияет на ожидаемое значение.

Поведение, которое вы видите, является правильным, если цель объективного округления, но это не всегда то, что необходимо.

Один трюк, чтобы получить тип округления, который вы хотите, - это добавить 0.5, а затем взять слово. Например, добавление от 0,5 до 2,5 дает 3, с полом 3.

Ответ 6

Краткая версия: используйте десятичный модуль . Он может точно представлять числа, такие как 2.675, в отличие от плавающих Python, где 2.675 действительно 2.67499999999999982236431605997495353221893310546875 (точно). И вы можете указать желаемое округление: ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_UP и ROUND_05UP - все варианты.

Ответ 7

Округление до ближайшего четного числа стало обычной практикой в ​​числовых дисциплинах. "Округление" приводит к небольшому смещению к более крупным результатам.

Итак, с точки зрения научного учреждения round имеет правильное поведение.

Ответ 8

Вы можете использовать:

from decimal import Decimal, ROUND_HALF_UP

for i in range(1, 15, 2):
    n = i / 2
    print(n, "=>", Decimal(str(n)).quantize(Decimal("1"), rounding=ROUND_HALF_UP))

Ответ 9

Люблю ответ fedor2612. Я расширил его необязательным аргументом "десятичные дроби" для тех, кто хочет использовать эту функцию для округления любого количества десятичных дробей (например, если вы хотите округлить валюту от $ 26,455 до $ 26,46).

import math

def normal_round(n, decimals=0):
    expoN = n * 10 ** decimals
    if abs(expoN) - abs(math.floor(expoN)) < 0.5:
        return math.floor(expoN) / 10 ** decimals
    return math.ceil(expoN) / 10 ** decimals

oldRounding = round(26.455,2)
newRounding = normal_round(26.455,2)

print(oldRounding)
print(newRounding)

Выход:

26,45

26,46

Ответ 10

Вот еще одно решение. Это будет работать как обычное округление в Excel.

from decimal import Decimal, getcontext, ROUND_HALF_UP

round_context = getcontext()
round_context.rounding = ROUND_HALF_UP

def c_round(x, digits, precision=5):
    tmp = round(Decimal(x), precision)
    return float(tmp.__round__(digits))

c_round(0.15, 1) → 0.2, c_round(0.5, 0) → 1

Ответ 11

Классическое математическое округление без каких-либо библиотек

def rd(x,y=0):
''' A classical mathematical rounding by Voznica '''
m = int('1'+'0'*y) # multiplier - how many positions to the right
q = x*m # shift to the right by multiplier
c = int(q) # new number
i = int( (q-c)*10 ) # indicator number on the right
if i >= 5:
    c += 1
return c/m

Compare:

print( round(0.49), round(0.51), round(0.5), round(1.5), round(2.5), round(0.15,1))  # 0  1  0  2  2  0.1

print( rd(0.49), rd(0.51), rd(0.5), rd(1.5), rd(2.5), rd(0.15,1))  # 0  1  1  2  3  0.2

Ответ 12

Следующее решение достигло "округления школьной моды" без использования decimal модуля (который оказывается медленным).

def school_round(a_in,n_in):
''' python uses "banking round; while this round 0.05 up" '''
    if (a_in * 10 ** (n_in + 1)) % 10 == 5:
        return round(a_in + 1 / 10 ** (n_in + 1), n_in)
    else:
        return round(a_in, n_in)

например

print(round(0.005,2)) # 0
print(school_round(0.005,2)) #0.01

Ответ 13

Вы можете попробовать это

def round(num):
    return round(num + 10**(-9))

это будет работать, так как num = x.5 всегда будет x.5 + 0.00...01 в процессе, который ближе к x+1 следовательно, функция округления будет работать правильно и будет округлять x.5 до x+1

Ответ 14

В этом вопросе это в основном проблема при делении положительного целого числа на 2. Самый простой способ - это int(n + 0.5) для отдельных чисел.

Однако мы не можем применить это к серии, поэтому то, что мы затем можем сделать, например, для кадра данных pandas, и не вдаваясь в циклы, это:

import numpy as np
df['rounded_division'] = np.where(df['some_integer'] % 2 == 0, round(df['some_integer']/2,0), round((df['some_integer']+1)/2,0))