#python #floating-point
Вопрос:
Я знаю, что при двоичном представлении невозможно точно представить число с плавающей запятой (и я также понимаю, почему 0.1 0.2 == 0.3 является ложным). Теперь вот где я застрял, пытаясь поэкспериментировать с различными случаями плавающей арифметики:
Глядя на то, как десятичные числа преобразуются в двоичный формат, я пришел к выводу, что два числа, которые имеют одинаковую дробную часть в десятичном представлении (например: 0,3 и 1,3), будут иметь одну и ту же дробную часть после преобразования в двоичную форму.
Чтобы проверить это, я попробовал следующие коды на python:
print(f"{0.3:.20f}")
print(f"{1.3:.20f}")
0.29999999999999998890
1.30000000000000004441
Я не мог понять, почему дробные части получались разными, поэтому, чтобы лучше понять ситуацию, я попробовал это:
print(f"{0.3:.20f}")
print(f"{1 0.3:.20f}")
print(f"{1.3:.20f}")
0.29999999999999998890
1.30000000000000004441
1.30000000000000004441
Вопрос: Поскольку 1 не является приблизительным числом (поскольку можно представить 1 в точной двоичной форме как 2^0), так почему добавление 1 изменяет дробную часть числа?
Кроме того, когда мы вычитаем 1 из 1,3, почему полученное значение не равно 0,3
print(f"{1.3:.20f}")
print(f"{1.3 - 1:.20f}")
print(f"{0.3:.20f}")
1.30000000000000004441
0.30000000000000004441
0.29999999999999998890
Всю мою проблему можно резюмировать в следующем сопоставлении:
print(1 .3 == 1.3)
print(0.3 == 1.3 -1)
True
False
Комментарии:
1. @КлаусД. Я не думаю, что это обман. ОП, похоже, понимает, что такое двоичное представление с плавающей запятой и его ограничения. Они спрашивают об этом конкретном явлении
2. Re: «Почему добавление 1 изменяет дробную часть числа?» Округление.
3. Это из-за другого ограничения округления из-за использования соответствующего стандарта, такого как IEEE_754, как описано в связанном (дублирующем) вопросе. Другими словами, существует процедура, которая преобразует дробь в округленные десятичные дроби, которая не согласуется с дробями с различными целыми частями, но одинаковыми десятичными частями, как вы заметили с 1.3 и 0.3, .3 в x.3 округляется по-разному в зависимости от x.
4. @j1-ли, приводит ли округление к тому, что результат операции с плавающей запятой конечной точности отличается от наивно ожидаемого результата, зависит от особенностей операндов, например, их относительной величины (для конкретных примеров см. Лемму Стербенца, вычитающая отмена). Иногда результат соответствует наивным ожиданиям, иногда-нет. Чтобы разобраться в этом, я бы рекомендовал имитировать рассматриваемую двоичную арифметическую операцию вручную, и все должно стать ясным.
5. @Gravity Это из-за того, что нюффа объясняет в своем комментарии выше: «Иногда результат соответствует наивным ожиданиям, иногда нет». ; Арифметические операции с поплавками не совпадают с наивными арифметическими операциями, которые вы ожидали бы. Вы можете использовать ecs.umass.edu/ece/koren/arith/simulator/FPAdd/ для сложения и вычитания, h-schmidt.net/FloatConverter/IEEE754.html для преобразования. Действительно, я получаю 0.3 1.0 == 1.3, 1.3 — 1.0 != 0.3 .
Ответ №1:
точно представляют собой точку с плавающей запятой
Эти числа с плавающей запятой занимают фиксированное количество места, 64 бита. Это позволяет точно закодировать около 2 64 различных значений. 0.3, 1.3 не входят в этот набор.
Каждое конечное кодированное значение представляет собой целое число * степень 2. Таким образом, 0.3, 1.3 точно не сохраняются, поскольку их нельзя масштабировать до «целого числа * степени 2». Вместо этого используются близлежащие значения:
5404319552844595*pow(2,-54): 0.299999999999999988897... (closest)
0.3 (not encodable)
5404319552844596*pow(2,-54): 0.300000000000000044408... (next closest)
5854679515581644*pow(2,-52): 1.299999999999999822364... (next closest)
1.3 (not encodable)
5854679515581645*pow(2,-52): 1.300000000000000044408... (closest)
0.3 == 1.3 -1
лучше рассматривать как 0.299999999999999988897... == 1.300000000000000044408... - 1.0
.
Вычитание 1.0 из 1.300000000000000044408… точно: 0,300000000000000044408… и явно не равно 0,2999999999999999988897… .
когда я попробовал 1.3 == 1 0.3, это оказалось правдой, вот что меня смутило
Это похоже 1.300000000000000044408... = 1.0 0.299999999999999988897...
на . Сумма 1.299999999999999988897...
не может быть представлена в виде плавающей точки, так как существует ограничение на количество битов, используемых для кодирования целого числа в целочисленной * степени 2. В качестве суммы используется ближайшее представимое значение.
5854679515581644 *pow(2,-52): 1.299999999999999822364 (next closest)
5854679515581644.75*pow(2,-52): 1.299999999999999988897 (not encodable)
5854679515581645 *pow(2,-52): 1.300000000000000044408 (closer)
Комментарии:
1. 0.3 != 1.3 -1 имело для меня смысл, но когда я попробовал 1.3 == 1 0.3, это оказалось правдой, вот что меня смутило