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进阶系列、好书推荐系列、技术写作、优质英文推荐与翻译等等,欢迎关注哦。

原文地址:https://www.cnblogs.com/pythonista/p/11565135.html

时间: 2024-10-04 05:21:59

Python 浮点数的冷知识的相关文章

盘点 Python 中的那些冷知识(二)

上一篇文章分享了 Python中的那些冷知识,地址在这里 盘点 Python 中的那些冷知识(一) 今天将接着分享!! 06. 默认参数最好不为可变对象 函数的参数分三种 可变参数 默认参数 关键字参数 这三者的具体区别,和使用方法在 廖雪峰的教程 里会详细的解释.这里就不搬运了. 今天要说的是,传递默认参数时,新手很容易踩雷的一个坑. 先来看一个示例 def func(item, item_list=[]):    item_list.append(item)    print(item_li

python 冷知识(装13 指南)

python 冷知识(装13 指南) list1 += list2 和 list1 = list1 + list2 的区别 alpha = [1, 2, 3] beta = alpha # alpha 的别名 beta += [4, 5] # alpha 和 beta 都是[1, 2, 3, 4, 5] beta = beta + [6, 7] # 此时beta的内存地址已经变成了,[1, 2, 3, 4, 5, 6, 7] print(alpha) # alpha 还是 [1, 2, 3, 4

Python冷知识

目录 省略号也是对象 奇怪的字符串 and 和 or 的取值顺序 访问类中的私有方法 时有时无的切片异常 两次 return for 死循环 intern机制 省略号也是对象 在python中一切皆对象,省略号(...)也是一个对象注意:只能是三个点的省略号 在python中叫做Ellipsis 在python3中能直接得到它 print(...) print(type(...)) Ellipsis <class 'ellipsis'> 而在python2中没有这个,只能通过Ellipsis来

【C#冷知识系列】(一)那些你知道或者不知道的奇淫巧技

引子 正如我在个人介绍中所写,我是一个仍然坚持.NET的头铁高级软件工程师,研究C#,.NET已经六年多,一直坚持认为自己的能力不足以教授别人,所以一直没有想法写博客.工作几年,内容涵盖了.NET框架下的各种软件的开发,WPF,WinForm,WebForm,ASP.NET,MVC5,开发的软件几乎涵盖了.NET家族的各个成员.让我下定决心写一系列C#高级开发文章的原因并不是因为某天早上起床突如其来的兴致勃勃的决定要将自己这些年积累的经验分享给大家,而是是因为公司前端的一句"快脱坑吧,你们做WP

前端不为人知的冷知识

前端已经被玩儿坏了!像console.log()可以向控制台输出图片等炫酷的玩意已经不是什么新闻了,像用||操作符给变量赋默认值也是人尽皆知的旧闻了,今天看到Quora上一个帖子,瞬间又GET了好多前端技能,一些属于技巧,一些则是闻所未闻的冷知识,一时间还消化不过来.现分类整理出来分享给大家,也补充了一些平时的积累和扩展了一些内容. HTML篇 浏览器地址栏运行JavaScript代码 这个很多人应该还是知道的,在浏览器地址栏可以直接运行JavaScript代码,做法是以javascript:开

你根本不知道的冷知识,看完我惊呆了,原来.....

有些冷知识,非常的有趣,而且说不定就是之后与人交流的谈资,所以,这些必须收藏. 历史文学 1. 朱熹一辈子不吃豆腐 3.方孝儒是中国历史上唯一一个被"株十族"的人 4.猫是<圣经>里唯一没有提到的家养动物 5.埃及金字塔四面均为等边三角形,正对东南西北四个方位 6.条条大路通罗马的原因如图: 科学百科 1. 兔子不会流汗 2.玫瑰和苹果属同科,百合和大葱属同科 3.一张纸不能对折8次 4.北极熊是左撇子 5. dreamt 是唯一以"mt"结尾的英文单词

科普你知道,运动冷知识,火速围观

也许你是一名资深的健身爱好者,对健身知识已经了如指掌:也许你刚加入健身行列不久,对健身只有片面的认识.但不算是健身新手还是老手,总有一些你不知道的"冷知识"! 关于跑步 1.90%的马拉松运动员腿都很细,所以跑步根本不用担心粗腿: 2.在跑步机上如果你抓着扶手跑步,会直接到时你所消耗的热量降低20%: 关于饮食 1.断食12个小时,会是你的基础新陈代谢下降40%,靠节食减肥基本等同于无用功: 2.快餐店老板不会告诉你一份套餐热量有多少,最好的办法就是自己烹饪: 关于热量 1.人体每增加

前端冷知识集锦[转载]

作者:伯乐在线专栏作者 - 刘哇勇 前端已经被玩儿坏了!像console.log()可以向控制台输出图片等炫酷的玩意已经不是什么新闻了,像用||操作符给变量赋默认值也是人尽皆知的旧闻了,今天看到Quora上一个帖子,瞬间又GET了好多前端技能,一些属于技巧,一些则是闻所未闻的冷知识,一时间还消化不过来.现分类整理出来分享给大家,也补充了一些平时的积累和扩展了一些内容. HTML篇 浏览器地址栏运行JavaScript代码 这个很多人应该还是知道的,在浏览器地址栏可以直接运行JavaScript代

前端不为人知的一面--前端冷知识集锦

前端已经被玩儿坏了!像console.log()可以向控制台输出图片等炫酷的玩意已经不是什么新闻了,像用||操作符给变量赋默认值也是人尽皆知的旧闻了,今天看到Quora上一个帖子,瞬间又GET了好多前端技能,一些属于技巧,一些则是闻所未闻的冷知识,一时间还消化不过来.现分类整理出来分享给大家,也补充了一些平时的积累和扩展了一些内容. HTML篇 浏览器地址栏运行JavaScript代码 这个很多人应该还是知道的,在浏览器地址栏可以直接运行JavaScript代码,做法是以javascript:开