Ограничение числа с плавающей запятой до двух десятичных знаков


1692

Я хочу aокругляться до 13,95 .

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

roundФункция не работает так , как я ожидал.



6
Хм ... Вы пытаетесь представлять валюту? Если это так, вы не должны использовать поплавки за доллары. Возможно, вы могли бы использовать поплавки для копеек, или какова бы ни была наименьшая общая единица валюты, которую вы пытаетесь смоделировать, но лучшая практика - использовать десятичное представление, как предложил ХУАХАГУА в своем ответе.
SingleNegationElimination

63
Важно не представлять валюту в обращении. Поплавки не точные. Но суммы пенни или цента являются целыми числами. Поэтому целые числа являются правильным способом представления валюты.
Давуд Тагави-Неджад

2
@ DavoudTaghawi-Nejad или более точно ... Десятичный тип
Базовый

17
Я прихожу сюда, вероятно, слишком поздно, но я хотел спросить, решили ли разработчики Python эту проблему? Потому что, когда я делаю раунд (13,949999999999999, 2), я просто получаю 13,95. Я пробовал это в Python 2.7.6, а также 3.4. Оно работает. Не уверен, что 2.7 даже был там в 2009 году. Может, это Python 2.5?
bad_keypoints

Ответы:


1691

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

С представлением с плавающей точкой ваша округленная версия - это то же число. Поскольку компьютеры являются двоичными, они хранят числа с плавающей запятой в виде целого числа, а затем делят его на степень двойки, поэтому 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 Существует принципиальная разница между сохраненным значением и тем, как вы отображаете это значение. Форматирование вывода должно позволять вам добавлять отступы по мере необходимости, а также добавлять запятые и т. Д.
Basic

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: я согласен, что они равны, но это ограничивает число с плавающей запятой до двух десятичных знаков :)
Jossef Harush

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
danger89

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 вам просто нужно добавить скобки print (...). И внутри них все, что я написал, правильно.
Алексей Антоненко

«Я имею в виду, что выведите« {0: .2f} {0: .2f} ». Формат (a, b) приведет к ошибке в выводе». Ах. Ну, это совсем другое утверждение! Может быть, вы должны отредактировать свой ответ? (Что означает «повысить ошибку» в текущем ответе, например? Можете ли вы привести пример случая, когда второе утверждение вызывает исключение, а первое нет?)
Марк Дикинсон

3
Вы будете после print ("{0: .2f} {1: .2f}". Format (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 (нажмите «Запустить сессию» в верхней части правого раздела).
Lifebalance

3
Если вы используете этот подход, вы должны добавить 0,5 для более точного представления. int (a * 100 + 0,5) / 100,0; Использование math.ceil является еще одним вариантом.
arhuaco

3
@ShashankSawant: Ну, во-первых, ответ в том виде, в котором он представлен, не округляется, он усекается. Предложение добавить половину в конце будет округлено, но тогда нет смысла делать это по сравнению с простым использованием roundфункции в первую очередь. Во-вторых, поскольку в этом решении все еще используется число с плавающей запятой, исходная проблема ОП сохраняется даже для «исправленной» версии этого «решения».
Джон Y

3
-1, это просто ненужная переопределение roundфункции (которая использовалась в вопросе).
междурядный

4
@interjay, который необходим, если round()не работает, как упомянуто OP.
Питикос,

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; парсинг float и мнимых литералов в коде Python; и десятичное преобразование в число с плавающей точкой.

В связи с этим repr () числа x с плавающей запятой теперь возвращает результат, основанный на самой короткой десятичной строке, которая гарантированно округляется до x при правильном округлении (с режимом округления от половины до четного). Ранее он давал строку, основанную на округлении x до 17 десятичных цифр.

Связанная проблема


Дополнительная информация: форматирование floatдо Python 2.7 было похоже на текущий numpy.float64. Оба типа используют одинаковую 64-битную IEEE 754 двойную точность с 52-битной мантиссой. Большая разница состоит в том, что np.float64.__repr__часто форматируется с чрезмерным десятичным числом, чтобы ни один бит не мог быть потерян, но между 13.949999999999999 и 13.950000000000001 не существует действительного числа IEEE 754. Результат не хороший, и преобразование repr(float(number_as_string))не обратимо с NumPy. С другой стороны: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 ). Вы можете согласиться?
Гинексер

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) предпочтительнее второй вариант .

Для получения дополнительной информации о втором варианте я предлагаю эту ссылку на форматирование строки из документации 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 output». Но если вы сравните «a == output», результатом будет «False», потому что шаг форматирования округляет плавающее значение «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

Используйте комбинацию десятичного объекта и метода 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

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

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'


5

Как насчет лямбда-функции, как это:

arred = lambda x,n : x*(10**n)//1/(10**n)

Таким образом, вы можете просто сделать:

arred(3.141591657,2)

и получить

3.14

4

Это просто как 1,2,3:

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

    д = Десятичный (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 >>> value = 0.184 >>> np.round (value / res) * res 0.17999999999999999
szeitlin

1
В поисках документации я вижу, что проблема заключается в numpy.roundточности / точности. Таким образом, он требует определить его как int перед умножением с разрешением. Я обновил код. Спасибо за это!
iblasi

Единственное, что необходимо - это преобразовать 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, по которой вы, вероятно, ошибочно проголосовали. Первоначально это не было о numpy.
Hynekcer

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

Добавление совершенно новой зависимости для числовой обработки (панды) - это «лучший способ»?
Hejazzman


-13

Я использую метод нарезки строк. Это относительно быстро и просто.

Во-первых, преобразуйте число с плавающей точкой в ​​строку и выберите желаемую длину.

float = str(float)[:5]

В приведенной выше строке мы преобразовали значение в строку, а затем сохранили строку только до первых четырех цифр или символов (включительно).

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


2
Пожалуйста, не публикуйте одинаковые ответы на несколько вопросов.
пр.

18
ВАУ ... тдх ... Пожалуйста, никогда не делайте никаких бухгалтерских программ ... Что произойдет, если число окажется 113,94 ?? в результате получится 113,9 ... пропущено 0,04 .... Также на это уже есть ответы более 5 лет назад ...
Злой 84
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.