Как Python сравнивает int с плавающими объектами? - PullRequest
4 голосов
/ 31 января 2020

Документация о типах чисел c гласит:

Python полностью поддерживает смешанную арифметику c: когда у двоичного арифметического оператора c есть операнды для различных числовых типов c операнд с более узким типом расширяется по сравнению с операндом другого типа, где целое число уже, чем число с плавающей запятой, которое уже, чем сложное. Сравнения между числами смешанного типа используют одно и то же правило.

Это поддерживается следующим поведением:

>>> int.__eq__(1, 1.0)
NotImplemented
>>> float.__eq__(1.0, 1)
True

Однако для больших целых чисел что-то еще похоже, что это происходит, так как они не будут сравниваться равными, если явно не преобразованы в float:

>>> n = 3**64
>>> float(n) == n
False
>>> float(n) == float(n)
True

С другой стороны, для степеней 2 это, похоже, не проблема:

>>> n = 2**512
>>> float(n) == n
True

Поскольку документация подразумевает, что int "расширен" (я предполагаю, преобразован / приведен?) До float Я бы ожидал, что float(n) == n и float(n) == float(n) будут похожими, но приведенный выше пример с n = 3**64 предполагает по-другому. Итак, какие правила Python использует для сравнения int с float (или смешанных чисел c типов в целом)?


Протестировано с CPython 3.7.3 из Анаконда и PyPy 7.3.0 (Python 3.6.9).

1 Ответ

0 голосов
/ 03 февраля 2020

Спецификация языка для сравнения значений содержит следующий абзац:

Номера встроенных числительных c типов (Numeri c Типы - int, float, complex) и из типов стандартных библиотек fractions.Fraction и decimal.Decimal можно сравнивать внутри и между их типами с ограничением на то, что комплексные числа не поддерживают сравнение порядка. В пределах используемых типов они математически (алгоритмически) корректно сравниваются без потери точности .

Это означает, что при сравнении двух типов чисел c фактическое ( математические) числа, представленные этими объектами, сравниваются. Например, цифра 16677181699666569.0 (то есть 3**34) представляет число 16677181699666569, и хотя в «float-space» нет разницы между этим числом и 16677181699666568.0 (3**34 - 1) они действительно представляют разные цифры. Из-за ограниченной точности с плавающей запятой в 64-битной архитектуре значение float(3**34) будет сохранено как 16677181699666568 и, следовательно, оно представляет собой число, отличное от целого числа 16677181699666569. По этой причине у нас есть float(3**34) != 3**34, который выполняет сравнение без потери точности.

Это свойство важно для того, чтобы гарантировать транзитивность отношения эквивалентности нумерации c типов. Если сравнение от int до float даст аналогичные результаты, как если бы объект int будет преобразован в объект float, тогда транзитивное отношение будет недействительным:

>>> class Float(float):
...     def __eq__(self, other):
...         return super().__eq__(float(other))
... 
>>> a = 3**34 - 1
>>> b = Float(3**34)
>>> c = 3**34
>>> a == b
True
>>> b == c
True
>>> a == c  # transitivity demands that this holds true
False

* float.__eq__ реализация с другой стороны, которая рассматривает представленные математические числа, не нарушает это требование:

>>> a = 3**34 - 1
>>> b = float(3**34)
>>> c = 3**34
>>> a == b
True
>>> b == c
False
>>> a == c
False

В результате отсутствия транзитивности порядок следующего списка не будет изменен сортировкой (так как все последовательные числа кажутся равными):

>>> class Float(float):
...     def __lt__(self, other):
...         return super().__lt__(float(other))
...     def __eq__(self, other):
...         return super().__eq__(float(other))
... 
>>> numbers = [3**34, Float(3**34), 3**34 - 1]
>>> sorted(numbers) == numbers
True

При использовании float, с другой стороны, порядок меняется на обратный:

>>> numbers = [3**34, float(3**34), 3**34 - 1]
>>> sorted(numbers) == numbers[::-1]
True
...