Python 浮点数的冷知识
本周的PyCoder's Weekly 上分享了一篇小文章,它里面提到的冷知识很有意思,我稍作补充,分享给大家。
它提到的部分问题,读者们可以先思考下:
- 若两个元组相等,即 a==b 且 a is b,那么相同索引的元素(如 a[0] 、b[0])是否必然相等呢?
- 若两个对象的 hash 结果相等,即 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') 表示一个“不是数”的东西,它本身不是确定值,两个对象作比较时不相等,但是其哈希结果是固定值,作比较时相等;可用作字典的键值,而且是不冲突的键值
- 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猫】, 本号连载优质的系列文章,有喵星哲学猫系列、Python进阶系列、好书推荐系列、技术写作、优质英文推荐与翻译等等,欢迎关注哦。
Python 浮点数的冷知识的更多相关文章
- 盘点 Python 中的那些冷知识(二)
上一篇文章分享了 Python中的那些冷知识,地址在这里 盘点 Python 中的那些冷知识(一) 今天将接着分享!! 06. 默认参数最好不为可变对象 函数的参数分三种 可变参数 默认参数 关键字参 ...
- python 冷知识(装13 指南)
python 冷知识(装13 指南) list1 += list2 和 list1 = list1 + list2 的区别 alpha = [1, 2, 3] beta = alpha # alpha ...
- 10个不为人知的 Python 冷知识
转载: 1. 省略号也是对象 ...这是省略号,在Python中,一切皆对象.它也不例外. 在 Python 中,它叫做 Ellipsis . 在 Python 3 中你可以直接写…来得到这玩意. 而 ...
- python冷知识(续)
python 冷知识 1.交互式中修改最大递归深度 大家都知道使用递归是有风险的,递归深度过深容易导致堆栈的溢出. 那到底,默认递归次数限制是多少呢? 可以使用sys这个库来查看 >>&g ...
- python冷知识
目录 省略号也是对象 奇怪的字符串 and 和 or 的取值顺序 访问类中的私有方法 时有时无的切片异常 两次 return for 死循环 intern机制 省略号也是对象 在python中一切皆对 ...
- 10 个不为人知的Python冷知识
1. 省略号也是对象 ... 这是省略号,在Python中,一切皆对象.它也不例外. 在 Python 中,它叫做 Ellipsis . 在 Python 3 中你可以直接写-来得到这玩意. > ...
- python小技巧 小知识
python小技巧 小知识 python系统变量(修改调用shell命令路径)或用户空间说明 20150418 python调用系统命令,报找不到.怎么办? 类似执行shell的: [ -f /etc ...
- Python开发(一):Python介绍与基础知识
Python开发(一):Python介绍与基础知识 本次内容 一:Python介绍: 二:Python是一门什么语言 三:Python:安装 四:第一个程序 “Hello world” 五:Pytho ...
- 这些鲜为人知的前端冷知识,你都GET了吗?
背景 最近公司项目不多,比较清闲,划水摸鱼混迹于各大技术博客平台,瞬间又GET了好多前端技能,一些属于技巧,一些则是闻所未闻的冷知识,一时间还消化不过来,不由的发出一声感叹! 前端可真是博大精深 于是 ...
随机推荐
- 快速搭建 SpringCloud 微服务开发环境的脚手架
本文适合有 SpringBoot 和 SpringCloud 基础知识的人群,跟着本文可使用和快速搭建 SpringCloud 项目. 本文作者:HelloGitHub-秦人 HelloGitHub ...
- windows系统与SQL SERVER 2008数据库服务性能监控分析简要
软件系统性能测试体系流程介绍之windows系统与SQL SERVER 2008数据库服务性能监控分析简要 目前大部分测试人员对操作系统资源.中间件.数据库等性能监控分析都是各自分析各自的监控指标方式 ...
- java8 Optional优雅非空判断
java8 Optional优雅非空判断 import java.util.ArrayList;import java.util.List;import java.util.Optional; pub ...
- Linux -- 进程管理之 fork() 函数
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间.然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同.相当于克隆了一个自己. Test1 f ...
- word2vec:CBOW和skip-gram模型
1.CBOW模型 之前已经解释过,无论是CBOW模型还是skip-gram模型,都是以Huffman树作为基础的.值得注意的是,Huffman树中非叶节点存储的中间向量的初始化值是零向量,而叶节点对应 ...
- python数据分析三个重要方法之:numpy和pandas
关于数据分析的组件之一:numpy ndarray的属性 4个必记参数:ndim:维度shape:形状(各维度的长度)size:总长度dtype:元素类型 一:np.array()产生n维 ...
- 小白学 Python 爬虫(9):爬虫基础
人生苦短,我用 Python 前文传送门: 小白学 Python 爬虫(1):开篇 小白学 Python 爬虫(2):前置准备(一)基本类库的安装 小白学 Python 爬虫(3):前置准备(二)Li ...
- Swift - 给UICollectionview设置组背景和圆角
钟情圆角怎么办 最近由于我们的UI钟情于圆角搞得我很方,各种圆角渐变,于是就有了下面这篇给UICollection组设置圆角和背景色的诞生,不知道在我们平时有没有遇到这样子的一些需求,就是按照每一组给 ...
- React Context上下文
目录 前言 一 context旧版使用步骤 1.1 根组件childContextTypes属性 1.2 根组件getChildContext方法 1.3 子组件contextTypes静态属性 1. ...
- DOM中操作结点的属性_操作元素结点的样式
有俩种方式操作结点的属性. 首先我们需要先获取所要操作的结点元素: var uname=document.getElementById("uname"); var gan=unam ...