博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
由浮点数的精度问题引出设计问题
阅读量:6530 次
发布时间:2019-06-24

本文共 1982 字,大约阅读时间需要 6 分钟。

本文由的一篇帖子“”引出,帖子也是我发的,在查看回复的时候学到了不少内容,有了一点感悟,所以就想总结一下。

 

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

 

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

大多数语言表示小数,都有单精度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 

 

还有一个就是在中提到的一个方案,整数比较法。什么是整数比较法呢?就是将比较的双方都换算成整数,准确的说,就是双方都放大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,如需转载请自行联系原作者

你可能感兴趣的文章
线上 ELK 集群健康值 red 状态问题排查与解决
查看>>
4:自适应高度ViewPager
查看>>
PHP中exec()函数执行系统命令失败
查看>>
WeexBox 给你最好的图片加载方式
查看>>
单一职责原则
查看>>
CSS中filter属性的使用
查看>>
Mysql读写分离与主从数据库设置方案
查看>>
phpstorm + xdebug 调试教程 -- 总纲
查看>>
Python实用技法第3篇:找到最大或最小的N个元素
查看>>
vue-avatar-tailor,vue头像裁剪组件
查看>>
我们知道CDN护航了双11十年,却不知道背后有那么多故事……
查看>>
python大佬养成计划--面向对象
查看>>
go run main.go undefined? golang main包那点事
查看>>
一个破碎的人,窃机浪漫飞行后自由坠毁
查看>>
前端进阶(13) - 搭建自己的前端脚手架
查看>>
数据挖掘(二):认识数据
查看>>
从零开始写一个npm包,一键生成react组件(偷懒==提高效率)
查看>>
mybatis-plus 不覆盖service,controller, 生成模板使用swagger注解
查看>>
js bom location对象
查看>>
Golang中的路由
查看>>