由浮点数的精度问题引出设计问题

本文由ruby-china的一篇帖子“由小数的精度问题引出设计问题”引出,帖子也是我发的,在查看回复的时候学到了不少内容,有了一点感悟,所以就想总结一下。

 

首先声明本文选用的编程语言为ruby,运行环境是ubuntu。

 

在编写财务,电子商务之类应用的时候,经常会碰到小数,小数乘、除、加、减的场景。

大多数语言表示小数,都有单精度float,双精度double,还有一个更加精确的decimal,无论是哪一种,都统称为浮点数。

 

浮点数是一个近似的数值,不是精确的数值。至于为什么不精确,这个涉及到操作系统的底层,和二进制的保存有关。关于浮点数,以及浮点数计算产生精度损失的内容可以参看下面几篇文章。

 

 

浮点数

定点数与浮点数的区别

避免对C#中float,double,decimal的错误理解

浮点数操作精度损失

浮点数的比较

 

浮点数的四则运算都会有精度损失问题,会导致浮点数的逻辑运算结果超出日常生活的认识。

常见问题:

 

 
 
  1. irb(main):005:0> 1.3-1.0 == 0.3 
  2. => false 
颠覆了我们生活中的常识,因为在计算机中1.3-1.0的结果是 0.30000000000000004。

 

还有就是d * g / g 不一定等于d, d / g * g也不一定等于d。

这就造成,在很多时候,不能使用==来直接比较两个浮点数,因为浮点数不是精确数值,而是一个近似值。

 

怎么办呢?

既然是近似值,就是说他们两个非常接近,差距也就是0.00000000001之类的,反正很小。我们可以利用这个特性,写一个我们的两个浮点数比较的函数。只要我们确认两个浮点数之间的差距小到一个我们可以接受的值,就认为这两个浮点数是相等的。比如我们定义只要小于0.000000001,就算这两个浮点数是相等的,就可以写出下面的代码。

 
 
  1. module FloatEqual 
  2.   def equal(b) 
  3.     return self==b || (self-b).abs < 0.000000001 
  4.   end 
  5. end 
  6.  
  7. (1.3-1.0).extend(FloatEqual).equal(0.3) # true 

 

还有一个就是Saito帖子中提到的一个方案,整数比较法。什么是整数比较法呢?就是将比较的双方都换算成整数,准确的说,就是双方都放大10倍,或者100倍,或者1000倍,反正就是放大同样的倍数,保证双方都是整数,这时候再来计算,再来比较。

为什么呢?因为整数的保存不存在精度损失问题,整数的四则运算不存在精度损失问题,所以计算结果的比较就可以是正确的,可以直接用==来比较了,

 

 
 
  1. (1.3*10 - 1.0*10) == 0.3*10 # true 

 

在ruby中还可以使用BigDecimal来比较浮点数,或者进行浮点数的四则运算,也可以避免精度损失导致的问题。

 
 
  1. BigDecimal.new("1.3")-BigDecimal.new("1")==BigDecimal.new("0.3"# true 

 

引出的设计问题

在电商应用中,假设我们出售的货物有各种重量单位:吨,公斤,千克,克。商品的包装有各种规格: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,如需转载请自行联系原作者

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值