Почему нужно меньше читать спам-блоги и как ровно округлить в Python

Python

Сегодня еще один новичок в Python был введен в заблуждение ненужными статьями в китайских технических блогах.

Вопрос начинающего:

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

Если вы выполните поиск в Google или Baidu, вы найдете множество статей из CSDN, Baijiahao, Toutiaohao или Jianshu, но они не говорят ничего, кроме следующего:

Бредовые статьи, в которых даже нет примеров

Как показано на картинке ниже, мне лень жаловаться.

Используйте функцию округления

Примеры они приводят:

>>> round(1.234, 2)
1.23

Такого рода статьи он только продемонстрировал四舍, но без демо五入. Итак, если вы немного измените код, вы обнаружите, что есть проблема:

>>> round(11.245, 2)
11.24

Увеличить, затем уменьшить

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

Тем не менее, такого рода статьи также полны лазеек.Пока вы попробуете еще несколько чисел, вы обнаружите проблему.В Python 2 и Python 3 эффект отличается. Давайте посмотрим на эффект запуска следующего Python 2:

В Python 2 используйте напрямуюround,1.125С точностью до двух знаков после запятой, как1.131.115С точностью до двух знаков после запятой1.11.

Давайте посмотрим на следующий эффект в Python 3:

Под Python 3,1.125После точности до двух знаков после запятой1.12.

Его пример увеличения, а затем уменьшения масштаба в Python 3 не всегда корректен.

Делать вид

Есть еще своего рода вынужденный товар.Статья похожа на увеличение и уменьшение масштаба, но он все равно знаетdecimalэтот модуль.

Но как он это использует, давайте посмотрим на него

具体原因不详? ? ? ?

不推荐使用这个方法? ? ?

Такому человеку нужно сначала установить принудительно, говоря, что он знает, что есть такая библиотека, но обнаруживает, что при ее использовании возникает проблема, и он не знает причины, поэтому не всем рекомендуется использовать Это.

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

Что не так с круглым?

Поругавшись, поговорим о, в Python 3,roundЧто именно не так с этой встроенной функцией.

Некоторые люди сказали в Интернете, потому что в компьютере десятичные дроби, например, неточны1.115В компьютере это на самом деле1.1149999999999999911182, поэтому, когда вы точны до двух знаков после запятой для этой десятичной точки, на самом деле третий знак после запятой4, так округлил, значит результат1.11.

Это утверждение верно наполовину.

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

Но если мы вставим Python0.125с точностью до двух знаков после запятой, тогда становится0.12:

>>> round(0.125, 2)
0.12

почему ты здесь四舍уже?

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

>>> round(0.375, 2)
0.38

почему снова здесь五入уже?

Поскольку в Python 3roundТочность десятичных знаков использует四舍六入五成双Путь.

Если вы написали отчет об эксперименте по физике в университете, вы должны помнить, что преподаватель сказал, что если вы используете округление напрямую, конечный результат может быть высоким. Так что нужно использовать奇进偶舍способ обработки.

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

  1. еслиdЕсли он меньше 5, он будет сразу отброшен.
  2. еслиdБольше 5, прямой перенос
  3. еслиdравно 5:
    1. dЗа ним нет данных, а c равно偶数, тогда не переносите, держите c
    2. dЗа ним нет данных, а c равно奇数, затем перенесите, c становится (c + 1)
    3. еслиdЕсть также ненулевые цифры позади, например, фактическое десятичное числоa.bcdef, в это время нужно нести, c становится (c + 1)

Что касается нечетных и четных домов, заинтересованные студенты могут найти эти две записи в Википедии:数值修约и奇进偶舍.

так,roundЕсли результат не такой, как вы ожидали, то вам нужно рассмотреть две причины:

  1. Можно ли точно сохранить эту десятичную дробь в компьютере? Если это невозможно, то, вероятно, он неправильно округляется, например.1.115, его третье десятичное место на самом деле4, конечно, будет отброшено.
  2. Если ваше десятичное число может быть точно представлено на компьютере, тоroundИспользуемый механизм переноски奇进偶舍, так что это зависит от того, какой бит вы хотите сохранить, будь то нечетный или четный, и есть ли данные после его следующего бита.

Как правильно сделать округление

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

Как правильно использовать десятичный модуль?

Читайте официальную документацию, не читайте китайские спам-блоги! ! !

Читайте официальную документацию, не читайте китайские спам-блоги! ! !

Читайте официальную документацию, не читайте китайские спам-блоги! ! !

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

Давайте взглянем:docs.Python.org/this-talent/3/неделя…

Официальный документ дает конкретный метод написания:

>>>Decimal('1.41421356').quantize(Decimal('1.000'))
Decimal('1.414')

Итак, давайте проверим это,0.125и0.375Какие два десятичных знака:

>>> from decimal import Decimal
>>> Decimal('0.125').quantize(Decimal('0.00'))
Decimal('0.12')
>>> Decimal('0.375').quantize(Decimal('0.00'))
Decimal('0.38')

как результат иroundТакой же? Давайте посмотрим на документациюquantizeВ прототипе функции и документации указано:

Здесь упоминается, что вы можете указатьroundingпараметр для определения метода переноса. Если не указаноroundingпараметр, то метод переноса, предоставляемый контекстом, используется по умолчанию.

Теперь давайте посмотрим, что такое метод переноса в контексте по умолчанию:

>>> from decimal import getcontext
>>> getcontext().rounding
'ROUND_HALF_EVEN'

Как показано ниже:

ROUND_HALF_EVENфактически奇进偶舍! Если мы хотим указать истинное округление, то нам нужно указать вquantizeУказанный метод переносаROUND_HALF_UP:

>>> from decimal import Decimal, ROUND_HALF_UP
>>> Decimal('0.375').quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
Decimal('0.38')
>>> Decimal('0.125').quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
Decimal('0.13')

Кажется, теперь все работает нормально.

Так кто-нибудь спросит дальше, а что, если параметр Decimal получает не строку, а число с плавающей запятой?

Давайте поэкспериментируем:


>>> Decimal(0.375).quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
Decimal('0.38')
>>> Decimal(0.125).quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
Decimal('0.13')

Означает ли это, что в первом параметре Decimal можно напрямую передавать числа с плавающей запятой?

Давайте изменим число для проверки:

>>> Decimal(11.245).quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
Decimal('11.24')
>>> Decimal('11.245').quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
Decimal('11.25')

почему числа с плавающей запятой11.245и строка'11.245', После передачи результат другой?

Продолжаем искать ответы в документации.

В официальной документации четко указано, что если параметр, который вы передаете, является числом с плавающей запятой, и значение с плавающей запятой не может быть точно сохранено в компьютере, оно сначала будет преобразовано в неточное двоичное значение, а затем это неточное двоичное значение. значения преобразуются в等效的十进制值.

Для десятичного числа, которое не может быть точно представлено, когда вы передаете его, до того, как Python получит число, число было преобразовано в неточное число. Итак, хотя вы передаете параметр как11.245, но то, что получает Python, на самом деле11.244999999999....

Но если вы передадите строку'11.245', то когда Python его получает, он знает, что это11.245, не будет заранее конвертирован в неточное значение, поэтому рекомендуется указатьDecimalПервый параметр строкового типа числа с плавающей запятой вместо прямой записи числа с плавающей запятой.

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

from decimal import Decimal, ROUND_HALF_UP

origin_num = Decimal('11.245')
answer_num = origin_num.quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
print(answer_num)

Эффект операции показан на следующем рисунке:

В частности, обратите внимание, что после выполнения точных вычислений числа с плавающей запятой больше не должны использоваться отдельно, а должны использоваться всегда.Decimal('浮点数'). В противном случае при присвоении значения точность теряется.Рекомендуется использовать Decimal, например, везде:

a = Decimal('0.1')
b = Decimal('0.2')
c = a + b
print(c)

Наконец, если некоторые студенты хотят знать, почему 0,125 и 0,375 могут быть сохранены точно, но 1,115 и 11,245 не могут быть точно сохранены, пожалуйста, оставьте сообщение под этой статьей.Если есть много студентов, которые хотят знать, я напишу статью, чтобы объяснять.

В конце концов, если вы не понимаете по-английски, а китайские документы слишком скучны, то используйте Nuggets. Качественные китайские статьи Наггетс в 10 000 раз выше, чем у CSDN.

Если статья была вам полезна, рассмотрите возможность подписаться на мою общедоступную учетную запись WeChat: Unwen Code (ID: istkingname)