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

简介:

本文由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,如需转载请自行联系原作者

目录
相关文章
|
测试技术
读完这篇文章后,才发现Allpairs这款工具,让测试用例变得如此简单
读完这篇文章后,才发现Allpairs这款工具,让测试用例变得如此简单
824 0
|
编解码 自然语言处理 算法
开源版图生视频I2VGen-XL:单张图片生成高质量视频
VGen是由阿里巴巴通义实验室开发的开源视频生成模型和代码系列,具备非常先进和完善的视频生成系列能力
|
存储 弹性计算 固态存储
阿里云服务器ECS共享型和独享区别选择(看这一篇就够了)
阿里云服务器ECS共享型和计算型、通用型有什么区别?什么是共享型云服务器?什么是独享型云服务器?性能区别大吗?如何选择?
3743 0
阿里云服务器ECS共享型和独享区别选择(看这一篇就够了)
|
人工智能 C++ iOS开发
ollama + qwen2.5-coder + VS Code + Continue 实现本地AI 辅助写代码
本文介绍在Apple M4 MacOS环境下搭建Ollama和qwen2.5-coder模型的过程。首先通过官网或Brew安装Ollama,然后下载qwen2.5-coder模型,可通过终端命令`ollama run qwen2.5-coder`启动模型进行测试。最后,在VS Code中安装Continue插件,并配置qwen2.5-coder模型用于代码开发辅助。
20206 71
|
安全 网络安全 算法框架/工具
SSH高版本连接问题排查
【6月更文挑战第21天】SSH高版本连接问题排查
1130 0
|
小程序 前端开发 算法
前端(十六)——微信小程序语音转文字,文字转语音功能的实现
前端(十六)——微信小程序语音转文字,文字转语音功能的实现
2423 0
|
PHP
PHP日期时间运用十一:三种方法比较两个指定的日期
在之前的文章《PHP日期时间运用十:将秒数转成格式为“天时分秒”》中给大家介绍了怎么将秒数转成格式为“天时分秒”,那么本文继续开始PHP日期时间系列的文章~ 正如标题所述,本文主要内容是给大家介绍比较两个指定日期的三种方法! 假设我们给定两个日期(date1 和 date2),当两个日期的格式相同时,在 PHP 中比较这两个日期是很简单的,但是当两个日期的格式不同时就会出现一些问题。
613 0
|
Linux 网络安全 开发工具
一个固定 WSL2 ip 的简单方法
本文介绍了如何在Win11 22H2及以上版本中让WSL与Windows共享IP,避免重启后IP变化带来的问题。只需在用户目录下创建`.wslconfig`文件,输入特定配置并重启WSL,即可实现IP一致,简化WSL网络设置。此外,还提供了一种在其他系统版本中通过Windows SSH连接WSL的替代方法。
4265 0
|
虚拟化 Docker 容器
Minikube - Kubernetes本地实验环境
为了方便大家本地开发和体验Kubernetes,社区提供了可以在本机部署的Minikube。本文介绍利用阿里云的镜像地址在Windows/Mac/Linux上来部署和配置Minikube
241019 71
Minikube - Kubernetes本地实验环境
|
Java Python Windows
Python pip 源设置成国内源,阿里云源,清华大学源,最方便的方式,都在这里了
Python pip 源设置成国内源,阿里云源,清华大学源,最方便的方式,都在这里了
80101 1