Python浮点数四舍五入问题的分析与解决方法
问题
昨天遇到一个问题,在6.6045保留三位小数时,使用round()函数进行计算,我们希望得到6.605,然而:
>>>round(6.6045,3) 6.604
网上有人说,因为在计算机里面,小数是不精确的,例如1.115在计算机中实际上是1.114999999999999991182,所以当你对这个小数精确到小数点后两位的时候,实际上小数点后第三位是4,所以四舍五入,结果为1.11.
这种说法,对了一半。
因为并不是所有的小数在计算机中都是不精确的。例如0.125这个小数在计算机中就是精确的,它就是0.125,没有省略后面的值,没有近似,它确确实实就是0.125.
但是如果我们在Python中运行:
>>>round(0.125,2) 0.12
为什么在这里四舍了?
还有更奇怪的,另一个在计算机里面能够精确表示的小数0.375,我们来看看精确到小数点后两位是多少:
>>>round(0.375,2) 0.38
为什么在这里又五入了?
解析
因为在Python3里面,round对小数的精确度采用了四舍六入五成双的方式。
如果你写过大学物理的实验报告,那么你应该会记得老师讲过,直接使用四舍五入,最后的结果可能会偏高,所以需要使用奇进偶舍的处理方法。
例如对于一个浮点数a.bcd,需要精确到小数点后两位,那么就要看小数点后第三位:
- 如果d小于5,直接舍去;
- 如果d大于5,直接进位;
- 如果d等于5:
d后面没有数据,且c为偶数,那么不进位,保留c
d后面没有数据,且c为奇数,那么进位,c变成(c+1) - 如果d后面还有非0数字,例如实际上小数为a.bcdef,此时一定要进位,c变成(c+1)
关于奇进偶舍,有兴趣的朋友可以在维基百科搜索这两个词条:数值修约和奇进偶舍。
所以,round给出的结果如果跟设想的不一样,那么需要考虑两个原因:
你的这个小数在计算机中能不能被精确储存?如果不能,那么它可能并没有达到四舍五入的标准,例如1.115,它的小数点后第三位实际上是4,当然会被舍去。
如果你的这个小数在计算机中能被精确表示,那么,round采用的进位机制是奇进偶舍,所以这取决于你要保留的那一位,它是奇数还是偶数,以及它的下一位后面还有没有数据。
关于奇进偶舍,有兴趣的朋友可以在搜索这两个词条:数值修约和奇进偶舍。
回到最开始的问题,对于6.6045这个浮点数,我们在Scheme中查看一下它的精确形式:
>(exact6.6045) 3718002967371055/562949953421312
也就是说它是不能被精确储存的,大概表现为6.60449999999999…的形式,因此四舍五入的时候得到了6.604。
如何正确进行四舍五入
如果要实现数学上的四舍五入,那么就需要使用decimal模块,具体用法参考官方文档:https://docs.python.org/zh-cn...。
其中quantize的函数原型和文档说明,提到了可以通过指定rounding参数来确定进位方式。如果没有指定rounding参数,那么会默认使用上下文提供的进位方式。
现在我们来查看一下默认的上下文中的进位方式是什么:
>>>fromdecimalimportgetcontext >>>getcontext().rounding 'ROUND_HALF_EVEN'
ROUND_HALF_EVEN实际上就是奇进偶舍,如果要指定真正的四舍五入,那么我们需要在quantize中指定进位方式为ROUND_HALF_UP:
>>>fromdecimalimportDecimal,ROUND_HALF_UP >>>Decimal('0.125').quantize(Decimal('0.00'),rounding=ROUND_HALF_UP) Decimal('0.13')
现在看起来一切都正常了。
有人可能会进一步追问一下,如果Decimal接收的参数不是字符串,而是浮点数会怎么样呢?
来实验一下:
>>>Decimal(0.125) Decimal('0.125')
那是不是说明,在Decimal的第一个参数,可以直接传浮点数呢?
我们换一个数来测试一下:
>>>Decimal(11.245) Decimal('11.2449999999999992184029906638897955417633056640625')
浮点数11.245和字符串'11.245'传进去以后的结果居然不一样。
我们继续在文档中寻找答案:
官方文档已经很清楚地说明了,如果你传入的参数为浮点数,并且这个浮点值在计算机里面不能被精确存储,那么它会先被转换为一个不精确的二进制值,然后再把这个不精确的二进制值转换为等效的十进制值。对于不能精确表示的小数,当你传入的时候,Python在拿到这个数前,这个数就已经被转成了一个不精确的数了。所以虽然参数传入的是11.245,但是Python拿到的实际上是11.24499999999…
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。