Обмеження плаває до двох знаків після коми


1692

Я хочу aбути округленим до 13,95 .

>>> a
13.949999999999999
>>> round(a, 2)
13.949999999999999

roundФункція не працює так , як я очікував.



6
Хм ... Ви намагаєтесь представляти валюту? Якщо так, то ви не повинні використовувати поплавці за долари. Напевно, ви можете використовувати поплавці за копійки або будь-яку найменшу загальну валюту, яку ви намагаєтеся моделювати, але найкраща практика - використовувати десяткове представлення, як запропонував HUAGHAGUAH у своїй відповіді.
SingleNegationElimination

63
Важливо не представляти валюту в плаваючому розмірі. Поплавці не точні. Але суми копійок або відсотків - цілі числа. Тому цілі числа є правильним способом представлення валюти.
Davoud Taghawi-Nejad

2
@ DavoudTaghawi-Nejad або більше до десяткового
Основний

17
Я приїжджаю, мабуть, занадто пізно сюди, але я хотів запитати, чи вирішили розробники Python цю проблему? Тому що, коли я роблю раунд (13.949999999999999, 2), я просто отримую 13,95. Я спробував це в Python 2.7.6, а також 3.4. Це працює. Не впевнений, 2,7 навіть там був у 2009 році. Можливо, це питання Python 2.5?
bad_keypoints

Відповіді:


1689

Ви стикаєтеся зі старою проблемою з числами з плаваючою комою, що не всі числа можуть бути представлені точно. Командний рядок просто показує вам повну форму з плаваючою точкою з пам'яті.

З представленням з плаваючою комою ваша округла версія - це те саме число. Оскільки комп'ютери є двійковими, вони зберігають числа з плаваючою комою як ціле число, а потім ділять його на потужність двох, тому 13,95 буде представлено аналогічно 125650429603636838 / (2 ** 53).

Числа подвійної точності мають 53 біт (16 цифр) точності, а звичайні поплавці мають 24 біти (8 цифр) точності. Тип плаваючої точки в Python використовує подвійну точність для зберігання значень.

Наприклад,

>>> 125650429603636838/(2**53)
13.949999999999999

>>> 234042163/(2**24)
13.949999988079071

>>> a = 13.946
>>> print(a)
13.946
>>> print("%.2f" % a)
13.95
>>> round(a,2)
13.949999999999999
>>> print("%.2f" % round(a, 2))
13.95
>>> print("{:.2f}".format(a))
13.95
>>> print("{:.2f}".format(round(a, 2)))
13.95
>>> print("{:.15f}".format(round(a, 2)))
13.949999999999999

Якщо у вас є лише два десяткових знаки (наприклад, для відображення вартості валюти), ви маєте кілька кращих варіантів:

  1. Використовуйте цілі числа і зберігайте значення в центах, а не в доларах, а потім розділіть на 100, щоб перетворити на долари.
  2. Або використовувати фіксований номер, як десятковий .

27
@Christian Існує принципова різниця між збереженим значенням і способом відображення цього значення. Форматування виводу повинно дозволяти додавати прокладки за потребою, а також додавати роздільники комами тощо.
Основне

22
Варто згадати, що "%.2f" % round(a,2)ви можете помістити не тільки у printf, але й у такі речі, якstr()
andilabs

20
чому це так, що люди завжди беруть валюту на округлення з плаваючою комою? іноді просто хочеться працювати з меншою точністю.
worc

9
@radtek: Вам потрібно зрозуміти, що двійкове значення (типу float) - це лише найближче наближення десяткового числа (яке вам відомо як людину). Не існує такого (кінцево представленого) бінарного значення, як 0,245. Він просто не існує і математично не може існувати. Бінарне значення, яке найближче до 0,245, трохи менше 0,245, тому, природно, воно округляється вниз. Аналогічно, у двійковій формі немає такого поняття, як 0,225, але бінарне значення, яке є найближчим до 0,225, трохи перевищує 0,225, тому, природно, воно округляється.
Джон Y

12
@radtek: Ви буквально просили пояснення. Насправді найпростішим рішенням є використання Decimal, і це було одне з рішень, представлених у цій відповіді. Іншим було перетворити ваші величини на цілі числа та використовувати цілу арифметику. Обидва ці підходи з'явилися і в інших відповідях та коментарях.
Джон Y

586

Існують нові специфікації формату, Міні-мова специфікації строкового формату :

Ви можете зробити так само, як:

"{:.2f}".format(13.949999999999999)

Примітка 1: вищезазначене повертає рядок. Для того щоб отримати як плав, просто оберніть float(...):

float("{:.2f}".format(13.949999999999999))

Примітка 2: обгортання з допомогою float()нічого не змінює:

>>> x = 13.949999999999999999
>>> x
13.95
>>> g = float("{:.2f}".format(x))
>>> g
13.95
>>> x == g
True
>>> h = round(x, 2)
>>> h
13.95
>>> x == h
True

17
додавати коми також можна, '{0:,.2f}'.format(1333.949999999)які відбитки '1,333.95'.
Стівен Блум

@ OnurYıldırım: так, але ви можете обернути це float(); float("{0:.2f}".format(13.9499999))
Йозеф Харуш

5
@JossefHarush ви можете обернути його float (), але ви нічого не отримали. Тепер у вас знову поплавок, з усією ж неточністю. 13,9499999999999 та 13,95 - це той самий поплавок.
Нед Батчелдер

4
@NedBatchelder: Я згоден, що вони рівні, але це обмежує поплавок до двох десяткових знаків :)
Джозеф Харуш

8
До речі, з Python 3.6 ми можемо використовувати f-рядки:f"Result is {result:.2f}"
Андрій

289

Вбудований round()працює чудово в Python 2.7 або пізнішої версії.

Приклад:

>>> round(14.22222223, 2)
14.22

Ознайомтеся з документацією .


1
Тож я маю зрозуміти, що це Python 2.7? Чому така фундаментальна функція дасть різні результати від v 2.7 до v 3?
MikeM

але round(2.16, 1)дати 2.2чому пітон просто пропонують truncateFUNC
jiamo

Наприклад, якщо ви спробуєте округлити значення 2.675 до двох знаків після коми, ви отримаєте це >>> round(2.675, 2) 2.67 docs.python.org/2/tutorial/floatingpoint.html
небезпека89

4
З сторінки документації Python 3:Note The behavior of round() for floats can be surprising: for example, round(2.675, 2) gives 2.67 instead of the expected 2.68. This is not a bug: it’s a result of the fact that most decimal fractions can’t be represented exactly as a float.
Річард Даллі

Зауважте, що якщо ви спробуєте скористатися цим методом для друку такого числа, як 1.00000, він буде роздруковувати лише 1,0, незалежно від того, скільки десяткових знаків ви вказали.
Джош Коррея

142

Я відчуваю, що найпростіший підхід - використовувати format()функцію.

Наприклад:

a = 13.949999999999999
format(a, '.2f')

13.95

Це дає число з поплавком у вигляді рядка, округленого до двох знаків після коми.


97

Використовуйте

print"{:.2f}".format(a)

замість

print"{0:.2f}".format(a)

Оскільки останні можуть призвести до помилок виводу при спробі вивести кілька змінних (див. Коментарі).


2
Це нісенітниця. Два наведені твердження поводяться однаково на Python 2.7, і лише друге твердження є дійсним для Python 2.6. (Жодне твердження не дійсне в Python 3 або Python <2.6.) Перша форма не має переваги крім стислості.
Марк Дікінсон

1
Я маю на увазі, друкувати "{0: .2f} {0: .2f}". Формат (a, b) призведе до помилки у виведенні - він виведе значення "a" двічі. Під час друку "{:. 2f} {: .2f}". Формат (a, b) видасть значення "a" та "b".
Олексій Антоненко

2
Для Python 3 просто потрібно додати дужки друку (...). І всередині них все, що я написав, правильно.
Олексій Антоненко

"Я маю на увазі, друкувати" {0: .2f} {0: .2f} ". Формат (a, b) призведе до помилки у виведенні". Ага. Ну, це зовсім інше твердження! Можливо, ви повинні відредагувати свою відповідь? (Що означає "помилка підвищення", наприклад, у поточній відповіді, наприклад? Чи можете ви навести приклад випадку, коли друге твердження викликає виняток, але перше - ні?)
Марк Дікінсон

3
Ви будете після друку ("{0: .2f} {1: .2f}". Формат (a, b)), якщо у вас є дві змінні
Hovo

95

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

>>> "%.2f" % 3.14159
'3.14'
>>> "%.2f" % 13.9499999
'13.95'

І нарешті, хоча, мабуть, найголовніше, якщо ви хочете точної математики, тоді ви взагалі не хочете плавати. Звичайний приклад - робота з грошима та зберігання "центів" як ціле число.


68

Спробуйте код нижче:

>>> a = 0.99334
>>> a = int((a * 100) + 0.5) / 100.0 # Adding 0.5 rounds it up
>>> print a
0.99

Але будьте обережні, значення a - це все-таки неточний плавець. Погляньте тут - repl.it/LJs (натисніть "Запустити сесію" у верхній частині правого розділу).
життєвий баланс

3
Якщо ви підете з таким підходом, вам слід додати 0,5 для більш точного подання. int (a * 100 + 0,5) / 100,0; Використання math.ceil - це ще один варіант.
arhuaco

3
@ShashankSawant: Ну, з одного боку, відповідь, як це подано, не є округлим, він обрізається. Пропозиція додати половину наприкінці буде заокруглена, але тоді немає ніякої користі робити це над просто використанням roundфункції в першу чергу. З іншого боку, оскільки це рішення все ще використовує плаваючу крапку, оригінальна проблема ОП залишається навіть для "виправленої" версії цього "рішення".
Джон Y

3
-1, це лише непотрібне повторне виконання roundфункції (яка була використана у питанні).
interjay

4
@interjay, який необхідний, якщо round()це не працює, як згадується в ОП.
Пітікос

57

TLDR;)

Проблема округлення вводу / виводу була остаточно вирішена Python 2.7.0 та 3.1 .

Правильно округлене число може бути зворотно перетворене вперед і назад:
str -> float() -> repr() -> float() ...або Decimal -> float -> str -> Decimal
Тип Десятковий більше не потрібен для зберігання.


(Природно, може знадобитися округлення результату додавання чи віднімання округлих чисел, щоб усунути накопичені останні помилки бітів. Явна десяткова арифметика може бути ще зручною, але перетворення на рядок на str()(тобто з округленням до 12 дійсних цифр ) достатньо хороший, якщо не потрібна надзвичайна точність або не потрібна велика кількість послідовних арифметичних операцій.)

Нескінченний тест :

import random
from decimal import Decimal
for x in iter(random.random, None):           # Verify FOREVER that rounding is fixed :-)
    assert float(repr(x)) == x                # Reversible repr() conversion.
    assert float(Decimal(repr(x))) == x
    assert len(repr(round(x, 10))) <= 12      # Smart decimal places in repr() after round.
    if x >= 0.1:                              # Implicit rounding to 12 significant digits
        assert str(x) == repr(round(x, 12))   # by str() is good enough for small errors.
        y = 1000 * x                             # Decimal type is excessive for shopping
        assert str(y) == repr(round(y, 12 - 3))  # in a supermaket with Python 2.7+ :-)

Документація

Див. Примітки до випуску Python 2.7 - Інша мова Змінює четвертий абзац:

Перетворення між номерами з плаваючою комою та рядками тепер правильно округляються на більшості платформ. Ці перетворення відбуваються у багатьох місцях: str () на плавках та складних числах; поплавкові та складні конструктори; чисельне форматування; сериализации і де-сериализации поплавці і комплексних чисел з використанням marshal, pickleі jsonмодулів; розбір плаваючих та уявних літералів в коді Python; і десяткове перетворення в плавати.

З цим пов'язано, repr () числа з плаваючою комою x тепер повертає результат на основі найкоротшого десяткового рядка, який гарантовано округлює назад до x при правильному округленні (при режимі округлення від пів-до рівного). Раніше він давав рядок на основі округлення х до 17 десяткових цифр.

Супутнє питання


Додаткова інформація: Форматування floatдо Python 2.7 було схожим на поточне numpy.float64. Обидва типи використовують однакову 64-бітну IEEE 754 подвійної точності з 52-бітною мантісою. Велика різниця полягає в тому, що np.float64.__repr__вони часто форматуються з надмірним десятковим числом, так що жоден біт не може бути втрачений, але не існує дійсного номера IEEE 754 між 13.9499999999999999 та 13.950000000000001. Результат не є приємним, і перетворення repr(float(number_as_string))не є оборотним з нумером. З іншої сторони:float.__repr__відформатовано так, щоб кожна цифра була важливою; послідовність без прогалин і перетворення є оборотним. Просто: Якщо у вас, можливо, є число numpy.float64, перетворіть його у звичайний плаваючий код, щоб він був форматований для людей, а не для числових процесорів, інакше нічого більше не потрібно з Python 2.7+.


Чому відмовились? Питання стосувалося Python float(подвійна точність) та нормальне round, а не про numpy.double та його перетворення у рядок. Звичайне округлення Python дійсно неможливо зробити краще, ніж у Python 2.7. Більшість відповідей написано раніше 2,7, але вони застаріли, хоча спочатку були дуже хорошими. Це причина моєї відповіді.
hynekcer

53 біт, коли ви включаєте "прихований біт", який неявно 1, за винятком "поступового переливання".
Рік Джеймс

Це не вина, а це дисплей.
Рік Джеймс

Так, це добре відомо. Однак я пропускаю контекст, якщо ви заперечуєте проти чогось у примітках Python 2.7 або випуску в моєму тексті або взагалі нічого. Це складніше, ніж було необхідне мета цього питання. Слід додати, що також перетворення з рядка в плаваючий було зафіксовано в Python 2.7 через округлення помилки на певних 32-бітових мікросхемах Intel і що "функція round () також тепер правильно округлена". ( Примітки до випуску - 3.1 функції, підтримувані на 2.7 ). Чи можете ви погодитися?
hynekcer

1
На жаль, це було a*bпроти b*a. Дякую за посилання - Ностальгія.
Рік Джеймс

52

З Python <3 (наприклад, 2.6 або 2.7) це можна зробити двома способами.

# Option one 
older_method_string = "%.9f" % numvar

# Option two (note ':' before the '.9f')
newer_method_string = "{:.9f}".format(numvar)

Але зауважте, що для версій Python вище 3 (наприклад, 3,2 або 3,3) варіант 2 є кращим .

Для отримання додаткової інформації щодо другого варіанту пропоную це посилання щодо форматування рядків з документації Python .

А для отримання додаткової інформації щодо першого варіанту цього посилання буде достатньо і містить інформацію про різні прапори .

Довідка: Перетворіть номер плаваючої точки до певної точності та скопіюйте в рядок


Як ви представляєте ціле число? Якщо я використовую формат "{i3}". (Numvar), я отримую помилку.
skytux

Це те, що я маю на увазі: якщо numvar=12.456, тоді "{:.2f}".format(numvar)поступається, 12.46але "{:2i}".format(numvar)дає помилку, і я очікую 12.
skytux


47

Здається, ніхто ще не згадував про це, тому дозвольте навести приклад у форматі f-string / template-string Python 3.6, який, на мою думку, є красивим:

>>> f'{a:.2f}'

Він добре працює і з більш тривалими прикладами, з операторами та не потребує паронів:

>>> print(f'Completed in {time.time() - start:.2f}s')


29

У Python 2.7:

a = 13.949999999999999
output = float("%0.2f"%a)
print output

1
Це зовсім не допомагає. outputмає точно таке ж значення a, що й ви, можливо, також написали print aзамість print outputостаннього рядка.
Марк Дікінсон

@MarkDickinson Чи можете ви спробуйте ще раз. Тому що він працює так, як очікувалося в моєму компіляторі.
Шашанк Сінгх

1
Ви пропускаєте мою думку. Так, ваш код друкує 13.95. Але це стосується саме print aцього значення aв Python 2.7, тому не зовсім зрозуміло, в чому полягав крок крокування форматування.
Марк Дікінсон

@MarkDickinson Я відредагував код. Я погоджуюся, що "print a" друкує те саме значення, що і "print print". Але якщо порівнювати "a == вихід", то результат буде "Неправдивим", оскільки крок форматування округляє плаваюче значення "a" до двох десяткових знаків.
Шашанк Сінгх

1
Ви насправді намагалися a == outputввести код, який ви показуєте? Це дає Trueдля мене, і я підозрюю, що це робить і для вас.
Марк Дікінсон

22

У підручнику Python є додаток під назвою Арифметика з плаваючою точкою: питання та обмеження . Читати. Це пояснює, що відбувається і чому Python робить все можливе. Є навіть приклад, який відповідає вашому. Дозвольте мені трохи навести:

>>> 0.1
0.10000000000000001

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

>>> round(0.1, 1)
0.10000000000000001

Проблема полягає в тому, що бінарне значення з плаваючою комою, що зберігається, “0.1” було вже найкращим бінарним наближенням до цього 1/10, тому спроба повторити його знову не може зробити його кращим: воно було настільки ж хорошим, як воно отримує.

Інший наслідок полягає в тому, що оскільки 0.1 це не точно 1/10, підсумовування десяти значень 0.1може не отримати точно 1.0, або:

>>> sum = 0.0
>>> for i in range(10):
...     sum += 0.1
...
>>> sum
0.99999999999999989

Однією з альтернатив та рішенням ваших проблем буде використання decimalмодуля.


18

Як вказував @Matt, Python 3.6 надає f-рядки , і вони також можуть використовувати вкладені параметри :

value = 2.34558
precision = 2
width = 4

print(f'result: {value:{width}.{precision}f}')

який відобразиться result: 2.35



11

Використовуйте комбінацію методу об'єкта Decimal і round ().

Python 3.7.3
>>> from decimal import Decimal
>>> d1 = Decimal (13.949999999999999) # define a Decimal
>>> d1 
Decimal('13.949999999999999289457264239899814128875732421875')
>>> d2 = round(d1, 2) # round to 2 decimals
>>> d2
Decimal('13.95')

7

Для фіксації плаваючої точки в динамічних мовах типу, таких як Python та JavaScript, я використовую цю техніку

# For example:
a = 70000
b = 0.14
c = a * b

print c # Prints 980.0000000002
# Try to fix
c = int(c * 10000)/100000
print c # Prints 980

Ви також можете використовувати Decimal у такий спосіб:

from decimal import *
getcontext().prec = 6
Decimal(1) / Decimal(7)
# Results in 6 precision -> Decimal('0.142857')

getcontext().prec = 28
Decimal(1) / Decimal(7)
# Results in 28 precision -> Decimal('0.1428571428571428571428571429')

1
getcontext().prec = 6працює лише для сфери функції або для всіх місць?
Хуліо Марінс

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

7
from decimal import Decimal


def round_float(v, ndigits=2, rt_str=False):
    d = Decimal(v)
    v_str = ("{0:.%sf}" % ndigits).format(round(d, ndigits))
    if rt_str:
        return v_str
    return Decimal(v_str)

Результати:

Python 3.6.1 (default, Dec 11 2018, 17:41:10)
>>> round_float(3.1415926)
Decimal('3.14')
>>> round_float(3.1445926)
Decimal('3.14')
>>> round_float(3.1455926)
Decimal('3.15')
>>> round_float(3.1455926, rt_str=True)
'3.15'
>>> str(round_float(3.1455926))
'3.15'

6
orig_float = 232569 / 16000.0

14.5355625

short_float = float("{:.2f}".format(orig_float)) 

14.54



4

Це просто, як 1,2,3:

  1. використовувати десятковий модуль для швидкої арифметики з десятковою плаваючою колом:

    d = десяткова (10000000.0000009)

щоб досягти округлення:

   d.quantize(Decimal('0.01'))

матиме результати Decimal('10000000.00')

  1. зробити вище СУХОГО:
    def round_decimal(number, exponent='0.01'):
        decimal_value = Decimal(number)
        return decimal_value.quantize(Decimal(exponent))

АБО

    def round_decimal(number, decimal_places=2):
        decimal_value = Decimal(number)
        return decimal_value.quantize(Decimal(10) ** -decimal_places)
  1. підтверджуйте цю відповідь :)

PS: критика інших: форматування не є округленням.


2

Для округлення числа до роздільної здатності найкращий спосіб - це наступний, який може працювати з будь-якою роздільною здатністю (0,01 для двох десяткових знаків або навіть інших кроків):

>>> import numpy as np
>>> value = 13.949999999999999
>>> resolution = 0.01
>>> newValue = int(np.round(value/resolution))*resolution
>>> print newValue
13.95

>>> resolution = 0.5
>>> newValue = int(np.round(value/resolution))*resolution
>>> print newValue
14.0

не працює для мене на python 3.4.3 та numpy 1.9.1? >>> імпортувати numpy як np >>> res = 0.01 >>> значення = 0.184 >>> np.round (значення / res) * res 0.17999999999999999
szeitlin

1
Шукаю документацію, я бачу, що проблема полягає в numpy.roundточності / точності. Тому потрібно визначити його як int перед множенням з роздільною здатністю. Я оновив код. Дякую тобі за це!
ібласі

Єдине, що потрібно - це перетворити numpy.float64результат np.round floatабо просто використовуватиround(value, 2) . Немає дійсного номера IEEE 754 між 13.949999999999999 (= 1395 / 100.) і 3.950000000000001 (= 1395 * .01). Чому ви вважаєте, що ваш метод найкращий? Початкове значення 13.949999999999999289 (= значення = кругла (значення, 2)) навіть точніше, ніж ваші 13.95000000000000178 (надруковано np.float96). До моєї відповіді тепер додано більше інформації про numpy, що ви, мабуть, спростували помилково. Спочатку мова не йшла про нудоту.
hynekcer

@hynekcer Я не думаю, що моя відповідь найкраща. Просто хотів додати приклад обмежуючого поплавця до n десяткових знаків, але найближчий до визначеної роздільної здатності. Я перевірив, як ви сказали, що замість цьогоint вас також можна використовувати floatприклад @szeitlin. Дякую за додатковий коментар (Вибачте, але я вас не порушив)
iblasi

Додавання цілком нової залежності для чисельної обробки (панди) - це "найкращий спосіб"?
Hejazzman

1

lambda x, n: int (x * 10 n + .5) / 10 n багато років працював на мене багатьма мовами.


-13

Я використовую метод методу нарізки струн. Це відносно швидко і просто.

Спочатку перетворіть поплавок на рядок, виберіть довжину, яку ви хотіли б.

float = str(float)[:5]

У наведеному вище рядку ми перетворили значення на рядок, а потім зберегли рядок лише до перших чотирьох цифр або символів (включно).

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


2
Не публікуйте однакових відповідей на кілька запитань.
vaultah

18
WOW ... tdh ... Будь ласка, ніколи не складайте жодного програмного забезпечення бухгалтерського обліку ... Що станеться, якщо число трапиться 113,94 ?? це призведе до 113,9 ... залишення 0,04 відсутнє .... Також на це вже є відповіді понад 5 років тому ....
Сердитий 84
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.