Числа с плавающей точкой представлены в компьютерах в виде двоичных дробей. Например, десятичная дробь 0.125
имеет значение 1/10 + 2/100 + 5/1000
, и таким же образом двоичная дробь 0.001
имеет значение 0/2 + 0/4 + 1/8
. Эти две дроби имеют одинаковые значения, единственное реальное отличие состоит в том, что первая записана в дробной записи с основанием 10, а вторая - с основанием 2.
К сожалению, большинство десятичных дробей не могут быть представлены точно как двоичные дроби. Следствием этого является то, что, как правило, вводимые десятичные числа с плавающей запятой аппроксимируются только двоичными числами с плавающей запятой, фактически сохраненными в машине.
Сначала проблему легче понять из базы 10. Рассмотрим дробь 1/3. Вы можете приблизить это как основную 10 фракцию: 0.3
или лучше, 0.33
или лучше, 0.333
и так далее. Независимо от того, сколько цифр вы хотите записать, результат никогда не будет ровно 1/3
, но будет все более приближенным к 1/3
.
Таким же образом, независимо от того, сколько цифр из 2-х оснований вы хотите использовать, десятичное значение 0,1
не может быть представлено в точности как дробь из 2-х оснований. В базе 2 1/10
- бесконечно повторяющаяся дробь 0.0001100110011001100110011...
Остановитесь на любом конечном количестве битов, и вы получите приближение. На большинстве современных машин числа с плавающей запятой аппроксимируются с использованием двоичной дроби, а числитель использует первые 53 бита, начиная с самого старшего бита, а знаменатель - как степень двух. В случае 1/10
двоичная дробь равна 3602879701896397/2 ** 55
, что близко, но не точно равно истинному значению 1/10
.
Многие пользователи не знают о приближении из-за способа отображения значений. Python печатает только десятичное приближение к истинному десятичному значению двоичного приближения, хранящегося на машине. На большинстве машин, если бы Python должен был печатать истинное десятичное значение двоичного приближения, хранящегося для 0.1, он должен был бы отображать
>>> 0.1 # 0.1000000000000000055511151231257827021181583404541015625
Это больше цифр, чем большинство людей считают полезным, поэтому Python сохраняет количество цифр управляемым, отображая округленное значение вместо
>>> 1 / 10 # 0.1
Просто помните, что хотя напечатанный результат выглядит как точное значение 1/10, фактическое сохраненное значение является ближайшей представимой двоичной дробью.
Интересно, что существует много разных десятичных чисел, которые имеют одну и ту же ближайшую приблизительную двоичную дробь. Например, числа 0.1
и 0.10000000000000001
и 0.1000000000000000055511151231...
все приблизительно равны 3602879701896397/2 ** 55
. Поскольку все эти десятичные значения имеют одинаковую аппроксимацию, любое из них может отображаться при сохранении инварианта eval(repr(x)) == x
.
Исторически Python и встроенная функция repr()
выбирали функцию с 17 значащими цифрами, 0.10000000000000001
. Начиная с Python 3.1 в большинстве систем теперь может выбирать самый короткий из них и просто отображать 0.1.
Обратите внимание, что это по своей природе двоичное число с плавающей точкой: это не ошибка в Python и не ошибка в вашем коде. Вы увидите то же самое на всех языках, которые поддерживают арифметику с плавающей запятой.
Для более приятного вывода вы можете использовать форматирование строки для получения ограниченного числа значащих цифр:
>>> import math >>> format(math.pi, '.12g') # '3.14159265359' >>> format(math.pi, '.2f') # '3.14' >>> repr(math.pi) # '3.141592653589793'
Важно понимать, что в действительности это иллюзия: вы просто округляете отображение истинного значения.
Одна иллюзия может породить другую. Например, поскольку 0,1 не является точно 1/10, суммирование трех значений 0,1 может также не дать точно 0,3:
>>> 0.1 + 0.1 + 0.1 == 0.3 # False
Кроме того, поскольку 0,1 не может приблизиться к точному значению 1/10, а 0,3 не может приблизиться к точному значению 3/10, предварительное округление функцией round()
может не помочь:
>>> round(0.1, 1) + round(0.1, 1) + round(0.1, 1) == round(0.3, 1) # False
Двоичная арифметика с плавающей точкой содержит много сюрпризов, подобных этому. Проблема с 0.1
подробно объясняется в разделе "Ошибка представления". Смотрите также "Опасности с плавающей точкой" для более полного описания других распространенных сюрпризов.
Как говорится, "простых ответов нет". Тем не менее, не следует чрезмерно опасаться чисел с плавающей запятой! Ошибки в операциях с плавающей запятой Python наследуются от аппаратного обеспечения чисел с плавающей запятой, и на большинстве машин они имеют порядок не более одной части в 2 ** 53
на операцию. Это более чем достаточно для большинства задач, но вам нужно помнить, что это не десятичная арифметика и что каждая операция с плавающей запятой может подвергаться новой ошибке округления.
Несмотря на то, что патологические случаи существуют, для наиболее случайного использования арифметики с плавающей запятой вы увидите ожидаемый результат в конце, если просто округлите отображение окончательных результатов до ожидаемого количества десятичных цифр. str()
обычно достаточно, и для более точного управления смотрите спецификаторы формата метода str.format()
.
Для случаев использования, которые требуют точного десятичного представления, попробуйте использовать модуль decimal
, который реализует десятичную арифметику, подходящую для приложений бухгалтерского учета и высокоточных приложений.
Другая форма точной арифметики поддерживается модулем fractions
, который реализует арифметику, основанную на рациональных числах, поэтому числа, такие как 1/3
могут быть представлены точно.
Если вы большой пользователь операций с плавающей запятой, вам следует взглянуть на пакет Numeric Python
и многие другие пакеты для математических и статистических операций, предоставляемых проектом SciPy
.
Python предоставляет инструменты, которые могут помочь в тех редких случаях, когда вы действительно хотите узнать точное значение числа с плавающей точкой. Метод float.as_integer_ratio()
выражает значение типа float
в виде дроби:
>>> x = 3.14159 >>> x.as_integer_ratio() # (3537115888337719, 1125899906842624)
Поскольку отношение является точным, оно может быть использовано для воссоздания исходного значения без потерь:
>>> x == 3537115888337719 / 1125899906842624 # True
Метод float.hex()
выражает число с плавающей запятой в шестнадцатеричном формате (основание 16), снова давая точное значение, сохраненное компьютером:
>>> x.hex() # '0x1.921f9f01b866ep+1'
Это точное шестнадцатеричное представление может быть использовано для точного восстановления значения с плавающей точкой:
>>> x == float.fromhex('0x1.921f9f01b866ep+1') True
Поскольку представление является точным, оно полезно для надежного переноса значений между различными версиями Python и обмена данными с другими языками, поддерживающими тот же формат, например Java.
Другим полезным инструментом является функция math.fsum()
, которая помогает уменьшить потерю точности во время суммирования. Она отслеживает "потерянные цифры", когда значения добавляются в промежуточный итог. Это может повлиять на общую точность, так что ошибки не накапливаются до такой степени, что бы влиять на итоговую сумму:
>>> import math >>> sum([0.1] * 10) == 1.0 False >>> math.fsum([0.1] * 10) == 1.0 True
В этом разделе подробно объясняется пример 0.1
и показано, как вы можете выполнить точный анализ подобных случаев самостоятельно. Предполагается базовое знакомство с двоичным представлением чисел с плавающей точкой.
Ошибка представления относится к тому факту, что фактически большинство десятичных дробей не могут быть представлены точно как двоичные дроби (основание 2) . Это главная причина, почему Python или Perl, C, C++, Java, Fortran и многие другие языки часто не будут отображать точное десятичное число, которое ожидаете.
Это почему? 1/10
не совсем представимо в виде двоичной дроби. Почти все машины сегодня используют арифметику IEEE-754 с плавающей точкой, и почти все платформы отображают плавающие значения Python в IEEE-754 с "двойной точностью". При вводе, компьютер стремится преобразовать 0,1 в ближайшую дробь по форме J / 2 ** N
, где J
- целое число, содержащее ровно 53 бита.
Перепишем 1 / 10 ~= J / (2**N)
как J ~= 2**N / 10
и напоминая, что J
имеет ровно 53 бита, это >= 2**52
, но < 2**53
, наилучшее значение для N
равно 56:
>>> 2**52 <= 2**56 // 10 < 2**53 # True
То есть 56 - единственное значение для N
, которое оставляет J
точно с 53 битами. Наилучшее возможное значение для J
тогда будет округлено:
>>> q, r = divmod(2**56, 10) >>> r # 6
Поскольку остаток больше половины от 10, наилучшее приближение получается округлением вверх:
>>> q+1 # 7205759403792794
Поэтому наилучшее возможное приближение к 1/10
при двойной точности 754: 7205759403792794 / 2 ** 56
. Деление числителя и знаменателя на два уменьшает дробь до: 3602879701896397 / 2 ** 55
Обратите внимание, поскольку мы округлили вверх, то значение на самом деле немного больше, чем 1/10
. Если бы мы не округлили, то значение был бы немного меньше 1/10
. Но ни в коем случае это не может быть ровно 1/10
!
Таким образом, компьютер никогда не "видит" 1/10: то, что он видит, это точная дробь, указанная выше, наилучшее двоичное приближение 754, которое он может получить:
>>> 0.1 * 2 ** 55 # 3602879701896397.0
Если мы умножим эту дробь на 10 ** 55
, мы увидим значение до 55 десятичных цифр:
>>> 3602879701896397 * 10 ** 55 // 2 ** 55 # 1000000000000000055511151231257827021181583404541015625
Это означает, что точное число, хранящееся в компьютере, равно десятичному значению полученному выше. Вместо отображения полного десятичного значения многие языки, включая более старые версии Python округляют результат до 17 значащих цифр:
>>> format(0.1, '.17f') '0.10000000000000001'
Модули fractions
and decimal
упрощают эти вычисления:
>>> from decimal import Decimal >>> from fractions import Fraction >>> Fraction.from_float(0.1) # Fraction(3602879701896397, 36028797018963968) >>> (0.1).as_integer_ratio() # (3602879701896397, 36028797018963968) >>> Decimal.from_float(0.1) # Decimal('0.1000000000000000055511151231257827021181583404541015625') >>> format(Decimal.from_float(0.1), '.17') # '0.10000000000000001'