Я нашел способы обрезать/усекать их (66.666666666 → 66.6666), но не раунд (66.666666666 → 66.6667).
Ответ 2
Я чувствую себя вынужденным предоставить контрапункт ответа Ашвини Чаудхари. Несмотря на видимость, форма с двумя аргументами функции round
не обходит плавающий Python до заданного количества десятичных знаков, и часто это не решение, которое вы хотите, даже если вы так думаете. Позвольте мне объяснить...
Возможность округлять (Python) float до некоторого количества десятичных знаков - это то, что часто запрашивается, но оказывается редко, что действительно необходимо. Простой ответ round(x, number_of_places)
- это что-то вроде привлекательной неприятности: похоже, что он делает то, что вы хотите, но благодаря тому, что поплавки Python хранятся внутри двоично, он делает что-то более тонкое. Рассмотрим следующий пример:
>>> round(52.15, 1)
52.1
С наивным пониманием того, что делает round
, это выглядит неправильно: конечно, оно должно округляться до 52.2
, а не вниз до 52.1
? Чтобы понять, почему на такое поведение нельзя полагаться, вы должны понимать, что, хотя это выглядит как простая операция с десятичной точностью, это далеко не просто.
Итак, вот что на самом деле происходит в приведенном выше примере. (глубокое дыхание) Мы показываем десятичное представление ближайшего двоичного числа с плавающей запятой до ближайшего десятичного числа n
-digits-after-to-point до двоичного приближения с плавающей запятой числового литерала, записанного в десятичном значении. Таким образом, чтобы получить от исходного числового литерала до отображаемого вывода, базовый механизм сделал четыре отдельных преобразования между двоичным и десятичным форматами, по два в каждом направлении. Разрушая его (и с обычными заявлениями о предполагаемом формате бинарного формата IEEE 754, округлении до четного округления и правилах IEEE 754):
-
Сначала числовой литерал 52.15
обрабатывается и преобразуется в плавающий Python. Фактическое сохраненное количество: 7339460017730355 * 2**-47
или 52.14999999999999857891452847979962825775146484375
.
-
Внутренне как первый шаг операции round
, Python вычисляет ближайшую десятичную строку с десятичной цифрой после запятой к сохраненному номеру. Поскольку это сохраненное число является касанием под исходным значением 52.15
, мы заканчиваем округление и получаем строку 52.1
. Это объясняет, почему мы получаем 52.1
как окончательный вывод вместо 52.2
.
-
Затем на втором этапе операции round
Python превращает эту строку обратно в float, получая ближайший двоичный номер с плавающей запятой до 52.1
, который теперь 7332423143312589 * 2**-47
или 52.10000000000000142108547152020037174224853515625
.
-
Наконец, как часть цикла чтения-eval-печати Python (REPL), отображается значение с плавающей запятой (в десятичной форме). Это включает преобразование двоичного значения обратно в десятичную строку, получив 52.1
в качестве конечного результата.
В Python 2.7 и более поздних версиях мы имеем приятную ситуацию, когда два преобразования на шагах 3 и 4 отменяют друг друга. Это связано с выбором Python реализации repr
, которая дает кратчайшее десятичное значение, гарантированное правильное округление до фактического поплавка. Одним из следствий этого выбора является то, что если вы начинаете с любого (не слишком большого, не слишком малого) десятичного литерала с 15 или менее значащими цифрами, тогда будет отображаться соответствующий поплавок с указанием тех же самых цифр:
>>> x = 15.34509809234
>>> x
15.34509809234
К сожалению, это приводит к иллюзии, что Python хранит значения в десятичной форме. Однако не в Python 2.6! Здесь исходный пример выполнен в Python 2.6:
>>> round(52.15, 1)
52.200000000000003
Мы не только обходим в обратном направлении, получаем 52.2
вместо 52.1
, но отображаемое значение даже не печатается как 52.2
! Такое поведение вызвало многочисленные сообщения для трекера Python по строкам "раунд сломан!". Но это не round
, что сломано, это ожидания пользователя. (Хорошо, хорошо, round
немного сломан в Python 2.6, поскольку он не использует правильное округление.)
Короткий вариант: если вы используете раунд с двумя аргументами, и вы ожидаете предсказуемого поведения от двоичного приближения к десятичному раунду двоичного приближения к десятичному полуприцепу, вы просите о неприятности.
Достаточно, чтобы аргумент "аргумент с двумя аргументами был плохим". Что вы должны использовать вместо этого? Есть несколько возможностей, в зависимости от того, что вы пытаетесь сделать.
-
Если вы округляете для целей показа, то вам совсем не нужен результат поплавка; вам нужна строка. В этом случае ответ заключается в использовании форматирования строк:
>>> format(66.66666666666, '.4f')
'66.6667'
>>> format(1.29578293, '.6f')
'1.295783'
Даже тогда нужно знать внутреннее двоичное представление, чтобы не удивляться поведению кажущихся десятичных полуприцепов.
>>> format(52.15, '.1f')
'52.1'
-
Если вы работаете в контексте, где важно, в каком направлении округлены десятичные числа, округленные (например, в некоторых финансовых контекстах), вы можете представить свои числа с помощью типа Decimal
. Выполнение десятичного раунда по типу Decimal
имеет гораздо больший смысл, чем двоичный тип (одинаково, округление до фиксированного числа двоичных мест имеет смысл в двоичном типе). Кроме того, модуль Decimal
дает вам лучший контроль над режимом округления. В Python 3, round
выполняет работу напрямую. В Python 2 вам понадобится метод quantize
.
>>> Decimal('66.66666666666').quantize(Decimal('1e-4'))
Decimal('66.6667')
>>> Decimal('1.29578293').quantize(Decimal('1e-6'))
Decimal('1.295783')
-
В редких случаях версия с двумя аргументами round
действительно является тем, что вы хотите: возможно, вы загружаете биннинг в ячейки размером 0.01
, и вам не особо важно, каким образом граничные случаи идти. Однако эти случаи редки, и трудно обосновать существование двух аргументной версии встроенного round
, основанного только на тех случаях.