社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

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

Crossin的编程教室 • 2 年前 • 576 次点击  

大家好,欢迎来到 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