真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

JS浮點(diǎn)數(shù)運(yùn)算結(jié)果不精確的Bug解決

前言

創(chuàng)新互聯(lián)"三網(wǎng)合一"的企業(yè)建站思路。企業(yè)可建設(shè)擁有電腦版、微信版、手機(jī)版的企業(yè)網(wǎng)站。實(shí)現(xiàn)跨屏營銷,產(chǎn)品發(fā)布一步更新,電腦網(wǎng)絡(luò)+移動(dòng)網(wǎng)絡(luò)一網(wǎng)打盡,滿足企業(yè)的營銷需求!創(chuàng)新互聯(lián)具備承接各種類型的網(wǎng)站設(shè)計(jì)制作、成都做網(wǎng)站項(xiàng)目的能力。經(jīng)過十年的努力的開拓,為不同行業(yè)的企事業(yè)單位提供了優(yōu)質(zhì)的服務(wù),并獲得了客戶的一致好評(píng)。

最近在做項(xiàng)目的時(shí)候,涉及到產(chǎn)品價(jià)格的計(jì)算,經(jīng)常會(huì)出現(xiàn)JS浮點(diǎn)數(shù)精度問題,這個(gè)問題,對于財(cái)務(wù)管理系統(tǒng)的開發(fā)者來說,是個(gè)非常嚴(yán)重的問題(涉及到錢相關(guān)的問題都是嚴(yán)重的問題),這里把相關(guān)的原因和問題的解決方案整理一下,也希望給各位提供一些參考。

一. 常見例子  

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

二. 導(dǎo)致原因

JavaScript 內(nèi)部只有一種數(shù)字類型Number,也就是說,JavaScript 語言的底層根本沒有整數(shù),所有數(shù)字都是以IEEE-754標(biāo)準(zhǔn)格式64位浮點(diǎn)數(shù)形式儲(chǔ)存,1與1.0是相同的。因?yàn)橛行┬?shù)以二進(jìn)制表示位數(shù)是無窮的。JavaScript會(huì)把超出53位之后的二進(jìn)制舍棄,所以涉及小數(shù)的比較和運(yùn)算要特別小心。

三. IEEE二進(jìn)制浮點(diǎn)數(shù)算術(shù)標(biāo)準(zhǔn)(IEEE 754)

IEEE二進(jìn)制浮點(diǎn)數(shù)算術(shù)標(biāo)準(zhǔn)(IEEE 754)是20世紀(jì)80年代以來最廣泛使用的浮點(diǎn)數(shù)運(yùn)算標(biāo)準(zhǔn),為許多CPU與浮點(diǎn)運(yùn)算器所采用。這個(gè)標(biāo)準(zhǔn)定義了表示浮點(diǎn)數(shù)的格式(包括負(fù)零-0)與反常值(denormal number)),一些特殊數(shù)值(無窮(Inf)與非數(shù)值(NaN)),以及這些數(shù)值的“浮點(diǎn)數(shù)運(yùn)算符”;它也指明了四種數(shù)值舍入規(guī)則和五種例外狀況(包括例外發(fā)生的時(shí)機(jī)與處理方式)。

四. 浮點(diǎn)數(shù)的存儲(chǔ)

JS的浮點(diǎn)數(shù)實(shí)現(xiàn)也是遵循IEEE 754標(biāo)準(zhǔn),采用雙精度存儲(chǔ)(double precision),使用64位固定長度來表示,其中1位用來表示符號(hào)位,11位用來表示指數(shù),52位表示尾數(shù)。如下圖:

JS浮點(diǎn)數(shù)運(yùn)算結(jié)果不精確的Bug解決

  • ​符號(hào)位(sign):第1位是正負(fù)數(shù)符號(hào)位,0代表正數(shù),1代表負(fù)數(shù)
  • 指數(shù)位(Exponent):中間11位存儲(chǔ)指數(shù),用來表示次方數(shù)
  • 尾數(shù)位(mantissa):最后的52位是尾數(shù),超出部分自動(dòng)進(jìn)一舍零

五. 浮點(diǎn)數(shù)的計(jì)算步驟(0.1+0.2)

【1】首先,十進(jìn)制的0.1和0.2會(huì)轉(zhuǎn)換成二進(jìn)制的,但是由于浮點(diǎn)數(shù)用二進(jìn)制表示是無窮的

0.1——>0.0001 1001 1001 1001 ...(1001循環(huán))
 0.2——>0.0011 0011 0011 0011 ...(0011循環(huán))

【2】IEEE754標(biāo)準(zhǔn)的64位雙精度浮點(diǎn)數(shù)的小數(shù)部分最多支持53位二進(jìn)制,多余的二進(jìn)制數(shù)字被截?cái)?,所以兩者相加之后的二進(jìn)制之和是

0.0100110011001100110011001100110011001100110011001101

【3】將截?cái)嘀蟮亩M(jìn)制數(shù)字再轉(zhuǎn)換為十進(jìn)制,就成了0.30000000000000004,所以在計(jì)算時(shí)產(chǎn)生了誤差

六. 解決辦法

【1】引用類庫

  • Math.js 
  • decimal.js   
  • big.js

【2】思路一:在知道小數(shù)位個(gè)數(shù)的前提下,可以考慮通過將浮點(diǎn)數(shù)放大倍數(shù)到整型(最后再除以相應(yīng)倍數(shù)),再進(jìn)行運(yùn)算操作,這樣就能得到正確的結(jié)果了  

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

【3】自定義一個(gè)轉(zhuǎn)換和處理函數(shù) 

 // f代表需要計(jì)算的表達(dá)式,digit代表小數(shù)位數(shù)
 Math.formatFloat = function (f, digit) {
  // Math.pow(指數(shù),冪指數(shù))
  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】加法函數(shù)  

 /**
  ** 加法函數(shù),用來得到精確的加法結(jié)果
  ** 說明:javascript的加法結(jié)果會(huì)有誤差,在兩個(gè)浮點(diǎn)數(shù)相加的時(shí)候會(huì)比較明顯。這個(gè)函數(shù)返回較為精確的加法結(jié)果。
  ** 調(diào)用:accAdd(arg1,arg2)
  ** 返回值:arg1加上arg2的精確結(jié)果
  **/
 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類型增加一個(gè)add方法,調(diào)用起來更加方便。
 Number.prototype.add = function (arg) {
  return accAdd(arg, this);
 };

【5】減法函數(shù)

 /**
  ** 減法函數(shù),用來得到精確的減法結(jié)果
  ** 說明:javascript的減法結(jié)果會(huì)有誤差,在兩個(gè)浮點(diǎn)數(shù)相減的時(shí)候會(huì)比較明顯。這個(gè)函數(shù)返回較為精確的減法結(jié)果。
  ** 調(diào)用:accSub(arg1,arg2)
  ** 返回值:arg1加上arg2的精確結(jié)果
  **/
 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 //動(dòng)態(tài)控制精度長度
  n = (r1 >= r2) ? r1 : r2;
  return ((arg1 * m - arg2 * m) / m).toFixed(n);
 }

 // 給Number類型增加一個(gè)mul方法,調(diào)用起來更加方便。
 Number.prototype.sub = function (arg) {
  return accMul(arg, this);
 };

【6】乘法函數(shù)

 /**
  ** 乘法函數(shù),用來得到精確的乘法結(jié)果
  ** 說明:javascript的乘法結(jié)果會(huì)有誤差,在兩個(gè)浮點(diǎn)數(shù)相乘的時(shí)候會(huì)比較明顯。這個(gè)函數(shù)返回較為精確的乘法結(jié)果。
  ** 調(diào)用:accMul(arg1,arg2)
  ** 返回值:arg1乘以 arg2的精確結(jié)果
  **/
 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類型增加一個(gè)mul方法,調(diào)用起來更加方便。
 Number.prototype.mul = function (arg) {
  return accMul(arg, this);
 };

【7】除法函數(shù)

  /** 
   ** 除法函數(shù),用來得到精確的除法結(jié)果
   ** 說明:javascript的除法結(jié)果會(huì)有誤差,在兩個(gè)浮點(diǎn)數(shù)相除的時(shí)候會(huì)比較明顯。這個(gè)函數(shù)返回較為精確的除法結(jié)果。
   ** 調(diào)用:accDiv(arg1,arg2)
   ** 返回值:arg1除以arg2的精確結(jié)果
   **/
  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類型增加一個(gè)div方法,調(diào)用起來更加方便。
  Number.prototype.div = function (arg) {
   return accDiv(this, arg);
  };

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對創(chuàng)新互聯(lián)的支持。


分享文章:JS浮點(diǎn)數(shù)運(yùn)算結(jié)果不精確的Bug解決
標(biāo)題URL:http://weahome.cn/article/ijoedh.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部