解决JS浮点数运算结果错误导致的Bug

文章探讨了JavaScript中浮点数计算的精度问题,原因在于JavaScript底层使用64位浮点数表示,可能导致小数计算的误差。作者提供了使用decimal.js等类库提高精度的方法,并解释了浮点数的存储机制。
摘要由CSDN通过智能技术生成

最近在做项目的时候,涉及到产品价格的计算,经常会出现JS浮点数精度问题,这个问题,对于财务管理系统的开发者来说,是个非常严重的问题(涉及到钱相关的问题都是严重的问题),这里把相关的原因和问题的解决方案整理一下,也希望给各位提供一些参考。

当时遇到的问题如下图
在这里插入图片描述
这里的四个数都是浮点数,统一由后端传入,按照规则,应该请求后端计算数据,判断结果再返回前端,但是考虑到这个功能可能有很多客户再用,对后端响应要求较高,所以在前端计算金额,但是如此简陋处理还是第一次见,各位科可万不能学习!!

问题的原因在于

JavaScript 内部只有一种数字类型Number,也就是说,JavaScript
语言的底层根本没有整数,所有数字都是以IEEE-754标准格式64位浮点数形式储存,1与1.0是相同的。因为有些小数以二进制表示位数是无穷的。JavaScript会把超出53位之后的二进制舍弃,所以涉及小数的比较和运算要特别小心。

这里再介绍一下IEEE 754

IEEE二进制浮点数算术标准(IEEE
754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用。这个标准定义了表示浮点数的格式(包括负零-0)与反常值(denormal
number)),一些特殊数值(无穷(Inf)与非数值(NaN)),以及这些数值的“浮点数运算符”;它也指明了四种数值舍入规则和五种例外状况(包括例外发生的时机与处理方式)。

可能比较难以理解,没关系,举个例子

   // 加法
   0.1 + 0.2 = 0.30000000000000004
   0.1 + 0.7 = 0.7999999999999999
   0.2 + 0.4 = 0.6000000000000001
 
   // 减法
   0.3 - 0.2 = 0.09999999999999998
   1.5 - 1.2 = 0.30000000000000004
 
   // 乘法
   0.8 * 3 = 2.4000000000000004
   19.9 * 100 = 1989.9999999999998
 
   // 除法
   0.3 / 0.1 = 2.9999999999999996
   0.69 / 10 = 0.06899999999999999
 
   // 比较
   0.1 + 0.2 === 0.3 // false
   (0.3 - 0.2) === (0.2 - 0.1) // false

【1】首先,十进制的0.1和0.2会转换成二进制的,但是由于浮点数用二进制表示是无穷的

0.1——>0.0001 1001 1001 1001 …(1001循环)
0.2——>0.0011 0011 0011 0011 …(0011循环)

【2】IEEE754标准的64位双精度浮点数的小数部分最多支持53位二进制,多余的二进制数字被截断,所以两者相加之后的二进制之和是

0.0100110011001100110011001100110011001100110011001101

【3】将截断之后的二进制数字再转换为十进制,就成了0.30000000000000004,所以在计算时产生了误差

最后再说说解决方案
1、引用类库
Math.js
decimal.js
big.js

上面任何一种都可以
这个问题我是这样解决的,附上代码

import Decimal from "decimal.js"
 // 恢复因为js浮点计算缺失的精度
        const res = Decimal((this.model.receiptmoney === '' || this.model.receiptmoney === undefined || this.model.receiptmoney === null) ? 0 : this.model.receiptmoney).add(Decimal((this.model.projectreceiptmoney === '' || this.model.projectreceiptmoney === undefined || this.model.projectreceiptmoney === null) ? 0 : this.model.projectreceiptmoney)).sub(Decimal((this.model.oldreceiptmoney === '' || this.model.oldreceiptmoney === undefined || this.model.oldreceiptmoney === null) ? 0 : this.model.oldreceiptmoney)).sub(Decimal((this.model.projectcontractmoney === '' || this.model.projectcontractmoney === undefined || this.model.projectcontractmoney === null) ? 0 : this.model.projectcontractmoney))
        if (res > 0) {
          this.$tap.alert('马赛克内容!', 'warning')
          return false
        }

这里使用了先判断前端值是否为‘’,undefined,null,避免传入数据错误导致引用包函数报错,如果不是,则没有问题,传入正确数据即可,必须先再项目里引入decimal包即可

npm install  decimal.js

最会再讲讲浮点数的存储
JS的浮点数实现也是遵循IEEE 754标准,采用双精度存储(double precision),使用64位固定长度来表示,其中1位用来表示符号位,11位用来表示指数,52位表示尾数。如下图:
在这里插入图片描述

  • 符号位(sign):第1位是正负数符号位,0代表正数,1代表负数
  • 指数位(Exponent):中间11位存储指数,用来表示次方数
  • 尾数位(mantissa):最后的52位是尾数,超出部分自动进一舍零

好了,今天的问题就解决到这里吧,该回家了,下次见

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值