Что найти?

Округление чисел в Python

/
/

В нашей повседневной жизни мы используем десятичную систему, основанную на числе 10. Компьютер использует двоичную систему с основанием 2, а внутри он хранит и обрабатывает значения как последовательность единиц и нулей. Ценности, с которыми мы работаем, должны постоянно трансформироваться между двумя представлениями. Как объяснено в документации Python:

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

Пример 1: неточности с числами с плавающей запятой.

>>> s = 0.3 + 0.3 + 0.3
>>> s
0.8999999999999999

Как вы можете видеть, результат неточный, так как он должен иметь значение 0.9.

В примере 2 показан аналогичный случай форматирования числа с плавающей запятой для 17 десятичных разрядов.

Пример 2: форматирование числа с плавающей запятой

>>> format(0.1, '.17f')
'0.10000000000000001'

Как вы, возможно, узнали из приведенных выше примеров, работа с числами с плавающей запятой немного сложна и требует дополнительных мер для достижения правильного результата и минимизации вычислительных ошибок. Округление значения может решить по крайней мере некоторые проблемы. Одна из возможностей – встроенная функция round():

Пример 3: расчет с округленными значениями

>>> s = 0.3 + 0.3 + 0.3
>>> s
0.8999999999999999
>>> s == 0.9
False
>>> round(0.9, 1) == 0.9
True

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

Чтобы сохранить такие значения, в игру вступают два модуля Python: десятичная и дробная. Но сначала давайте подробнее рассмотрим термин «округление».

Что такое округление?

В двух словах процесс округления означает:

… заменяя [значение] другим числом, которое примерно равно исходному, но имеет более короткое, простое или более явное представление.

По сути, он увеличивает неточность точно рассчитанного значения, сокращая его. В большинстве случаев это делается путем удаления цифр после десятичной точки, например от 3,73 до 3,7, от 16,67 до 16,7 или от 999,95 до 1000.

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

Цифры от 0 до 4 ведут к округлению в меньшую сторону, а числа от 5 до 9 – к округлению в большую сторону. В таблице ниже показаны варианты использования.

| original value | rounded to   | result |
|----------------|--------------|--------|
| 226            | the ten      | 230    |
| 226            | the hundred  | 200    |
| 274            | the hundred  | 300    |
| 946            | the thousand | 1,000  |
| 1,024          | the thousand | 1,000  |
| 10h45m50s      | the minute   | 10h45m |

Методы

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

Например, округление от нуля до половины применяется Европейской комиссией по экономическим и финансовым вопросам при конвертации валют в евро. Некоторые страны, такие как Швеция, Нидерланды, Новая Зеландия и Южная Африка, следуют правилу под названием «округление денежных средств», «округление пенни».

[Округление наличных] происходит, когда минимальная расчетная единица меньше наименьшего физического достоинства валюты. Сумма, подлежащая выплате за транзакцию с наличными, округляется до ближайшего кратного значения минимальной доступной денежной единицы, тогда как транзакции, оплаченные другими способами, не округляются.

В Южной Африке с 2002 года округление наличных денег производится до ближайших 5 центов. Обычно такое округление не применяется к электронным безналичным платежам.

Напротив, округление от половины до четного является стратегией по умолчанию для Python, Numpy и Pandas и используется встроенной функцией round(), о которой уже упоминалось ранее. Он принадлежит к категории методов округления до ближайшего и также известен как конвергентное округление, округление статистики, по голландскому языку, по Гауссу, по нечетному и четному. Этот метод определен в IEEE 754 и работает таким образом, что «если дробная часть x равна 0,5, то y является ближайшим к x четным целым числом».

Предполагается, что «вероятности связи в наборе данных при округлении в меньшую или большую сторону равны», что обычно и имеет место на практике. Хотя эта стратегия не полностью совершенна, она дает заметные результаты.

В таблице ниже приведены практические примеры округления в Python для этого метода:

| original value | rounded to |
|----------------|------------|
| 23.3           | 23         |
| 23.5           | 24         |
| 24.0           | 24         |
| 24.5           | 24         |
| 24.8           | 25         |
| 25.5           | 26         |

Функции

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

Пример 4: округление с указанным количеством цифр

>>> round(15.45625, 1)
15.5
>>> round(15.45625, 2)
15.46
>>> round(15.45625, 4)
15.4563

Если вы вызываете эту функцию без второго параметра, значение округляется до полного целого числа.

Пример 5: без указанного количества цифр

>>> round(0.85)
1
>>> round(0.25)
0
>>> round(1.5)
2

Округленные значения в Python подходят, если вам не требуются абсолютно точные результаты. Имейте в виду, что сравнение округленных значений также может быть кошмаром. Это станет более очевидным в следующем примере – сравнении округленных значений на основе предварительного и последующего округления.

Первый расчет в примере 6 содержит предварительно округленные значения и описывает округление перед сложением значений. Второй расчет содержит итоговую сумму после округления, что означает округление после суммирования. Вы заметите, что результат сравнения другой.

Пример 6:

>>> round(0.3, 10) + round(0.3, 10) + round(0.3, 10) == round(0.9, 10)
False
>>> round(0.3 + 0.3 + 0.3, 10) == round(0.9, 10)
True

Модули для вычислений с плавающей запятой

Есть четыре популярных модуля, которые помогут вам правильно работать с числами с плавающей запятой. Сюда входят модули math, Numpy, decimal и fraction.

Математический модуль (math) сосредоточен на математических константах, операциях с плавающей запятой и тригонометрических методах. Модуль Numpy описывает себя как «фундаментальный пакет для научных вычислений» и известен своим разнообразием методов работы с массивами. Модуль decimal охватывает десятичную арифметику с фиксированной и плавающей запятой, а модуль fraction имеет дело, в частности, с рациональными числами.

Во-первых, мы должны попытаться улучшить вычисления из примера 1. Как показано в примере 7, после импорта математического модуля мы можем получить доступ к методу fsum(), который принимает список чисел с плавающей запятой. Для первого вычисления нет разницы между встроенным методом sum() и методом fsum() из математического модуля, но для второго – он возвращает правильный результат, которого мы ожидали. Точность зависит от базового алгоритма IEEE 754.

Пример 7: вычисления с плавающей запятой с помощью модуля math

>>> import math
>>> sum([0.1, 0.1, 0.1])
0.30000000000000004
>>> math.fsum([0.1, 0.1, 0.1])
0.30000000000000004
>>> sum([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1])
0.9999999999999999
>>> math.fsum([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1])
1.0

Во-вторых, давайте посмотрим на модуль Numpy. Он поставляется с методом around(), который округляет значения, предоставленные в виде массива. Он обрабатывает отдельные значения так же, как метод round() по умолчанию.

Для сравнения значений Numpy предлагает метод equal(). Как и around(), он принимает отдельные значения, а также списки значений (так называемые векторы) для обработки. В примере 8 показано сравнение отдельных значений, а также округленных. Наблюдаемое поведение очень похоже на ранее показанные методы.

Пример 8: сравнение значений с использованием метода equal из модуля Numpy

>>> import numpy
>>> print (numpy.equal(0.3, 0.3))
True
>>> print (numpy.equal(0.3 + 0.3 + 0.3 , 0.9))
False
>>> print (numpy.equal(round(0.3 + 0.3 + 0.3) , round(0.9)))
True

Вариант третий – десятичный модуль (decimal). Он предлагает точное десятичное представление и сохраняет значащие цифры. По умолчанию точность составляет 28 цифр, и вы можете изменить это значение на число, которое будет настолько большим, насколько это необходимо для вашей проблемы. В примере 9 показано, как использовать точность до 8 цифр.

Пример 9: создание десятичных чисел с помощью модуля decimal

>>> import decimal
>>> decimal.getcontext().prec = 8
>>> a = decimal.Decimal(1)
>>> b = decimal.Decimal(7)
>>> a / b
Decimal('0.14285714')

Теперь сравнение значений с плавающей запятой стало намного проще и привело к желаемому результату.

Пример 10: сравнение с использованием модуля Decimal

>>> import decimal
>>> decimal.getcontext().prec = 1
>>> a = decimal.Decimal(0.3)
>>> b = decimal.Decimal(0.3)
>>> c = decimal.Decimal(0.3)
>>> a + b + c
Decimal('0.9')
>>> a + b + c == decimal.Decimal('0.9')
True

Модуль decimal также имеет метод округления значений – quantize(). Стратегия округления по умолчанию – округление от половины до четного, и при необходимости ее также можно изменить на другой метод. В примере 11 показано использование метода quantize(). Обратите внимание, что количество цифр указывается с использованием десятичного значения в качестве параметра.

Пример 11: округление значения с помощью quantize()

>>> d = decimal.Decimal(4.6187)
>>> d.quantize(decimal.Decimal("1.00"))
Decimal('4.62')

И последнее, но не менее важное: мы рассмотрим модуль fractions. Этот модуль позволяет обрабатывать значения с плавающей запятой как дроби, например 0,3 как 3/10. Это упрощает сравнение значений с плавающей запятой и полностью исключает округление значений. В примере 12 показано, как использовать модуль fractions.

Пример 12: хранение и сравнение значений с плавающей запятой

>>> import fractions
>>> fractions.Fraction(4, 10)
Fraction(2, 5)
>>> fractions.Fraction(6, 18)
Fraction(1, 3)
>>> fractions.Fraction(125)
Fraction(125, 1)
>>> a = fractions.Fraction(6, 18)
>>> b = fractions.Fraction(1, 3)
>>> a == b
True

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

Пример 13: работа с десятичными знаками и дробями

>>> import fractions
>>> import decimal
>>> a = fractions.Fraction(1,10)
>>> b = fractions.Fraction(decimal.Decimal(0.1))
>>> a,b
(Fraction(1, 10), Fraction(3602879701896397, 36028797018963968))
>>> a == b
False

Оставить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

This div height required for enabling the sticky sidebar