Python浮点数冷知识:NaN与Infinity的哈希陷阱

七月
七月
发布时间:2026-10-13| 6人浏览过
原创

本文探讨Python中浮点数的两个反直觉现象:相等元组的元素可能不相等,以及不相等的NaN对象哈希值却相同。深入解析float()对'nan'和'inf'的解析规则,揭示NaN作为非数值在比较、哈希及字典键使用中的特殊行为,并揭秘解释器中硬编码的哈希彩蛋。

Python浮点数冷知识:NaN与Infinity的哈希陷阱

本周PyCoder's Weekly推荐了一篇趣味短文,其中涉及的浮点数特性颇具启发性,在此进行补充并分享。

文中提出的几个问题值得读者先行思考:

  • 如果两个元组完全相等,即满足 a==b 且 a is b,那么它们相同索引处的元素(例如 a[0] 与 b[0])是否一定相等?
  • 如果两个对象的哈希值相同,即 hash(a) == hash(b),这是否意味着这两个对象必然相等?

上述两个问题的答案均为否定,否则便不构成所谓的“冷知识”。建议读者先尝试给出自己的解答,随后再查阅下文分析。

-----思考分割线-----

相等元组的元素为何不相等?

首先分析第一个问题。对于两个相同的元组 a 和 b,它们之间存在如下关系:


>>> a = (float('nan'),)
>>> b = a
>>> a  # (nan,)
>>> b  # (nan,)

>>> type(a), type(b)
(<type 'tuple'>, <type 'tuple'>)

>>> a == b
True

>>> a is b # 即 id(a) == id(b)
True

>>> a[0] == b[0]
False

上述代码执行结果显示:a 与 b 相等(类型、值及内存ID均一致),但它们对应的元素却不相等。

这两个元组仅包含一个元素(逗号后无其他元素,此为单元素元组的语法表示,即 len(a)==1 )。float() 作为内置函数,用于将参数转换为浮点数。

造成这一现象的原因需查阅文档,该内置函数的解析逻辑如下:


sign      ::= "+" | "-"
infinity    ::= "Infinity" | "inf"
nan      ::= "nan"
numeric_value ::= floatnumber | infinity | nan
numeric_string ::= [sign] numeric_value

在解析过程中,它可以处理前后空格、正负号前缀(+/-)以及常规浮点数。此外,它还支持两类不区分大小写的字符串:“Infinity”或“inf”,代表无穷大;以及“nan”,代表非数值(not-a-number),确切地说,它指代除常规数值外的所有情况。

前文提到的第一个冷知识便与“nan”相关。尽管两个元组整体相等,但其唯一元素却不相等。原因在于“nan”代表非数值概念,是一个范围而非具体值,因此不具备可比性。

作为对比,我们观察两个“无穷大浮点数”的表现:


>>> a = (float('inf'),)
>>> b = a
>>> a  # (inf,)
>>> b  # (inf,)

>>> a == b # True
>>> a is b # True
>>> a[0] == b[0] # True

请注意最后一次比较,其结果与前两个元组恰好相反。由此可得出结论:两个无穷大浮点数数值相等,而两个“非数值”对象数值不相等。

简化上述逻辑,可表述为:


>>> a = float('inf')
>>> b = float('inf')
>>> c = float('nan')
>>> d = float('nan')

>>> a == b # True
>>> c == d # False

不相等对象的哈希值为何相同?

至此,第一个冷知识已揭秘。接着探讨第二个问题:


>>> hash(float('nan')) == hash(float('nan'))
True

前文指出两个“非数值”对象不相等,但此处显示它们的哈希结果却相等,这似乎违背直觉。

我们可以推导出一条简明结论:两个不相等的对象,其哈希结果可能相同。

原因在于 hash(float('nan')) 的结果固定为 0,这是一个恒定值,因此在比较时必然相等。

实际上,关于 hash() 函数还隐藏着一个彩蛋:


>>> hash(float('inf')) # 314159
>>> hash(float('-inf')) # -314159

这个数值是否似曾相识?它正是圆周率 π 的前五位 3.14159 去掉小数点后的结果。在早期Python版本中,负无穷大的哈希值实为 -271828,取自自然对数 e 的前几位。这两个数值均硬编码于Python解释器中,可谓一种技术致敬。

由于 float('nan') 的哈希值相同,通常意味着它们不能作为字典中不同的键,但实际情况却出人意料:


>>> a = {float('nan'): 1, float('nan'): 2}
>>> a
{nan: 1, nan: 2}

# 作为对比:
>>> b = {float('inf'): 1, float('inf'): 2}
>>> b
{inf: 2}

如上所示,两个 nan 键在表示上完全一致(注意未加引号),它们可以共存;而 inf 则会被合并为一个,再次展现了 nan 的特殊性。

至此,两个冷知识分享完毕。其根本原因在于 float() 在解析浮点数时,Python 允许了 nan(非数值)的存在,这种不确定的存在导致了上述奇怪结果。

最后,我们进行总结:

  • 包含 float('nan') 的两个元组,作为整体比较时结果相等;但这两个相等元组的对应元素可能不相等。
  • float('nan') 代表“非数值”,其本身非确定值,两个对象比较时不相等,但其哈希值为固定值(0),比较时相等;它可用作字典键,且互不冲突。
  • float('inf') 代表无穷大浮点数,可视为确定值,两个对象比较时相等,其哈希值也相等;它可用作字典键,但会产生冲突(后值覆盖前值)。
  • float('nan') 的哈希结果为 0,float('inf') 的哈希结果为 314159。

参考资料:

https://docs.python.org/3/library/functions.html#float

https://www.pythondoeswhat.com/2019/09/welcome-to-float-zone.html

总结

以上就是本文的全部内容,希望这些内容对您的学习或工作有所帮助,感谢支持。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。