昨天遇到math.fsum之问后,马上向一位师姐求解,经过师姐点拨,终于弄明白其中的玄奥。
问题重现及简化:
>>> math.fsum([0.1, 0.2])
0.30000000000000004
结论
先说结论,fsum函数的确是精确地浮点数求和,但是由于二进制计算的原因,结果总是会有误差,而fsum函数中的“精确”是指较高位有效数字的计算,并不是完全精确!事实上,即使是会计中精确计算用到的demical也不会完全精确。其中的种种,请往下看……
计算机内存中的二进制浮点数运算
大家都知道,计算机内存的数都是使用二进制表示的,比如,0.001在内存中表示为0/2 + 0/4 + 1/8(这里面的2、4、8应当理解为2的一次幂、两次幂、三次幂),但是很遗憾,大部分的数字都没法用二进制精确表示,这些数字用二进制表示的话会是一个无限循环小数。
既然是无限循环小数,那么怎么处理呢?计算机内存总是有限的。这个时候,一位科学家站出来了——Kahan教授,他发布了二进制浮点数算术标准IEEE 754。顺便提一句,IEEE 754的发明人Kahan教授也因此获得1987年的图灵奖。
IEEE 754是什么样的呢?跟咱们这相关的,简单来说,就是他规定了二进制的64位双精度表示法,其中又规定了一个浮点数的小数部分使用53位进行表示。因此:
>>> format(0.1,'.55f')
'0.1000000000000000055511151231257827021181583404541015625'
>>> format(0.2,'.55f')
'0.2000000000000000111022302462515654042363166809082031250'
这两个数53位二进制表示后的的精确和为0.3000000000000000166533453693773481063544750213623046875,而与该精确和最接近的可表示的IEEE 754浮点数为0.3000000000000000444089209850062616169452667236328125。又由于Python只显示17位十进制数,因此,就是我们看到的上面的问题。
昨天碰到的那些问题也是这个原因。
所以,从一开始我对fsum函数用于精确求解浮点数和中的“精确”就理解有误!看似结果出现了偏差,但那恰恰是经过高精度运算后的结果。
一点回味
终于是大概概搞懂了,但是心中仍然有很多疑问,比如如何求出0.1用双精度模型表示的数字呢?如何寻找与精确求和结果最接近的IEEE 754浮点数呢?……这些问题要全都搞明白,我想需要的不是一个很短的时间。所以,我暂且放下这些疑问,待日后有机会再一一求证!“路漫漫其修远兮,吾将上下而求索”
最后,感谢师姐的帮助,感谢网上大佬们的博客,受益匪浅!
参考文献
Python浮点算数:争议和限制
IEEE 754二进制浮点数算术标准
Python2 math.fsum不准确吗?
百度百科:IEEE 754