В этой статье MarsDevs расскажет вам, что такое числа с плавающей запятой, как они используются в арифметике в Python, а также их проблемы и ограничения.
Введение -
В компьютерном оборудовании числа с плавающей запятой представляются как дроби с основанием 2 (двоичные).
Пример —
Десятичная дробь 0,125 имеет значение 1/10 + 2/100 + 5/1000, а двоичная дробь 0,001 имеет значение 0/2 + 0/4 + 1/8. У них схожие значения, единственная реальная разница заключается в их основаниях.
Но не все дроби могут быть представлены именно как двоичные дроби. Тогда вы действительно можете приблизиться только к двоичным числам с плавающей запятой, хранящимся в компьютере.
Пример —
Давайте посмотрим на этот пример в системе счисления 10. Если бы вы хотели представить 1/9, приблизительное значение было бы 0,1111111111111… в системе счисления 10. Значение 1/9 имеет бесконечное количество единиц после запятой.
Вы можете взять 0,1 или лучше 0,11 или лучше 0,111 для представления в машине. Независимо от того, сколько цифр вы выберете для записи, результат никогда не будет точно 1/9, а лучше приблизится к 1/9.
Следовательно, вы не можете точно представить 1/9 на компьютере или машине, но вы можете представить ее приблизительное значение, используя метод представления с плавающей запятой.
Метод представления с плавающей запятой -
Выбирая любое конечное число битов. В настоящее время наиболее популярным представлением является использование двоичной дроби с числителем, использующим первые 53 бита, начиная со старшего бита, и со знаменателем в степени двойки.
Python просто печатает десятичное приближение к фактическому десятичному значению двоичного приближения, хранящемуся в машине. В противном случае на большинстве машин он печатает все значение в 53 битах.
Пример —
Десятичное значение 0,1 будет состоять из 53 бит,
из десятичного импорта Decimal
Десятичный (0,1)
Он печатает -
Десятичный(‘0.1000000000000000055511151231257827021181583404541015625’)
Но в нем больше цифр, чем большинство людей считают полезным, поэтому в Python вместо этого печатайте округленное значение.
Пример —
Десятичное значение 0,1 в Python, как правило,
0.1
Он печатает -
0.1
Пример —
из десятичного импорта Decimal
Десятичный (1/9)
Он печатает -
Десятичный(‘0,111111111111111104943205418749130330979824066162109375’)
Точно так же он также имеет больше цифр, чем большинство людей считают полезным, поэтому вместо этого в Python печатайте округленное значение.
Пример —
1/9
Он печатает -
0.1111111111111111
Обратите внимание, что хотя напечатанный результат выглядит как точное значение 1/9, фактическое сохраненное значение является ближайшей представимой двоичной дробью.
Проблемы -
Одно и то же представление может быть для разных чисел.
Пример —
из десятичного импорта Decimal
печать (десятичный (0,10000000000000001))
печать (десятичный (0,1))
Он печатает -
0.1000000000000000055511151231257827021181583404541015625
0.1000000000000000055511151231257827021181583404541015625
Встроенная функция Python repr() выберет 0,10000000000000000001, содержащее 17 значащих цифр. Самый маленький из них отображает только 0,1.
В природе двоичного представления с плавающей запятой ваше оборудование поддерживает арифметику с плавающей запятой.
Пример —
импортировать математику
print(repr(math.pi)) # Использовать repr
print(format(math.pi, '.13g')) # Дать 13 значащих цифр
print(format(math.pi, '.2f ')) # Дайте 2 цифры после точки
Он печатает -
3.141592653589793
3.14159265359
3.14
Как вы заметили, может быть одно и то же представление для разных десятичных значений, поэтому следующий пример возвращает false.
.1 + .1 + .1 == .3
Он печатает -
ЛОЖЬ
Потому что 0,1 — это не совсем 1/10, а 0,3 не может приблизиться к точному значению 3/10. Таким образом, 0,1 + 0,1 + 0,1 не может дать ровно 0,3. Поэтому предварительное округление с помощью функции round() здесь может не помочь.
раунд (.1, 1) + раунд (.1, 1) + раунд (.1, 1) == раунд (.3, 1)
Он печатает -
ЛОЖЬ
Но здесь может помочь пост-округление с помощью функции round(), чтобы результаты с точными значениями стали сопоставимыми.
раунд (.1 + .1 + .1, 1) == раунд (.3, 1)
Он печатает -
Истинный
Модули для точного десятичного представления -
Для случаев использования, требующих точного десятичного представления -
- Использование десятичного модуля —
Он реализует десятичную арифметику, подходящую для бухгалтерских приложений и высокоточных приложений. - Использование модуля дробей —
Он реализует арифметику на основе рациональных чисел, поэтому такие числа, как 1/9, могут быть представлены точно.
Пакет NumPy и многие другие пакеты для математических и статистических операций также могут помочь с представлением чисел с плавающей запятой и арифметикой.
(1). метод float.as_integer_ratio() -
Метод float.as_integer_ratio() выражает значение числа с плавающей запятой в виде дроби. Он пытается найти числитель и знаменатель отношения данной дроби.
Пример -
x = 5,15699
x.as_integer_ratio()
Он печатает -
(725781820073543, 140737488355328)
Для которого -
x == 725781820073543 / 140737488355328
Он печатает -
Истинный
(2). метод float.hex() -
Метод float.hex() используется для выражения числа с плавающей запятой в шестнадцатеричном формате (с основанием 16). Это дает точное значение, хранящееся на вашей машине.
Пример -
х = 5,15699
x.hex()
Он печатает -
0x1.4a0c1fc8f3238p+2
Для которого -
x == float.fromhex('0x1.4a0c1fc8f3238p+2')
Он печатает -
Истинный
Поскольку представление является точным, его можно портировать на другие версии Python Exchange с другими языками, поддерживающими тот же формат.
(3). метод math.fsum() -
Метод math.fsum() помогает уменьшить потерю точности при суммировании. Он отслеживает «потерянные цифры» по мере добавления значений к промежуточной сумме.
Пример —
# Без использования math.fsum
print(sum([0.2] * 10) == 2.0)
# С использованием math.fsum
print(math.fsum([0.2] * 10) == 2.0)
Он печатает -
ЛОЖЬ
Истинный
Ошибка представления -
Ошибка представления возникает, когда десятичные дроби не могут быть точно представлены как двоичные дроби (с основанием 2). Это основная причина, по которой многие языки программирования не отображают точные десятичные числа, которые вы ожидаете.
Арифметика с плавающей запятой IEEE-754 используется почти во всех компьютерах, и Python также поддерживает числа с плавающей запятой IEEE-754 «двойной точности». Точность 53 бита.
Пример —
1/10 нельзя точно представить в виде двоичной дроби. Чтобы округлить 0,1 до ближайшей дроби, его можно представить в виде J/2**N, где J — целое число, состоящее ровно из 53 бит.
Это значит,
1 / 10 ~= J / (2**N)
J ~= 2**N / 10
Поэтому,
2**52 <= 2**56 // 10 < 2**53
Он печатает -
Истинный
Поскольку J имеет ровно 53 бита (равно>= 2**5 2, но ‹ 2**53), а лучшее значение для N — 56.
Следовательно,
q, r = divmod(2**56, 10)
print(r)
print(q+1)
Он печатает -
6
7205759403792794
Приближение к 1/10 в 754 двойной точности -
7205759403792794 / 2 ** 56
3602879701896397/2**55# Деление и числителя, и знаменателя на два
Это не совсем 1/10 с мишенью или без нее. Если округлить, получится чуть больше 1/10, а если нет, то частное будет чуть меньше 1/10. Вышеуказанная цифра округлена. Следовательно, она ни в коем случае не может быть ровно 1/10.
0.1 * 2 ** 55
Если умножить на 10**55, то получится значение до 55 знаков после запятой -
3602879701896397 * 10 ** 55 // 2 ** 55
Он равен -
1000000000000000055511151231257827021181583404541015625
Если мы округлим в Python -
формат(0.1, ‘.17f’)
Он печатает -
0.10000000000000001
Используя десятичные и дробные модули -
# Импорт необходимых модулей
из десятичного импорта Decimal
из дробей import Fraction
# Используя from_float
print(Fraction.from_float(0.1))
# Использование as_integer_ratio
print((0.1).as_integer_ratio())
# Использование from_float
print(Decimal.from_float(0.1))
# Использование from_float
print(format(Decimal.from_float(0.1), ‘.17’))
Он печатает -
3602879701896397/36028797018963968
(3602879701896397, 36028797018963968)
0.1000000000000000055511151231257827021181583404541015625
0.10000000000000001