Py学习  »  Python

在Python里想要四舍五入有多麻烦?

Crossin的编程教室 • 1 月前 • 30 次点击  

大家好,欢迎来到 Crossin的编程教室~

四舍五入」是小学就学过的数学知识,也是日常计算中经常会用到的处理方法。

然而让人没想到的是,一个简单的四舍五入操作,在Python里居然这么难搞,网上还一堆错误的教程。

来看这个例子,有一个变量a为1.135,现在希望把它保留2位小数,要怎么做?

网上搜索一下,找到两种方法:

第1种,round函数。第一个参数是原数字,第二个参数是要保留的小数位数

round(a, 2)

结果 1.14,没有问题。

第2种,通过格式说明符.f对浮点数进行字符串格式化,f前加上要保留的小数位数。

这种写法在 %格式化、format方法和f-string 上均适用。

print('%.2f' % a, '{:.2f}'.format(a), f'{a:.2f}')

果 1.14,也没有问题。

所以看来,以上两种方法都可以实现四舍五入地保留小数位数……

but,真的是这样吗?

显然事情没这么简单。如果把a的值改成1.125,再跑一下之前的代码,就发现两种方法都不对了。

别小看这么一点误差,我曾经在做助教时,就因为类似的原因,导致一位原本算好被60分放过的同学挂了科。

有些不靠谱的半瓶水教程会跟你说,这是因为Python用了种叫做「四舍六入五成双」的保留机制:5前面的数字是奇数就进位,是偶数就保持不变,所以1.135会得到1.14,而1.125就是1.12。

还有的教程告诉你有种方法可以实现四舍五入:就是把要保留N位的小数,乘以10的N次方,加上0.5后取整,再除以10的N次方

int(a * 10 ** 2 + 0.5) / 10 ** 2)

结果确实是 1.13。

但你以为这样就可以了吗?

如果再把a改成1.035。结果既没有什么所谓的奇数进位,也没能通过先乘再除的方法实现四舍五入。

把1.005,1.015,1.025,一直到1.995,用前面提到过的3种方法保留2位小数的结果输出出来就会发现。round和字符串格式化得到的保留结果是一样的,且基本没有规律可言。

而先乘后除法虽然在大部分情况下是符合四舍五入的,但仍然有一些例外的情况。

导致这种现象的原因,和之前讲解过的 0.1 + 0.2 != 0.3 一样,都是因为浮点数的精度造成的。

让这些小数输出更多位数,就会看到,很多值虽然结尾是5,但在计算机中以二进制存储的实际值其实不到5。那么按照四舍五入来说,当然是要被舍去了。

真正可以做到对小数保留位数进行精确控制的方法是使用 Python 内置的 decimal 模块,它用于高精度的十进制算术运算。

用 round 函数对于 Decimal 类型对象进行保留,才是真正的四舍六入五成双。

from decimal import Decimalx = 1.035print(round(Decimal(str(x)), 2))

这种机制又被称作「银行家舍入」,它其实比四舍五入更合理。因为5是两个数的中间值,全都进位会让数据在整体分布上偏大,而银行家舍入规则可以让累积误差趋向于0。

如果你就是想要按照四舍五入来保留,也可以,通过将 Context 里的 rounding 属性设置为 ROUND_HALF_UP 就可以

from decimal import Decimal, ROUND_HALF_UP, getcontextx = 1.045getcontext().rounding = ROUND_HALF_UPprint(round(Decimal(str(x)), 2))

另一种写法是通过 Decimal 的 quantize 方法,指定保留位数和舍入规则,效果是一样的。

from decimal import Decimalx = 1.045print(Decimal(str(x)).quantize(Decimal('0.01'), rounding='ROUND_HALF_UP'))

这样,就能完美地按四舍五入保留小数了。

不过这里还有一个小小的坑,就是一定要通过字符串去创建 Decimal 对象,否则实际值仍然是带有误差的,从而导致四舍五入失效。

好吧,没想到一个简单的四舍五入操作,竟然还这么复杂,你学会了吗?

作者:Crossin的编程教室


Crossin的新书《码上行动:用ChatGPT学会Python编程》已经上市了。本书以ChatGPT为辅助,系统全面地讲解了如何掌握Python编程,适合Python零基础入门的读者学习。【点此查看详细介绍】
购买后可加入读者交流群,Crossin为你开启陪读模式,解答你在阅读本书时的一切疑问。

添加微信 crossin123 ,加入编程教室共同学习~

感谢转发点赞的各位~

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/168344
 
30 次点击