здається, що круглий () не відповідає округленню


123

Документація для функції round () вказує, що ви передаєте їй число, а позиції, що минають через десятковий, до круглого. Таким чином, слід зробити це:

n = 5.59
round(n, 1) # 5.6

Але, насправді, добре старе дивацтво з плаваючою комою прокрадається і ви отримуєте:

5.5999999999999996

Для цілей користувальницького інтерфейсу мені потрібно відобразити 5.6. Я розібрався в Інтернеті і знайшов деяку документацію про те, що це залежить від моєї реалізації Python. На жаль, це відбувається як на моїй машині розробки Windows, так і на кожному сервері Linux, який я спробував. Дивіться також тут .

Окрім створення власної круглої бібліотеки, чи є спосіб це зробити?


4
Я спробував це з пітоном 2.7.11 раундом (5.59) , і це дає результат , як 5,6 в обох вікнах і Linux x86 64 - бітної машина, Cython (посилання документації згадувалася змінюються тепер я здогадуюся)?
Alex Punnen

2
Там, де це насправді не працює належним чином, є round(5.55, 1) = 5.5.
Дмитро

Відповіді:


101

Я не можу допомогти способу його зберігання, але принаймні форматування працює правильно:

'%.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)
1414

2
Не округлюється правильно: '%.5f' % 0.988625дає0.98862
schlamar

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

на жаль, "% .2f"% 2.675 повернеться 2.67 - що може бути несподіваною відповіддю для тих, хто використовує цей метод і очікує 2,68
Діоні

30

Якщо ви використовуєте модуль Десятковий, ви можете наблизити його без використання функції "круглого". Ось що я використовував для округлення, особливо під час написання грошових заявок:

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

Десятична «заснована на моделі з плаваючою комою, яка була розроблена з урахуванням людей і обов'язково має найважливіший керівний принцип - комп’ютери повинні забезпечувати арифметику, яка працює так само, як арифметика, яку люди вивчають у школі». - уривок із десяткової специфікації арифметики.

і

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

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



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 "правило" полягає в тому, що ви повинні округлюватись, якщо попереднє число було непарним, а не округлим, якщо попереднє число було парним. Кругла функція в python просто обрізає 5.


1
Те, про що ви говорите, - це "округлення банкірів" , один із багатьох різних способів проведення округлення.
Simon 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. Не менш погані проблеми траплялися б, якби епсілон був усередині круглої функції.
Майкл Скотт Катберт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.