本文共 1982 字,大约阅读时间需要 6 分钟。
本文由的一篇帖子“”引出,帖子也是我发的,在查看回复的时候学到了不少内容,有了一点感悟,所以就想总结一下。
首先声明本文选用的编程语言为ruby,运行环境是ubuntu。
在编写财务,电子商务之类应用的时候,经常会碰到小数,小数乘、除、加、减的场景。
大多数语言表示小数,都有单精度float,双精度double,还有一个更加精确的decimal,无论是哪一种,都统称为浮点数。
浮点数是一个近似的数值,不是精确的数值。至于为什么不精确,这个涉及到操作系统的底层,和二进制的保存有关。关于浮点数,以及浮点数计算产生精度损失的内容可以参看下面几篇文章。
浮点数的四则运算都会有精度损失问题,会导致浮点数的逻辑运算结果超出日常生活的认识。
常见问题:
还有就是d * g / g 不一定等于d, d / g * g也不一定等于d。
这就造成,在很多时候,不能使用==来直接比较两个浮点数,因为浮点数不是精确数值,而是一个近似值。
怎么办呢?
既然是近似值,就是说他们两个非常接近,差距也就是0.00000000001之类的,反正很小。我们可以利用这个特性,写一个我们的两个浮点数比较的函数。只要我们确认两个浮点数之间的差距小到一个我们可以接受的值,就认为这两个浮点数是相等的。比如我们定义只要小于0.000000001,就算这两个浮点数是相等的,就可以写出下面的代码。
还有一个就是在中提到的一个方案,整数比较法。什么是整数比较法呢?就是将比较的双方都换算成整数,准确的说,就是双方都放大10倍,或者100倍,或者1000倍,反正就是放大同样的倍数,保证双方都是整数,这时候再来计算,再来比较。
为什么呢?因为整数的保存不存在精度损失问题,整数的四则运算不存在精度损失问题,所以计算结果的比较就可以是正确的,可以直接用==来比较了,
在ruby中还可以使用BigDecimal来比较浮点数,或者进行浮点数的四则运算,也可以避免精度损失导致的问题。
引出的设计问题
在电商应用中,假设我们出售的货物有各种重量单位:吨,公斤,千克,克。商品的包装有各种规格:1000.26元/吨,298.45元/公斤,357.84元/千克,3.56元/克。
消费者如果买点公斤包装的,如果你还允许论两买的话,也就是可能出现0.15公斤(3两),然后乘以298.45这种单价,会产生四位小数。
反正只要是小数,四则运算,就会有开头提到的精度损失问题,这里我们涉及的是电商,都是真实的金额往来,需要额外小心,需要消除精度损失带来的问题。
可以用我们上面提到的几种办法,绝对值,转整数,bigdecimal,来帮助我们更好的处理小数的运算。
其实这里面还隐藏者一个设计问题,就是后台的结算单位问题。
我们使用的结算单位没有统一,而是依据商品的包装重量,但是一次购买如果包含多种包装规格,浮点数的精度损失就会比较明显,会很明显的干扰到我们的结果。
有一种办法,就是统一我们的结算单位。也就是分离商品的显示重量单位和我们的结算使用重量单位,也算是一种职责分离吧。
在后台计算,我们一律统一到最低的重量单位,甚至更低,保证我们的计算过程都只有整数参与,然后在合算最终金额的时候,再转换回去。
比如说我们统一到毫克,标价1.23元/克,就相当于1230元/毫克,消费者购买15克,相当于购买15000毫克。总价就是
(1230*15000)/(1000*1000).round(2)
如果只是中间过程,我们就不需要round,直接用整数比较,就可以消除精度损失对于浮点数比较产生的问题。
结算单位的选择,放大的倍数,需要连在一起考虑,根据项目的不同而不同,甚至根据场景的不同而不同。
就是别忘了,在最终显示金额,或者财务处理的某些部分,需要把结果再缩小回来,放大多少倍,缩小多少倍。
本文转自 virusswb 51CTO博客,原文链接:http://blog.51cto.com/virusswb/1081679,如需转载请自行联系原作者