说起python中的四舍五入的问题,可能还有部分童鞋不太清楚,会说四舍五入有啥可说道的,round 大法走天下。如果你在金融,电商等相关领域有过开发经验的话,可能对我说的这个问题,有过深刻的理解,毕竟这个问题折磨的可不止python从业者。
假设有这么一个场景, 用户在电商网站挑选商品,根据商品价格 (price),购买数量 (quantity),
计算用户应付金额 (pay)
你会怎么做呢。
price * quantity = pay
这个公式相信大家都会的,但是如果你直接把得出的pay 不加处理的话,肯定是会有问题的。
比如price = 1.25 quantity = 1.3 那么应付的钱数为 1.25 * 1.3 = 1.625
但是由于钱的最小单位是 分, 所以小数点后两位为有效数字。即1.63 才是我们想要的数字。
那么1.625 怎么正确的变为1.63 就是今天要和大家讨论的话题。
方案一: round
大家都知道python 中的round 是专门用来四舍五入的,第一时间都相到了用round.
于是, 但是大家通过round计算发现 round(1.625, 2) = 1.62
![510f4851d859bad9a7346e158cb88d2e.png](https://i-blog.csdnimg.cn/blog_migrate/e7b70118c17f2053385a413ca7731a2a.jpeg)
这是什么原因呢。有人说 round 遵循 四舍六入五平分,啥意思呢,也就是如果最后
一位是5的情况下,是进一位还是舍弃这两种情况五五开。其实也不难理解计算机是二进制
的,有些浮点数在计算机中不能被正确的表达,它可能是一个近似值,所以就出现了这种情况。
所以说round不太适合做精确的四舍五入。
估计会有童鞋抬杠,说不是还有一种方法吗,将数值放大100倍
就可以实现保留2位小数的精确四舍五入。咱也不废话,直接上图。
![fd10fa336038f517dc88094ba6bbcfb0.png](https://i-blog.csdnimg.cn/blog_migrate/8fff9a74471aa0b21fb006c9092e7682.jpeg)
1.115 通过round普通方法,缺失会出现四舍五入的问题。但是 通过数值放大100倍处理
我们发现,缺失能正确的四舍五入了。但是通过这种方法计算本例中的1.625, 会发现依然
没有奏效。
也不晓得是不是还要更为有效的办法,能够解决这个问题
反正自从我知道这个问题,并尝试改进round无法奏效后就放弃了round
方案二: decimal
这是网上呼声最高的解决方案了。我们先来看下效果。
![dc2d1067c2ecd9cf3881ee89ff9d01aa.png](https://i-blog.csdnimg.cn/blog_migrate/8db27f7125912814837ccb440e2a66d8.jpeg)
一切似乎是对的。但是在作者使用过程中,发现依然会产生该进位的没进位的情况。
![5678c07ee822842e08e5e3f9ca2ec097.png](https://i-blog.csdnimg.cn/blog_migrate/c8e6b94a11357558bc221a9b7c243218.jpeg)
似乎没比round高级到哪里去呢。带着疑问我们去网上找资料,但是网上一堆博客文章
都在讲decimal是专门用来高精度计算的,如何如何。既然吹的牛皮震天响,那么怎么解决
这个问题呢。
![e88e9fd3114f253a0e74df86c493322e.png](https://i-blog.csdnimg.cn/blog_migrate/f5ac7ef8596854dd83e85bc7979131aa.jpeg)
我们观察到上图有这么一句话
getcontext().rounding = "ROUND_HALF_UP"
这句话的作用就是设置decimal的舍入方式为真正的四舍五入的。
那么默认的舍入方式是什么呢
![922296fdd21c79a80ecda15bef8b86f4.png](https://i-blog.csdnimg.cn/blog_migrate/49f96b38384b11b2f01a3d6cae69b81b.jpeg)
我们发现默认的舍入方式 是ROUND_HALF_EVEN, 大概也能猜出来,即前面猜的五平分的意思
那么rounding 还有那些值呢。
ROUND_HALF_DOWN:5,则做进位操作;否则舍弃;
ROUND_HALF_UP:末位大于等于5,则做进位操作;否则舍弃;
ROUND_HALF_EVEN:默认情况看下图,摘抄互联网。
![f52ed8f50c3e42f5d07de9031bfea333.png](https://i-blog.csdnimg.cn/blog_migrate/09be1e30758832e4b51f870d42ad5083.jpeg)
还有一些其它值,感兴趣的可以看下官方文档
https://docs.python.org/zh-cn/3/library/decimal.html
看到有人说通过decimal还可以设置精度。
某博客是这样说的
![31d8a22ce3ba96c3d6429199483d3efb.png](https://i-blog.csdnimg.cn/blog_migrate/3f88bbe9ac9f111eac4d5a82d7ffb836.jpeg)
getcontext().prec = 2
按照这样的理解,这样就保留小数点后两位,事实真的如此吗。
![4300ceecd82f7c802bbfefb02339b645.png](https://i-blog.csdnimg.cn/blog_migrate/d3cf410f8230db716d4e9e2336789fd3.jpeg)
貌似不起作用嘛,那么这个prec该怎么理解才是正确的呢
我们注意到官方文档有这么一段
![f51be0d2c48a432ed554724558a5a629.png](https://i-blog.csdnimg.cn/blog_migrate/7a94f3f036feeb8efe98604e00c2d91f.jpeg)
看到了吗,该属性,尽在运算的时候才发挥作用,而且也不是小数点后的精度,
它表示的是几位有效数字。
由此看来 decimal 不愧是用做精确运算的。
作者之前虽然知道decimal, 但是在解决问题中,遇到四舍五入的问题避开了,
手动写了个处理方法,与这种方式相比麻烦许多,但是程序经过节假日大促的考验,
倒也没出过错,大概思路就是把小数转化为字符串,然后把小数点前后数字拆开,通过
整数运算后,在拼成字符串,然后转换成float 或其它想要的类型。再次就不献丑了。
如果有更好的更简单的方案,欢迎大家下发留言讨论。