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

前言

最近在做项目的时候,涉及到产品价格的计算,经常会出现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】引用类库

【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);
  };

总结

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

(0)

相关推荐

  • javascript将浮点数转换成整数的三个方法

    Summary 暂时我就想到3个方法而已.如果读者想到其他好用方法,也可以交流一下 parseInt 位运算符 Math.floor Math.ceil Description 一.parseInt 1. 实例 parseInt("13nash");//13 parseInt("")// NaN parseInt("0xA") //10(十六进制) parseInt(" 13")//13 parseInt("070&

  • JavaScript 浮点数运算 精度问题

    Js代码 复制代码 代码如下: <script type="text/javascript" language="javascript"> alert(1/3);//弹出: 0.3333333333333333 alert(0.09999999 + 0.00000001);//弹出: 0.09999999999999999 alert(-0.09999999 - 0.00000001);//弹出: -0.09999999999999999 alert(0

  • 解决JS浮点数运算出现Bug的方法

    37.5*5.5=206.08 (JS算出来是这样的一个结果,我四舍五入取两位小数) 我先怀疑是四舍五入的问题,就直接用JS算了一个结果为:206.08499999999998 怎么会这样,两个只有一位小数的数字相乘,怎么可能多出这么小数点出来. 我Google了一下,发现原来这是JavaScript浮点运算的一个bug. 比如:7*0.8 JavaScript算出来就是:5.6000000000000005 网上找到了一些解决办法,就是重新写了一些浮点运算的函数或直接扩大倍数运算. 下面就把这

  • 深入理解JavaScript中的浮点数

    js只有一种数值型数据类型,不管是整数还是浮点数,js都把归为数字. typeof 17;   // "number" typeof 98.6; // "number" typeof –2.1; // "number" js中的所有数字都是双精度浮点数.是由IEEE754标准制定的64位编码数字(这个是什么东东,不知道,回头查一下吧) 那么js是如何表达整数的,双精度浮点数可以完美地表示高达53位精度的整数(没有什么概念,没处理过多大的数据,没用

  • js浮点数精确计算(加、减、乘、除)

    复制代码 代码如下: <SPAN style="FONT-SIZE: 18px">//说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显.这个函数返回较为精确的加法结果.   //调用:accAdd(arg1,arg2)   //返回值:arg1加上arg2的精确结果   function accAdd(arg1,arg2){      var r1,r2,m;      try{r1=arg1.toString().split(".&

  • js浮点数保留两位小数点示例代码(四舍五入)

    复制代码 代码如下: var changeTwoDecimal_f= function (floatvar){      var f_x = parseFloat(floatvar);      if (isNaN(f_x)){          return '0.00';      }      var f_x = Math.round(f_x*100)/100;      var s_x = f_x.toString();      var pos_decimal = s_x.indexO

  • JS判断是否为数字,是否为整数,是否为浮点数的代码

    正则表达式方法 复制代码 代码如下: function checkRate(input) { var re = /^[0-9]+.?[0-9]*$/; //判断字符串是否为数字 //判断正整数 /^[1-9]+[0-9]*]*$/ if (!re.test(input.rate.value)) { alert("请输入数字(例:0.02)"); input.rate.focus(); return false; } } 下面为普通函数写法 复制代码 代码如下: function BAS

  • js判断输入是否为正整数、浮点数等数字的函数代码

    1.下面列出了一些判读数值类型的正则表达式 复制代码 代码如下: /^\d+$/ //非负整数(正整数 + 0) /^[0-9]*[1-9][0-9]*$/ //正整数 /^((-\d+)|(0+))$/ //非正整数(负整数 + 0) /^-[0-9]*[1-9][0-9]*$/ //负整数 /^-?\d+$/ //整数 /^\d+(\.\d+)?$/ //非负浮点数(正浮点数 + 0) /^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0

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

    前言 最近在做项目的时候,涉及到产品价格的计算,经常会出现JS浮点数精度问题,这个问题,对于财务管理系统的开发者来说,是个非常严重的问题(涉及到钱相关的问题都是严重的问题),这里把相关的原因和问题的解决方案整理一下,也希望给各位提供一些参考. 一. 常见例子   // 加法 0.1 + 0.2 = 0.30000000000000004 0.1 + 0.7 = 0.7999999999999999 0.2 + 0.4 = 0.6000000000000001 // 减法 0.3 - 0.2 =

  • js小数运算出现多位小数如何解决

    和大家分享一个有趣的测试: 0.1+0.2 == 0.3 //false 顿时郁闷,好吧!原来0.1+0.2变成:0.30000000000000004 再来一个 2.4/0.8 =>2.9999999999999996 没办法换种方式,都转换成整数 (2.4 * 100)/(0.8 * 100) 10.22 现在要减去 0.11 结果值又出现了很多的小数 10.110000000000001,然后我就用了 toFixed 方法来过滤小数,但是不知道跟前面那种转换成整数后再执行哪种效率高,好!还

  • JS小数运算出现多为小数问题的解决方法

    写在前面的话: 今天帮同事解决了一个问题,就是小数相乘出现很多位小数的问题:这个问题自己以前也遇到过,现在特意来总结一下: Number类型: Number类型是ECMAScript中最常用和最令人关注的类型了:这种类型使用IEEE754格式来表示整数和浮点数值(浮点数值在某些语言中也被成为双精度数值),为支持各种数据类型,ECMA-262定义了不同的数值面量格式. 十进制: var intNum=10; //整数 八进制: var octalNum1=070; //八进制的56 var oct

  • Python如何执行精确的浮点数运算

    问题 你需要对浮点数执行精确的计算操作,并且不希望有任何小误差的出现. 解决方案 浮点数的一个普遍问题是它们并不能精确的表示十进制数. 并且,即使是最简单的数学运算也会产生小的误差,比如: >>> a = 4.2 >>> b = 2.1 >>> a + b 6.300000000000001 >>> (a + b) == 6.3 False >>> 这些错误是由底层CPU和IEEE 754标准通过自己的浮点单位去执行

  • 详解JavaScript 浮点数运算的精度问题

    问题描述 在 JavaScript 中整数和浮点数都属于 Number 数据类型,所有数字都是以 64 位浮点数形式储存,即便整数也是如此. 所以我们在打印 1.00 这样的浮点数的结果是 1 而非 1.00 .在一些特殊的数值表示中,例如金额,这样看上去有点变扭,但是至少值是正确了.然而要命的是,当浮点数做数学运算的时候,你经常会发现一些问题,举几个例子: // 加法 ===================== // 0.1 + 0.2 = 0.30000000000000004 // 0.7

  • 聊聊Python中的浮点数运算不准确问题

    大家好,老 Amy 来了.之前就意识到一个问题,但是最近又有朋友提出来了,所以就想着干脆记录下来,分享给大家叭~ 啥问题呢?请看题: 也就是说,需要大家计算1.1-1的值,很多朋友会说:"emmm-这还不简单,玩我呢?不就是0.1嘛" 但是如果你用 python 去执行一下,会发现结果跟你想的不太一样,如下图: 这样大家是不是发现了什么问题?是的,浮点数在运算过程中并没有保证完全精确,是什么原因导致了这种现象呢?很多朋友就会窃喜:"这不就是 Python 的 bug 嘛~&q

  • 一个不错的js html页面倒计时可精确到秒

    一个不错的js html页面倒计时可精确到秒,很简单,但和实用 <!doctype html> <html> <head> <meta charset="utf-8"> </head> <body> <DIV id="CountMsg" class="HotDate"> <span id="t_d">00天</span>

  • php中让人头疼的浮点数运算分析

    本文实例分析了php中让人头疼的浮点数运算.分享给大家供大家参考,具体如下: 在做电商的时候,计算价格是免不了的,然后发现了php的一个坑,口算应该正确的值,php运算出来会跟你不一样 请看下面的代码: $price=69.1; $count=100; $total=$price*$count-6910; echo $total; 你猜一下变量$total的值是多少,运行一下这个代码输出:-9.09494701773E-13 怎么解决这个问题呢? 使用round函数 代码修改成: $price=

随机推荐