Почему int (x-1) == x True в Python 3.7 с некоторыми значениями x?

В Python 3.7 int(x-1) == x равно True для x = 5e+17 Почему это так и как я могу предотвратить эту ошибку?

Чтобы воспроизвести, вставьте это в вашу консоль Python:

int(5e+17-1) == 5e+17
>True

(Я использую int потому что x является результатом деления, и мне нужно проанализировать его как int.)

Ответы

Ответ 1

Начнем с того, что установим, что 5 == 5.0 - это True хотя 5 - это int а 5.0 - это число с float. Это по замыслу.

Если мы будем помнить об этом, мы также можем принять, что int(5e+17) == 5e+17 - это True.

Наконец, мы видим, что int(5e+17) == int(5e+17-1) также имеет значение True из-за ошибок точности (спасибо @juanpa.arrivillaga за ссылку).

Теперь понятно, почему int(5e+17-1) == 5e+17 - True.

Это можно решить с помощью Decimal но обязательно инициализируйте его строкой:

from decimal import Decimal

Decimal('5e+17') - 1 ==  Decimal('5e+17')

# False

Ответ 2

Python float хранится в виде двойной точности с плавающей запятой числа. Они имеют точность только 53 бита, поэтому целые числа больше 2 53, сохраняемые как числа с плавающей запятой, начинают терять точность. Вот наглядный пример того, как большие числа начинают терять точность:

>>> x = float(2**53-10)
>>> x
9007199254740982.0
>>> for i in range(20):
...   print(x+i)
...
9007199254740982.0
9007199254740983.0
9007199254740984.0
9007199254740985.0
9007199254740986.0
9007199254740987.0
9007199254740988.0
9007199254740989.0
9007199254740990.0
9007199254740991.0  <--- 2**53-1
9007199254740992.0  <--- 2**53
9007199254740992.0  <--- NOT 2**53+1
9007199254740994.0  <--- 2**53+2
9007199254740996.0
9007199254740996.0
9007199254740996.0
9007199254740998.0
9007199254741000.0
9007199254741000.0
9007199254741000.0

Вышеприведенное число составляет приблизительно 9e + 15, поэтому ваше число 1e + 17 может привести к потере точности. Вы должны добавить/вычесть 16 из таких больших чисел, чтобы ожидать изменения хранимого значения:

>>> x = 1e17
>>> for i in range(20):
...  print(f'{x+i:.1f}')
...
100000000000000000.0
100000000000000000.0
100000000000000000.0
100000000000000000.0
100000000000000000.0
100000000000000000.0
100000000000000000.0
100000000000000000.0
100000000000000000.0
100000000000000016.0
100000000000000016.0
100000000000000016.0
100000000000000016.0
100000000000000016.0
100000000000000016.0
100000000000000016.0
100000000000000016.0
100000000000000016.0
100000000000000016.0
100000000000000016.0

В Python есть функции для преобразования в и из точного двоичного значения с плавающей запятой. 1 до и 13 шестнадцатеричных цифр после десятичного знака указывают 53-битное значение:

>>> (1e17).hex()
'0x1.6345785d8a000p+56'
>>> print(f"{float.fromhex('0x1.6345785d8a000p56'):.1f}")
100000000000000000.0

Добавление одного к 53-битному значению:

>>> print(f"{float.fromhex('0x1.6345785d8a001p56'):.1f}")
100000000000000016.0

Ответ 3

5e+17 - литерал с плавающей точкой, а не целочисленный.

В CPython тип с float - это тип с плавающей запятой двойной точности, который в большинстве современных процессоров является 64-битным IEEE 754. Это означает, что у вас есть только 53 бита для представления цифр (около 16 десятичных цифр). Дополнительные цифры будут потеряны. Например:

>>> 111111111111111119.0 == 111111111111111118.0
True
>>> 0.111111111111111119 == 0.111111111111111118
True

В то время как:

>>> 111111111111111119 == 111111111111111118
False

OTH, вы можете использовать целые числа; Хотя 64-разрядные целые числа могут представлять только около 19 десятичных цифр, Python автоматически создает целочисленные объекты неограниченной точности вместо целых чисел машинного размера, когда это необходимо.

>>> 5*10**17-1
499999999999999999

>>> 5*10**70 -1
49999999999999999999999999999999999999999999999999999999999999999999999

>>> a = 5*10**100 
>>> a
50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
>>> a-1 == a
False

Поскольку вы хотите обработать результат как int, вы можете преобразовать его в целое число после деления перед дальнейшей обработкой, чтобы сохранить точность:

>>> int(5e+17)-1 == 5 * 10**17
False