round () не округляется правильно


123

В документации для функции round () указано, что вы передаете ей число, а позиции после десятичной дроби нужно округлять. Таким образом, он должен сделать это:

n = 5.59
round(n, 1) # 5.6

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

5.5999999999999996

Для пользовательского интерфейса мне нужно отобразить 5.6. Я покопался в Интернете и нашел документацию о том, что это зависит от моей реализации Python. К сожалению, это происходит как на моей машине разработчика Windows, так и на каждом сервере Linux, который я пробовал. Смотрите также здесь .

Есть ли способ обойти это, если не считать создания собственной круглой библиотеки?


4
Я пробовал это с python 2.7.11 round (5.59), и он дает результат 5.6 как в Windows, так и в 64-битной машине Linux x86, Cython? (Упомянутая ссылка на документацию теперь изменена, я думаю)
Alex Punnen

2
Где это на самом деле не работает правильно, так это round(5.55, 1) = 5.5.
Дмитрий

Ответы:


102

Ничего не могу поделать с тем, как он хранится, но, по крайней мере, форматирование работает правильно:

'%.1f' % round(n, 1) # Gives you '5.6'

11
Я пробовала, print '%.2f' % 655.665но он возвращается 655.66, так и должно быть655.67
Лиза

1
@Kyrie см stackoverflow.com/questions/9301690/... . Здесь виновата неточность с плавающей точкой - «5,665 -> 5,67», но «15,665 -> 15,66». Используйте десятичные дроби, если вам нужна точная точность.
Джимми

7
это работает после поиска :) from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_DOWN# использовать для округления чисел с плавающей запятой Decimal(str(655.665)).quantize(Decimal('1.11'), rounding=ROUND_HALF_UP)# Проблемы и ограничения при использовании чисел с плавающей запятой
Лиза

102

Форматирование работает правильно даже без округления:

"%.1f" % n

18
Согласно документации , этот стиль форматирования строк со временем исчезнет. Формат нового стиля будет таким"{:.1f}".format(n)
здесь,

2
Не '%.5f' % 0.9886250.98862
округляет

@schlamar: Это тоже поведение round (): round (0.988625,5) также дает 0.98862. round (0,988626,5), а также "% .5f"% 0,988626 дают 0,98863
Винко Врсалович

к сожалению, "% .2f"% 2.675 вернет 2,67 - что может быть неожиданным ответом для тех, кто использует этот метод и ожидает 2,68
Дион

30

Если вы используете модуль Decimal, вы можете выполнить аппроксимацию без использования функции «округления». Вот что я использовал для округления, особенно при написании денежных приложений:

Decimal(str(16.2)).quantize(Decimal('.01'), rounding=ROUND_UP)

Это вернет десятичное число, равное 16,20.


4
Это канонический ответ - в любом случае, где точность имеет значение, а это практически везде. Конечно: это немного многословно . Но добавьте эту присоску в вспомогательную функцию, и все готово, и все готово.
Сесил Карри,

2
rounding='ROUND_UP'
LMc

Если вы получаете эту ошибку NameError: global name 'ROUND_UP' is not definedнеобходимо импортировать округление функции: from decimal import Decimal, ROUND_UP. Другие функции округления
Стивен Блэр

Ваш пример все еще кажется опасным: вы полагаетесь на округление, обеспечиваемое str ().
YvesgereY

21

round(5.59, 1)работает нормально. Проблема в том, что 5.6 нельзя точно представить в двоичной системе с плавающей запятой.

>>> 5.6
5.5999999999999996
>>> 

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

В Python есть модуль для десятичной арифметики, если он вам нужен.


1
Это больше не проблема Python 2.7 или Python 3.5
vy32


10

Вы можете переключить тип данных на целое число:

>>> n = 5.59
>>> int(n * 10) / 10.0
5.5
>>> int(n * 10 + 0.5)
56

Затем отобразите число, вставив десятичный разделитель языкового стандарта.

Однако ответ Джимми лучше.


5

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


5

Взгляните на модуль Decimal

Decimal «основан на модели с плавающей запятой, которая была разработана для людей и обязательно имеет первостепенный руководящий принцип - компьютеры должны обеспечивать арифметику, которая работает так же, как арифметика, которую люди изучают в школе». - выдержка из спецификации десятичной арифметики.

и

Десятичные числа могут быть представлены точно. Напротив, числа, подобные 1.1 и 2.2, не имеют точного представления в двоичной системе с плавающей запятой. Конечные пользователи обычно не ожидают, что 1.1 + 2.2 будет отображаться как 3.3000000000000003, как это происходит с двоичной плавающей запятой.

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



4

Это действительно большая проблема. Попробуйте этот код:

print "%.2f" % (round((2*4.4+3*5.6+3*4.4)/8,2),)

Он отображает 4.85. Затем вы делаете:

print "Media = %.1f" % (round((2*4.4+3*5.6+3*4.4)/8,1),)

и показывает 4.8. Вы подсчитываете вручную, точный ответ - 4,85, но если вы попробуете:

print "Media = %.20f" % (round((2*4.4+3*5.6+3*4.4)/8,20),)

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


3

Вы можете использовать оператор строкового формата %, аналогичный sprintf.

mystring = "%.2f" % 5.5999


2

Я делаю:

int(round( x , 0))

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

так

>>> int(round(5.59,0))
6

Я думаю, что этот ответ работает лучше, чем формирование строки, и мне также удобнее использовать круглую функцию.


2

Я бы вообще не стал полагаться round()в этом случае. Рассматривать

print(round(61.295, 2))
print(round(1.295, 2))

выведет

61.3
1.29

что не является желаемым результатом, если вам нужно твердое округление до ближайшего целого числа. Чтобы обойти это поведение, используйте math.ceil()(или, math.floor()если вы хотите округлить):

from math import ceil
decimal_count = 2
print(ceil(61.295 * 10 ** decimal_count) / 10 ** decimal_count)
print(ceil(1.295 * 10 ** decimal_count) / 10 ** decimal_count)

выходы

61.3
1.3

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


1

Код:

x1 = 5.63
x2 = 5.65
print(float('%.2f' % round(x1,1)))  # gives you '5.6'
print(float('%.2f' % round(x2,1)))  # gives you '5.7'

Вывод:

5.6
5.7

0

Вот где я вижу неудачу раунда. Что, если вы захотите округлить эти 2 числа до одного десятичного знака? 23,45 23,55 Мое образование заключалось в том, что округляя их, вы должны получить: 23,4 23,6 «правило» состоит в том, что вы должны округлять в большую сторону, если предыдущее число было нечетным, а не в большую, если предыдущее число было четным. Функция round в Python просто обрезает 5.


1
Вы говорите о "банковском округлении" , одном из многих способов округления.
Саймон MᶜKenzie

0

Проблема только в том, что последняя цифра равна 5. Например. 0,045 хранится внутри как 0,044999999999999 ... Вы можете просто увеличить последнюю цифру до 6 и округлить. Это даст вам желаемый результат.

import re


def custom_round(num, precision=0):
    # Get the type of given number
    type_num = type(num)
    # If the given type is not a valid number type, raise TypeError
    if type_num not in [int, float, Decimal]:
        raise TypeError("type {} doesn't define __round__ method".format(type_num.__name__))
    # If passed number is int, there is no rounding off.
    if type_num == int:
        return num
    # Convert number to string.
    str_num = str(num).lower()
    # We will remove negative context from the number and add it back in the end
    negative_number = False
    if num < 0:
        negative_number = True
        str_num = str_num[1:]
    # If number is in format 1e-12 or 2e+13, we have to convert it to
    # to a string in standard decimal notation.
    if 'e-' in str_num:
        # For 1.23e-7, e_power = 7
        e_power = int(re.findall('e-[0-9]+', str_num)[0][2:])
        # For 1.23e-7, number = 123
        number = ''.join(str_num.split('e-')[0].split('.'))
        zeros = ''
        # Number of zeros = e_power - 1 = 6
        for i in range(e_power - 1):
            zeros = zeros + '0'
        # Scientific notation 1.23e-7 in regular decimal = 0.000000123
        str_num = '0.' + zeros + number
    if 'e+' in str_num:
        # For 1.23e+7, e_power = 7
        e_power = int(re.findall('e\+[0-9]+', str_num)[0][2:])
        # For 1.23e+7, number_characteristic = 1
        # characteristic is number left of decimal point.
        number_characteristic = str_num.split('e+')[0].split('.')[0]
        # For 1.23e+7, number_mantissa = 23
        # mantissa is number right of decimal point.
        number_mantissa = str_num.split('e+')[0].split('.')[1]
        # For 1.23e+7, number = 123
        number = number_characteristic + number_mantissa
        zeros = ''
        # Eg: for this condition = 1.23e+7
        if e_power >= len(number_mantissa):
            # Number of zeros = e_power - mantissa length = 5
            for i in range(e_power - len(number_mantissa)):
                zeros = zeros + '0'
            # Scientific notation 1.23e+7 in regular decimal = 12300000.0
            str_num = number + zeros + '.0'
        # Eg: for this condition = 1.23e+1
        if e_power < len(number_mantissa):
            # In this case, we only need to shift the decimal e_power digits to the right
            # So we just copy the digits from mantissa to characteristic and then remove
            # them from mantissa.
            for i in range(e_power):
                number_characteristic = number_characteristic + number_mantissa[i]
            number_mantissa = number_mantissa[i:]
            # Scientific notation 1.23e+1 in regular decimal = 12.3
            str_num = number_characteristic + '.' + number_mantissa
    # characteristic is number left of decimal point.
    characteristic_part = str_num.split('.')[0]
    # mantissa is number right of decimal point.
    mantissa_part = str_num.split('.')[1]
    # If number is supposed to be rounded to whole number,
    # check first decimal digit. If more than 5, return
    # characteristic + 1 else return characteristic
    if precision == 0:
        if mantissa_part and int(mantissa_part[0]) >= 5:
            return type_num(int(characteristic_part) + 1)
        return type_num(characteristic_part)
    # Get the precision of the given number.
    num_precision = len(mantissa_part)
    # Rounding off is done only if number precision is
    # greater than requested precision
    if num_precision <= precision:
        return num
    # Replace the last '5' with 6 so that rounding off returns desired results
    if str_num[-1] == '5':
        str_num = re.sub('5$', '6', str_num)
    result = round(type_num(str_num), precision)
    # If the number was negative, add negative context back
    if negative_number:
        result = result * -1
    return result

0

Другой возможный вариант:

def hard_round(number, decimal_places=0):
    """
    Function:
    - Rounds a float value to a specified number of decimal places
    - Fixes issues with floating point binary approximation rounding in python
    Requires:
    - `number`:
        - Type: int|float
        - What: The number to round
    Optional:
    - `decimal_places`:
        - Type: int 
        - What: The number of decimal places to round to
        - Default: 0
    Example:
    ```
    hard_round(5.6,1)
    ```
    """
    return int(number*(10**decimal_places)+0.5)/(10**decimal_places)

-4

Что о:

round(n,1)+epsilon

Это будет работать только в том случае, если округление будет постоянно отличаться от круглого числа на эпсилон. Если epsilon = .000001тогда round(1.0/5.0, 1) + epsilonвзять точное представление 0,2 и сделать его 0,00001. Не менее серьезные проблемы возникли бы, если бы эпсилон находился внутри функции round.
Майкл Скотт Катберт
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.