Python如何将'int'与'float'对象进行比较?

8

关于数字类型的文档表明:

Python 完全支持混合算术:当二进制算术运算符具有不同数值类型的操作数时,“较窄”类型的操作数会扩展到另一个操作数的类型,其中整数比浮点数“窄”,而复数比浮点数“窄”。混合类型之间的比较使用相同的规则。

这得到了以下行为的支持:

>>> 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) == nfloat(n) == float(n) 相似,但在上面的 n = 3 ** 64 示例中表现不同。因此,Python 在比较 intfloat (或一般混合数字类型)时使用什么规则?


使用 Anaconda 中的 CPython 3.7.3 和 PyPy 7.3.0(Python 3.6.9)进行测试。


4
你可能只是遇到了浮点精度限制,float(3**64)并不完全等于int(3**64) - deceze
2
相反,float(2**512)可以被准确地表示,尽管它更大,因为它是2的幂。尾数只需要1位即可完全精确,指数只需要9位。 - chepner
3
我理解这种行为,但文件说明指出,“较窄”的类型,例如int,会被扩展到另一个类型,例如float。因此,在往返转换中不应该有精度损失的问题;我预期float(n) == n会在内部处理,使得右侧被转换成float,即类似于float(n) == float(n) - a_guest
3
如果我没记错的话,Python 或者至少一些实现会费尽心思地精确比较值。当 3**64float(n) 进行比较时,它不会转换为 float 类型。相反,精确数学值 3**64 与精确数学值 float(n) 进行比较。由于它们不同,结果是 False。当你将 3**64 转换为 float 类型时,它会转换为一个可表示为 float 类型的值,引入了一些误差。在文档中的措辞不幸地说 floatint "更宽"。通常,它不能表示所有的 int 值... - Eric Postpischil
4
似乎 https://github.com/python/cpython/blob/master/Objects/floatobject.c#L382 负责浮点数和整数的比较。它肯定不仅仅是将整数转换为浮点数然后进行比较,但是我的 C 语言阅读能力不够强,无法确定它究竟在做什么。 - Kevin
显示剩余17条评论
1个回答

4

值比较语言规范 包含以下段落:

内置数字类型(数字类型-int,float,complex)和标准库类型fractions.Fractiondecimal.Decimal的数字可以在其类型内部和跨类型进行比较,但复数不支持顺序比较。在涉及的类型限制内,它们在算法上进行正确的数学比较而没有精度损失。

这意味着当比较两个数值类型时,将比较这些对象所表示的实际(数学)数字。例如,numeral 16677181699666569.0(即3**34)表示数字16677181699666569,尽管在“浮点空间”中,这个数字和16677181699666568.03**34 - 1)之间没有区别,但它们代表不同的数字。由于浮点精度有限,在64位架构上,值float(3**34)将存储为16677181699666568,因此它代表的数字与整数数字16677181699666569不同。因此,我们有float(3**34) != 3**34,它可以在不丢失精度的情况下进行比较。

这个属性很重要,以保证数值类型的等价关系传递性。如果将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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接