Округление чисел в 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
Автор