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

詳解JavaScript中浮點數(shù)的精度計算

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

一、前言

JavaScript 只有一種數(shù)字類型 Number,而且在Javascript中所有的數(shù)字都是以IEEE-754標準格式表示的。即所有數(shù)字都是以 64 位浮點數(shù)形式儲存,即便整數(shù)也是如此。 所以我們在打印 1.00 這樣的浮點數(shù)的結(jié)果是 1 而非 1.00 。浮點數(shù)的精度問題也不只是JavaScript特有,因為有些小數(shù)以二進制表示位數(shù)是無窮的。

十進制二進制
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上無法真正的表示 ‘1.1',而只能做到一定程度上的準確,這是無法避免的精度丟失:1.09999999999999999

二、浮點數(shù)精度問題

其實,在JavaScript中浮點數(shù)精度問題還要復(fù)雜些,這里只給一些在Chrome中測試數(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

那如何來避免這類 1.0-0.9 != 0.1 的非bug型問題發(fā)生呢?下面給出一種目前用的比較多的解決方案, 在判斷浮點運算結(jié)果前對計算結(jié)果進行精度縮小,因為在精度縮小的過程總會自動四舍五入:

(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

寫成一個方法:

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

接下來,再來試試浮點數(shù)的運算,

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ù),用來得到精確的加法結(jié)果
//說明:javascript的加法結(jié)果會有誤差,在兩個浮點數(shù)相加的時候會比較明顯。這個函數(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類型增加一個add方法,調(diào)用起來更加方便。
Number.prototype.add = function (arg){
  return accAdd(arg,this);
}
//減法函數(shù),用來得到精確的減法結(jié)果
//說明:javascript的加法結(jié)果會有誤差,在兩個浮點數(shù)相加的時候會比較明顯。這個函數(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
  //動態(tài)控制精度長度
  n=(r1>=r2)?r1:r2;
  return ((arg1*m-arg2*m)/m).toFixed(n);
}
//除法函數(shù),用來得到精確的除法結(jié)果
//說明:javascript的除法結(jié)果會有誤差,在兩個浮點數(shù)相除的時候會比較明顯。這個函數(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類型增加一個div方法,調(diào)用起來更加方便。
Number.prototype.div = function (arg){
  return accDiv(this, arg);
}
//乘法函數(shù),用來得到精確的乘法結(jié)果
//說明:javascript的乘法結(jié)果會有誤差,在兩個浮點數(shù)相乘的時候會比較明顯。這個函數(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類型增加一個mul方法,調(diào)用起來更加方便。
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 

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

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

Javascript 中,整數(shù)精度同樣存在問題,先來看看問題:

JavaScript 代碼:

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

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

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

四、解決方案

類庫

通常這種對精度要求高的計算都應(yīng)該交給后端去計算和存儲,因為后端有成熟的庫來解決這種計算問題。前端也有幾個不錯的類庫:

4.1.1 Math.js

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

4.1.2 decimal.js

JavaScript 提供十進制類型的任意精度數(shù)值。

4.1.3 big.js

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

下面介紹各個更加簡單的解決方案。

整數(shù)表示 對于整數(shù),我們可以通過用String類型的表示來取值或傳值,否則會喪失精度。

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

五、延伸閱讀

5.1 IEEE 754 標準

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

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

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

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

5.2 toFixed()

浮點數(shù)運算的解決方案有很多,這里給出一種目前常用的解決方案, 在判斷浮點數(shù)運算結(jié)果前對計算結(jié)果進行精度縮小,因為在精度縮小的過程總會自動四舍五入。

toFixed() 方法使用定點表示法來格式化一個數(shù),會對結(jié)果進行四舍五入。語法為:

numObj.toFixed(digits)

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

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

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

具體可以查看 MDN中的說明,那么我們可以這樣解決精度問題:

parseFloat((數(shù)學表達式).toFixed(digits)); // toFixed() 精度參數(shù)須在 0 與20 之間
// 運行
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()方法返回值不一定準確。所以這個方法以前很少用。

六、拓展閱讀

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

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

相關(guān)文章

最新評論