欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解JavaScript中浮點(diǎn)數(shù)的精度計(jì)算

 更新時(shí)間:2023年06月21日 09:39:05   作者:NoSilverBullet  
這篇文章主要來(lái)和大家介紹一下JavaScript中浮點(diǎn)數(shù)精度計(jì)算的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下

一、前言

JavaScript 只有一種數(shù)字類(lèi)型 Number,而且在Javascript中所有的數(shù)字都是以IEEE-754標(biāo)準(zhǔn)格式表示的。即所有數(shù)字都是以 64 位浮點(diǎn)數(shù)形式儲(chǔ)存,即便整數(shù)也是如此。 所以我們?cè)诖蛴?1.00 這樣的浮點(diǎn)數(shù)的結(jié)果是 1 而非 1.00 。浮點(diǎn)數(shù)的精度問(wèn)題也不只是JavaScript特有,因?yàn)橛行┬?shù)以二進(jìn)制表示位數(shù)是無(wú)窮的。

十進(jìn)制二進(jìn)制
0.10.0001 1001 1001 1001 ...
0.20.0011 0011 0011 0011 ...
0.30.0100 1100 1100 1100 ...
0.40.0110 0110 0110 0110 ...
0.50.1
0.60.1001 1001 1001 1001 ...

比如 1.1,其程序?qū)嶋H上無(wú)法真正的表示 ‘1.1',而只能做到一定程度上的準(zhǔn)確,這是無(wú)法避免的精度丟失:1.09999999999999999

二、浮點(diǎn)數(shù)精度問(wèn)題

其實(shí),在JavaScript中浮點(diǎn)數(shù)精度問(wèn)題還要復(fù)雜些,這里只給一些在Chrome中測(cè)試數(shù)據(jù):

console.log(1.0-0.9 == 0.1)    //false
console.log(1.0-0.8 == 0.2)    //false
console.log(1.0-0.7 == 0.3)    //false
console.log(1.0-0.6 == 0.4)    //true
console.log(1.0-0.5 == 0.5)    //true
console.log(1.0-0.4 == 0.6)    //true
console.log(1.0-0.3 == 0.7)    //true
console.log(1.0-0.2 == 0.8)    //true
console.log(1.0-0.1 == 0.9)    //true

那如何來(lái)避免這類(lèi) 1.0-0.9 != 0.1 的非bug型問(wèn)題發(fā)生呢?下面給出一種目前用的比較多的解決方案, 在判斷浮點(diǎn)運(yùn)算結(jié)果前對(duì)計(jì)算結(jié)果進(jìn)行精度縮小,因?yàn)樵诰瓤s小的過(guò)程總會(huì)自動(dòng)四舍五入:

(1.0-0.9).toFixed(digits)  // toFixed() 精度參數(shù)digits須在0與20之間
console.log(parseFloat((1.0-0.9).toFixed(10)) === 0.1)   //true
console.log(parseFloat((1.0-0.8).toFixed(10)) === 0.2)    //true
console.log(parseFloat((1.0-0.7).toFixed(10)) === 0.3)    //true
console.log(parseFloat((11.0-11.8).toFixed(10)) === -0.8)   //true

寫(xiě)成一個(gè)方法:

//通過(guò)isEqual工具方法判斷數(shù)值是否相等
function isEqual(number1, number2, digits){
  digits = digits == undefined? 10: digits; // 默認(rèn)精度為10
  return number1.toFixed(digits) === number2.toFixed(digits);
}
console.log(isEqual(1.0-0.7, 0.3));  //true
//原型擴(kuò)展方式,更喜歡面向?qū)ο蟮娘L(fēng)格
Number.prototype.isEqual = function(number, digits){
  digits = digits == undefined? 10: digits; // 默認(rèn)精度為10
  return this.toFixed(digits) === number.toFixed(digits);
}
console.log((1.0-0.7).isEqual(0.3)); //true

接下來(lái),再來(lái)試試浮點(diǎn)數(shù)的運(yùn)算,

console.log(1.79+0.12)  //1.9100000000000001
console.log(2.01-0.12)   //1.8899999999999997
console.log(1.01*1.3)    //1.3130000000000002
console.log(0.69/10)     //0.06899999999999999

解決方案 - 先升冪再降冪,同時(shí)輔以精度控制

//加法函數(shù),用來(lái)得到精確的加法結(jié)果
//說(shuō)明: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;
  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))
  return (arg1*m+arg2*m)/m
}
//給Number類(lèi)型增加一個(gè)add方法,調(diào)用起來(lái)更加方便。
Number.prototype.add = function (arg){
  return accAdd(arg,this);
}
//減法函數(shù),用來(lái)得到精確的減法結(jié)果
//說(shuō)明: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)控制精度長(zhǎng)度
  n=(r1>=r2)?r1:r2;
  return ((arg1*m-arg2*m)/m).toFixed(n);
}
//除法函數(shù),用來(lái)得到精確的除法結(jié)果
//說(shuō)明: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類(lèi)型增加一個(gè)div方法,調(diào)用起來(lái)更加方便。
Number.prototype.div = function (arg){
  return accDiv(this, arg);
}
//乘法函數(shù),用來(lái)得到精確的乘法結(jié)果
//說(shuō)明: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類(lèi)型增加一個(gè)mul方法,調(diào)用起來(lái)更加方便。
Number.prototype.mul = function (arg){
  return accMul(arg, this);
}

結(jié)果如下:

console.log(accAdd(1.79, 0.12));  //1.91
console.log(accSub(2.01, 0.12));  //1.89
console.log(accDiv(0.69, 10));    //0.069<br>console.log(accMul(1.01, 1.3));   //1.313 

改造之后,可以愉快地進(jìn)行浮點(diǎn)數(shù)加減乘除操作了~ 

三、整數(shù)精度問(wèn)題

Javascript 中,整數(shù)精度同樣存在問(wèn)題,先來(lái)看看問(wèn)題:

JavaScript 代碼:

console.log(19571992547450991); //=> 19571992547450990
console.log(19571992547450991===19571992547450992); //=> true

同樣的原因,在 JavaScriptNumber類(lèi)型統(tǒng)一按浮點(diǎn)數(shù)處理,整數(shù)是按最大54位來(lái)算最大(253 - 1,Number.MAX_SAFE_INTEGER,9007199254740991) 和最小(-(253 - 1),Number.MIN_SAFE_INTEGER,-9007199254740991) 安全整數(shù)范圍的。所以只要超過(guò)這個(gè)范圍,就會(huì)存在被舍去的精度問(wèn)題。

當(dāng)然這個(gè)問(wèn)題并不只是在 Javascript 中才會(huì)出現(xiàn),幾乎所有的編程語(yǔ)言都采用了 IEEE-745 浮點(diǎn)數(shù)表示法,任何使用二進(jìn)制浮點(diǎn)數(shù)的編程語(yǔ)言都會(huì)有這個(gè)問(wèn)題,只不過(guò)在很多其他語(yǔ)言中已經(jīng)封裝好了方法來(lái)避免精度的問(wèn)題,而 JavaScript 作為一門(mén)弱類(lèi)型語(yǔ)言,從設(shè)計(jì)思想上就沒(méi)有對(duì)浮點(diǎn)數(shù)有嚴(yán)格的數(shù)據(jù)類(lèi)型,所以精度誤差的問(wèn)題就顯得格外突出。

四、解決方案

類(lèi)庫(kù)

通常這種對(duì)精度要求高的計(jì)算都應(yīng)該交給后端去計(jì)算和存儲(chǔ),因?yàn)楹蠖擞谐墒斓膸?kù)來(lái)解決這種計(jì)算問(wèn)題。前端也有幾個(gè)不錯(cuò)的類(lèi)庫(kù):

4.1.1 Math.js

Math.js 是專(zhuān)門(mén)為 JavaScriptNode.js 提供的一個(gè)廣泛的數(shù)學(xué)庫(kù)。它具有靈活的表達(dá)式解析器,支持符號(hào)計(jì)算,配有大量?jī)?nèi)置函數(shù)和常量,并提供集成解決方案來(lái)處理不同的數(shù)據(jù)類(lèi)型 像數(shù)字,大數(shù)(超出安全數(shù)的數(shù)字),復(fù)數(shù),分?jǐn)?shù),單位和矩陣。 功能強(qiáng)大,易于使用。

4.1.2 decimal.js

JavaScript 提供十進(jìn)制類(lèi)型的任意精度數(shù)值。

4.1.3 big.js

這幾個(gè)類(lèi)庫(kù)幫我們解決很多這類(lèi)問(wèn)題,不過(guò)通常我們前端做這類(lèi)運(yùn)算通常只用于表現(xiàn)層,應(yīng)用并不是很多。所以很多時(shí)候,一個(gè)函數(shù)能解決的問(wèn)題不需要引用一個(gè)類(lèi)庫(kù)來(lái)解決。

下面介紹各個(gè)更加簡(jiǎn)單的解決方案。

整數(shù)表示 對(duì)于整數(shù),我們可以通過(guò)用String類(lèi)型的表示來(lái)取值或傳值,否則會(huì)喪失精度。

格式化數(shù)字、金額、保留幾位小數(shù)等 如果只是格式化數(shù)字、金額、保留幾位小數(shù)等可以查看這里。

五、延伸閱讀

5.1 IEEE 754 標(biāo)準(zhǔn)

JavaScript 里的數(shù)字是采用 IEEE 754 標(biāo)準(zhǔn)的 64 位雙精度浮點(diǎn)數(shù)。該規(guī)范定義了浮點(diǎn)數(shù)的格式,對(duì)于64位浮點(diǎn)數(shù)在內(nèi)存中的表示,最高1位是符號(hào)位,接著的11位是指數(shù),剩下的52位為有效數(shù)字,具體:

  • 第0位:符號(hào)位, s 表示 ,0表示正數(shù),1表示負(fù)數(shù);
  • 第1位到第11位:儲(chǔ)存指數(shù)部分, e 表示 ;
  • 第12位到第63位:儲(chǔ)存小數(shù)部分(即有效數(shù)字),f 表示;

其中:符號(hào)位決定了一個(gè)數(shù)的正負(fù),指數(shù)部分決定了數(shù)值的大小,小數(shù)部分決定了數(shù)值的精度。

IEEE 754規(guī)定,有效數(shù)字第一位默認(rèn)總是1,不保存在64位浮點(diǎn)數(shù)之中。也就是說(shuō),有效數(shù)字總是1.xx…xx的形式,其中xx..xx的部分保存在64位浮點(diǎn)數(shù)之中,最長(zhǎng)可能為52位。因此,JavaScript提供的有效數(shù)字最長(zhǎng)為53個(gè)二進(jìn)制位(64位浮點(diǎn)的后52位+有效數(shù)字第一位的1)。

5.2 toFixed()

浮點(diǎn)數(shù)運(yùn)算的解決方案有很多,這里給出一種目前常用的解決方案, 在判斷浮點(diǎn)數(shù)運(yùn)算結(jié)果前對(duì)計(jì)算結(jié)果進(jìn)行精度縮小,因?yàn)樵诰瓤s小的過(guò)程總會(huì)自動(dòng)四舍五入。

toFixed() 方法使用定點(diǎn)表示法來(lái)格式化一個(gè)數(shù),會(huì)對(duì)結(jié)果進(jìn)行四舍五入。語(yǔ)法為:

numObj.toFixed(digits)

參數(shù) digits 表示小數(shù)點(diǎn)后數(shù)字的個(gè)數(shù);介于 0 到 20 (包括)之間,實(shí)現(xiàn)環(huán)境可能支持更大范圍。如果忽略該參數(shù),則默認(rèn)為 0。

返回一個(gè)數(shù)值的字符串表現(xiàn)形式,不使用指數(shù)記數(shù)法,而是在小數(shù)點(diǎn)后有 digits 位數(shù)字。該數(shù)值在必要時(shí)進(jìn)行四舍五入,另外在必要時(shí)會(huì)用 0 來(lái)填充小數(shù)部分,以便小數(shù)部分有指定的位數(shù)。 如果數(shù)值大于 1e+21,該方法會(huì)簡(jiǎn)單調(diào)用 Number.prototype.toString()并返回一個(gè)指數(shù)記數(shù)法格式的字符串。

注意:toFixed() 返回一個(gè)數(shù)值的字符串表現(xiàn)形式。

具體可以查看 MDN中的說(shuō)明,那么我們可以這樣解決精度問(wèn)題:

parseFloat((數(shù)學(xué)表達(dá)式).toFixed(digits)); // toFixed() 精度參數(shù)須在 0 與20 之間
// 運(yùn)行
parseFloat((1.0 - 0.9).toFixed(10)) // 結(jié)果為 0.1   
parseFloat((0.3 / 0.1).toFixed(10)) // 結(jié)果為 3  
parseFloat((9.7 * 100).toFixed(10)) // 結(jié)果為 970 
parseFloat((2.22 + 0.1).toFixed(10)) // 結(jié)果為 2.32

注意:在老版本的IE瀏覽器(IE 6,7,8)中,toFixed()方法返回值不一定準(zhǔn)確。所以這個(gè)方法以前很少用。

六、拓展閱讀

js保留兩位小數(shù)方法總結(jié)

到此這篇關(guān)于詳解JavaScript中浮點(diǎn)數(shù)的精度計(jì)算的文章就介紹到這了,更多相關(guān)JavaScript浮點(diǎn)數(shù)精度計(jì)算內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論