侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

JS浮点数运算结果不精确的Bug解决

前端  /  管理员 发布于 4年前   393

前言

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

一. 常见例子  

 // 加法 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

二. 导致原因

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

三. IEEE二进制浮点数算术标准(IEEE 754)

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

四. 浮点数的存储

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

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

五. 浮点数的计算步骤(0.1+0.2)

【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

【2】思路一:在知道小数位个数的前提下,可以考虑通过将浮点数放大倍数到整型(最后再除以相应倍数),再进行运算操作,这样就能得到正确的结果了  

0.1 + 0.2 ――> (0.1 * 10 + 0.2 * 10) / 10 // 0.3
0.8 * 3 ――> ( 0.8 * 100 * 3) / 100         //2.4

【3】自定义一个转换和处理函数 

 // f代表需要计算的表达式,digit代表小数位数 Math.formatFloat = function (f, digit) {  // Math.pow(指数,幂指数)  var m = Math.pow(10, digit);  // Math.round() 四舍五入  return Math.round(f * m, 10) / m; } console.log(Math.formatFloat(0.3 * 8, 1)); // 2.4 console.log(Math.formatFloat(0.35 * 8, 2)); // 2.8

【4】加法函数  

 /**  ** 加法函数,用来得到精确的加法结果  ** 说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。  ** 调用:accAdd(arg1,arg2)  ** 返回值:arg1加上arg2的精确结果  **/ function accAdd(arg1, arg2) {  var r1, r2, m, c;  try {  r1 = arg1.toString().split(".")[1].length;  } catch (e) {  r1 = 0;  }  try {  r2 = arg2.toString().split(".")[1].length;  } catch (e) {  r2 = 0;  }  c = Math.abs(r1 - r2);  m = Math.pow(10, Math.max(r1, r2));  if (c > 0) {  var cm = Math.pow(10, c);  if (r1 > r2) {   arg1 = Number(arg1.toString().replace(".", ""));   arg2 = Number(arg2.toString().replace(".", "")) * cm;  } else {   arg1 = Number(arg1.toString().replace(".", "")) * cm;   arg2 = Number(arg2.toString().replace(".", ""));  }  } else {  arg1 = Number(arg1.toString().replace(".", ""));  arg2 = Number(arg2.toString().replace(".", ""));  }  return (arg1 + arg2) / m; } //给Number类型增加一个add方法,调用起来更加方便。 Number.prototype.add = function (arg) {  return accAdd(arg, this); };

【5】减法函数

 /**  ** 减法函数,用来得到精确的减法结果  ** 说明:javascript的减法结果会有误差,在两个浮点数相减的时候会比较明显。这个函数返回较为精确的减法结果。  ** 调用:accSub(arg1,arg2)  ** 返回值:arg1加上arg2的精确结果  **/ function accSub(arg1, arg2) {  var r1, r2, m, n;  try {  r1 = arg1.toString().split(".")[1].length;  } catch (e) {  r1 = 0;  }  try {  r2 = arg2.toString().split(".")[1].length;  } catch (e) {  r2 = 0;  }  m = Math.pow(10, Math.max(r1, r2)); //last modify by deeka //动态控制精度长度  n = (r1 >= r2) ? r1 : r2;  return ((arg1 * m - arg2 * m) / m).toFixed(n); } // 给Number类型增加一个mul方法,调用起来更加方便。 Number.prototype.sub = function (arg) {  return accMul(arg, this); };

【6】乘法函数

 /**  ** 乘法函数,用来得到精确的乘法结果  ** 说明:javascript的乘法结果会有误差,在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果。  ** 调用:accMul(arg1,arg2)  ** 返回值:arg1乘以 arg2的精确结果  **/ function accMul(arg1, arg2) {  var m = 0,  s1 = arg1.toString(),  s2 = arg2.toString();  try {  m += s1.split(".")[1].length;  } catch (e) {}  try {  m += s2.split(".")[1].length;  } catch (e) {}  return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m); } // 给Number类型增加一个mul方法,调用起来更加方便。 Number.prototype.mul = function (arg) {  return accMul(arg, this); };

【7】除法函数

  /**    ** 除法函数,用来得到精确的除法结果   ** 说明:javascript的除法结果会有误差,在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。   ** 调用:accDiv(arg1,arg2)   ** 返回值:arg1除以arg2的精确结果   **/  function accDiv(arg1, arg2) {   var t1 = 0,    t2 = 0,    r1, r2;   try {    t1 = arg1.toString().split(".")[1].length;   } catch (e) {}   try {    t2 = arg2.toString().split(".")[1].length;   } catch (e) {}   with(Math) {    r1 = Number(arg1.toString().replace(".", ""));    r2 = Number(arg2.toString().replace(".", ""));    return (r1 / r2) * pow(10, t2 - t1);   }  }  //给Number类型增加一个div方法,调用起来更加方便。  Number.prototype.div = function (arg) {   return accDiv(this, arg);  };

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家的支持。

您可能感兴趣的文章:

  • js浮点数精确计算(加、减、乘、除)
  • js判断输入是否为正整数、浮点数等数字的函数代码
  • js浮点数保留两位小数点示例代码(四舍五入)
  • JavaScript 浮点数运算 精度问题
  • 解决JS浮点数运算出现Bug的方法
  • JS判断是否为数字,是否为整数,是否为浮点数的代码
  • javascript将浮点数转换成整数的三个方法
  • 深入理解JavaScript中的浮点数


  • 上一条:
    vue项目中全局引入1个.scss文件的问题解决
    下一条:
    jQuery中DOM常见操作实例小结
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 使用 Alpine.js 排序插件对元素进行排序(0个评论)
    • 在js中使用jszip + file-saver实现批量下载OSS文件功能示例(0个评论)
    • 在vue中实现父页面按钮显示子组件中的el-dialog效果(0个评论)
    • 使用mock-server实现模拟接口对接流程步骤(0个评论)
    • vue项目打包程序实现把项目打包成一个exe可执行程序(0个评论)
    • 近期文章
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-06
    • 2017-07
    • 2017-08
    • 2017-09
    • 2017-10
    • 2017-11
    • 2018-03
    • 2018-04
    • 2018-05
    • 2018-06
    • 2018-09
    • 2018-11
    • 2018-12
    • 2019-02
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2021-04
    • 2021-05
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客