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


Posted in Javascript onAugust 01, 2019

前言

最近在做项目的时候,涉及到产品价格的计算,经常会出现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位表示尾数。如下图:

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

  • ​符号位(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);
  };

总结

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

Javascript 相关文章推荐
一个很酷的拖动层的js类,兼容IE及Firefox
Jun 23 Javascript
JQERY limittext 插件0.2版(长内容限制显示)
Aug 27 Javascript
jquery二级导航内容均分的原理及实现
Aug 13 Javascript
jQuery实现为图片添加镜头放大效果的方法
Jun 25 Javascript
springMVC结合AjaxForm上传文件
Jul 12 Javascript
JavaScript如何实现跨域请求
Aug 05 Javascript
基于javascript的Form表单验证
Dec 29 Javascript
JavaScript实现打地鼠小游戏
Apr 23 Javascript
vue2.0 循环遍历加载不同图片的方法
Mar 06 Javascript
vue vant Area组件使用详解
Dec 09 Javascript
写给新手同学的vuex快速上手指北小结
Apr 14 Javascript
微信小程序实现简单文字跑马灯
May 26 Javascript
微信小程序动画组件使用解析,类似vue,且更强大
Aug 01 #Javascript
vue项目中全局引入1个.scss文件的问题解决
Aug 01 #Javascript
jQuery中DOM操作原则实例分析
Aug 01 #jQuery
详解package.json版本号规则
Aug 01 #Javascript
ES6 新增的创建数组的方法(小结)
Aug 01 #Javascript
详解基于Wepy开发小程序插件(推荐)
Aug 01 #Javascript
深入浅析Vue中mixin和extend的区别和使用场景
Aug 01 #Javascript
You might like
咖啡店都有些什么常规豆子呢?有什么风味在里面
2021/03/04 咖啡文化
PHP 已经成熟
2006/12/04 PHP
destoon实现商铺管理主页设置增加新菜单的方法
2014/06/26 PHP
php使用ZipArchive提示Fatal error: Class ZipArchive not found in的解决方法
2014/11/04 PHP
Yii视图操作之自定义分页实现方法
2016/07/14 PHP
php生成毫秒时间戳的实例讲解
2017/09/22 PHP
Thinkphp5行为使用方法汇总
2017/12/21 PHP
jQuery 1.9移除了$.browser可以使用$.support来替代
2014/09/03 Javascript
AngularJS Ajax详解及示例代码
2016/08/17 Javascript
jQuery中fadein与fadeout方法用法示例
2016/09/16 Javascript
jQuery实现文字自动横移
2017/01/08 Javascript
vue2.0 elementUI制作面包屑导航栏
2018/02/22 Javascript
总结js函数相关知识点
2018/02/27 Javascript
使用webpack搭建react开发环境的方法
2018/05/15 Javascript
详解create-react-app 自定义 eslint 配置
2018/06/07 Javascript
vue + webpack如何绕过QQ音乐接口对host的验证详解
2018/07/01 Javascript
Vue-cli3简单使用(图文步骤)
2019/04/30 Javascript
用Python程序抓取网页的HTML信息的一个小实例
2015/05/02 Python
Java Web开发过程中登陆模块的验证码的实现方式总结
2016/05/25 Python
pycharm远程调试openstack的图文教程
2017/11/21 Python
深入flask之异步非堵塞实现代码示例
2018/07/31 Python
python lxml中etree的简单应用
2019/05/10 Python
bluepy 一款python封装的BLE利器简单介绍
2019/06/25 Python
服务器端jupyter notebook映射到本地浏览器的操作
2020/04/14 Python
python实现逢七拍腿小游戏的思路详解
2020/05/26 Python
idealfit英国:世界领先的女性健身用品和运动衣物品牌
2017/11/25 全球购物
Haggar官网:美国男装品牌
2020/02/16 全球购物
Java面向对象面试题
2016/12/26 面试题
国际经济贸易专业推荐信
2013/11/06 职场文书
心理学专业毕业生推荐信范文
2013/11/21 职场文书
教育学专业毕业生的自我评价
2013/11/21 职场文书
品质管理部岗位职责范文
2014/03/01 职场文书
总经理岗位职责说明书
2014/07/30 职场文书
汽车4S店销售经理岗位职责
2015/04/02 职场文书
话题作文之自信作文
2019/11/15 职场文书
html网页引入svg图片的4种方式
2022/08/05 HTML / CSS