请教一个 Python 浮点数的小问题

2022-02-06 01:42:08 +08:00
 knowckx

v1 = 2.2 * 3 # 6.6
v2 = 3.3 * 2 # 6.6
print(v1, v2, v1==v2, v1<=v2, v1>=v2)

输出结果是:
6.6000000000000005 6.6 False False True

这个结果惊讶到我了,没想到这里也会有坑。
所以浮点数比较的正确方式是?

5031 次点击
所在节点    Python
60 条回复
0attocs
2022-02-06 18:49:01 +08:00
@des #28 在你这套荒谬逻辑下“进制转换也是不会损失精度的,提前是使用得当”,而你举的例子只是使用不当。
maojun
2022-02-06 18:51:06 +08:00
很好奇业界的最佳实践是怎么样的,蹲一个大佬。另外,如果用分数结构来表示浮点数的话,貌似就可以回避很多计算过程中的精度损失了吧🤨不过不知道为什么很少看到有这么做的。
0attocs
2022-02-06 19:10:46 +08:00
@des #22 而且你提的“精度损失基本都发生在“进制转换”的过程中”说法本身也很奇怪。且不说多少涉及实数 /浮点数的问题 /程序里有你所谓的“进制转换”,你这里的 error 完全就是 rounding error ,跟什么进制转换没必然关系,只要需要浮点数 /有限精度表示就有可能引入 error 。都是二进制就没事了吗,2^-53 的 64 位浮点数表示会带来精度损失吗?
0attocs
2022-02-06 19:14:49 +08:00
@0attocs #32 *1+2^-53 的 64 位浮点数表示
UN2758
2022-02-06 19:15:05 +08:00
@maojun #42 分数表示理解起来也很麻烦,一般是对数化之后做运算
akazure
2022-02-06 19:31:51 +08:00
@maojun 我记得好像有个游戏公司(还是 AMD 推土机来着)就是梭哈整数运算,几乎放弃浮点数运算,然后寄了的
0attocs
2022-02-06 19:54:45 +08:00
@ipwx 加一个常量 tolerance epsilon 肯定比比没有要好,但还是有点蒙住眼睛骗自己问题解决了的味道,无法被称为“正确”。要知道浮点数的分布不是均匀的,ulp 不是一个常量,而 machine epsilon 只是定义为 1 右边 ulp 的一半。

一个比只加常量 tolerance epsilon 更好一点点的方案是根据比较的两数来 scale 这个 tolerance epsilon 。此时 x < y 会被定义为 | x - y | / | x | <= epsilon or | x - y | / | y | <= epsilon 。
littlewing
2022-02-06 20:00:02 +08:00
@whusnoopy 哪里都有杠精
sutra
2022-02-06 20:40:04 +08:00
需要注意的是 golang 那个是 float64 ,别的另外几个语言是 32 bit 的。
disk
2022-02-06 21:00:05 +08:00
看需求吧,有些情况可以考虑符号计算
ipwx
2022-02-06 21:54:10 +08:00
@0attocs | x - y | / | x | <= epsilon or | x - y | / | y | <= epsilon 也并不那么合理。

比如我 delta = x - y 是某一步运算。算到很后面我需要比较 delta 是不是和 0 相等。

那很自然应该 abs(delta) < epsilon 而不是 abs(delta) / abs(delta) < epsilon
qdcanyun
2022-02-06 23:45:09 +08:00
jinliming2
2022-02-06 23:45:42 +08:00
@knowckx #18: https://go.dev/blog/constants#numbers 所有纯数字表达式(包括四则运算的结果、复数等)都是常量,会在编译时给你替换好。
icyalala
2022-02-07 02:04:33 +08:00
Python 和其他语言是正确的,编译时和运行时表现相同。

Go 才是特例:Go 会在编译时把 constant expressions 在编译时求值,用的还是高精度计算:
https://go.dev/ref/spec#Constants 这样就会造成编译时和运行时表现不同。

这个问题 10 年前就有人提到了: https://github.com/golang/go/issues/2789
Go 的作者之一回复 "It is too late to change the language spec at this point."
Hanggi
2022-02-07 03:19:47 +08:00
计算机计算整数是精确的,所以如果想要达到精确计算通常可以使用类似 Decimal 的库,或者用 BigInt 转换使用。

浮点数不管哪个语言都有这个问题,打开 chrome 控制台,在里面输入 0.1 + 0.2 回车试试。
whusnoopy
2022-02-07 08:55:53 +08:00
whusnoopy
2022-02-07 09:04:40 +08:00
@littlewing #48

不好意思前面按错快捷键直接发出去了

如果你觉得我是杠精,那不应该浪费时间再回我一句

如果是我的表述让其他人不适,请告知我不妥的地方,这样我才能意识到问题并去改正

如果是我想讨论的大家意识偏差有问题,那么我们回到问题本身,看是我的逻辑有问题还是定义有问题

在 #15 @ipwx 的回复里提了一句「科班才是硬道理」并有点赞,不管赞的是这句话,还是后面更详尽的解释,联想本站很多讨论和社会舆论,是有很多对科班有要求的地方

在 #21 @knowckx 的回复里,通过自己的专业和学习过程,说明自己是正儿八经的科班,并不认为科班就能避开主楼里提出和衍生出来的问题

那我在 #36 里想讨论的,如果大家有假定「科班可以避开相关问题」,楼主也列举了他的科班经历并表示同经历的很多人也还是会有疑惑,那到底是大家对「科班」的定义不一致,还是「科班可以避开相关问题」这个假设就不应该成立?
joApioVVx4M4X6Rf
2022-02-07 10:00:23 +08:00
@qdcanyun 这个帖子总结的真棒!!学到了
pcell
2022-02-07 19:01:42 +08:00
其实我更好奇 excel 是怎么处理小数点,财务都用 excel 记数应该是没有这种问题。
jinliming2
2022-02-10 01:47:32 +08:00
@pcell excel 也存在问题,但是现在比较新的版本都做了近似补偿: https://docs.microsoft.com/en-us/office/troubleshoot/excel/floating-point-arithmetic-inaccurate-result
比如输入公式:=1.333+1.225-1.333-1.225 可以得到 0 ,但是输入 =(1.333+1.225-1.333-1.225) 就得到了 -2.22045E-16

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/832021

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX