摘要:由于浮點(diǎn)數(shù)不是精確的值,所以涉及小數(shù)的比較和運(yùn)算要特別小心。根據(jù)標(biāo)準(zhǔn),位浮點(diǎn)數(shù)的指數(shù)部分的長(zhǎng)度是個(gè)二進(jìn)制位,意味著指數(shù)部分的最大值是的次方減。也就是說,位浮點(diǎn)數(shù)的指數(shù)部分的值最大為。
一 前言
這篇文章主要解決以下三個(gè)問題:
</>復(fù)制代碼
問題1:浮點(diǎn)數(shù)計(jì)算精確度的問題
0.1 + 0.2; //0.30000000000000004
0.1 + 0.2 === 0.3; // false
0.3 / 0.1; // 2.9999999999999996
(0.3 - 0.2) === (0.2 - 0.1); // false
問題2: 浮點(diǎn)數(shù)精確表示的范圍問題以及超出精確范圍后哪些能精確表示的問題
Math.pow(2, 53); // 9007199254740992
Math.pow(2, 53) + 1; // 9007199254740992
Math.pow(2, 53) + 2; // 9007199254740994
問題3:浮點(diǎn)數(shù)可以表示的范圍的問題
Math.pow(2, 1024) // Infinity
Math.pow(2, -1075); // 0
二 正文
JavaScript 內(nèi)部,所有數(shù)字都是以64位浮點(diǎn)數(shù)形式儲(chǔ)存,即使整數(shù)也是如此。
所以,1與1.0是相同的,是同一個(gè)數(shù)。
</>復(fù)制代碼
1 === 1.0 // true
這就是說,JavaScript 語(yǔ)言的底層根本沒有整數(shù),所有數(shù)字都是小數(shù)(64位浮點(diǎn)數(shù))。容易造成混淆的是,某些運(yùn)算只有整數(shù)才能完成,此時(shí) JavaScript 會(huì)自動(dòng)把64位浮點(diǎn)數(shù),轉(zhuǎn)成32位整數(shù),然后再進(jìn)行運(yùn)算。
由于浮點(diǎn)數(shù)不是精確的值,所以涉及小數(shù)的比較和運(yùn)算要特別小心。
</>復(fù)制代碼
0.1 + 0.2 === 0.3
// false
0.3 / 0.1
// 2.9999999999999996
(0.3 - 0.2) === (0.2 - 0.1)
// false
// 建議的方式:變成整數(shù)處理方式
0.1*10+0.2*10===0.3*10 // true
1.Javascript numbers
根據(jù)國(guó)際標(biāo)準(zhǔn)IEEE 754 ,JavaScript 浮點(diǎn)數(shù)的64個(gè)二進(jìn)制位,從最左邊開始,是這樣組成的:
第1位:符號(hào)位,0表示正數(shù),1表示負(fù)數(shù)
第2位到第12位(共11位):指數(shù)部分
第13位到第64位(共52位):小數(shù)部分(即有效數(shù)字)
符號(hào)位決定了一個(gè)數(shù)的正負(fù),指數(shù)部分決定了數(shù)值的大小,小數(shù)部分決定了數(shù)值的精度。
(-1)^符號(hào)位 1.xx...xx 2^指數(shù)部分
2.The fractionIEEE 754 規(guī)定,有效數(shù)字的第一位默認(rèn)總是1,不保存在64位浮點(diǎn)數(shù)之中。
也就是說,有效數(shù)字這時(shí)總是1.xx...xx的形式,其中xx..xx的部分保存在64位浮點(diǎn)數(shù)之中,最長(zhǎng)可能為52位。因此,JavaScript 提供的有效數(shù)字最長(zhǎng)為53個(gè)二進(jìn)制位:
</>復(fù)制代碼
關(guān)于有效數(shù)字的第一位默認(rèn)總是1,下面有提及,這里先了解一下:
正規(guī)化normalized:有效數(shù)位的最高位始終是1
非正規(guī)化denormalized:
當(dāng)數(shù)值非常趨近0的時(shí)候(指數(shù)位全是0),逐漸失去精確度,這時(shí)候可以用有效數(shù)位的最高位是0. 例如2e-1022.
其中:%表示是二進(jìn)制表示方式,f表示有效數(shù)字位的值,p表示指數(shù)位的值
2-1 Representing integers
編碼為整數(shù)提供了多少位?
有效數(shù)字有53個(gè)數(shù)字,1個(gè)在小數(shù)點(diǎn)之前,52個(gè)在小數(shù)點(diǎn)之后。當(dāng)p = 52時(shí),我們有一個(gè)53位的自然數(shù)。唯一的問題是最高位始終為1。也就是說,我們沒有全部位可供我們隨意使用。
分兩步去除這個(gè)限制:
首先,如果需要最高位為0的53位數(shù)字,0后面是默認(rèn)的1,則設(shè)置p = 51。這樣有效數(shù)位的最低位將成為小數(shù)點(diǎn)之后的第一個(gè)小數(shù)數(shù)字,整數(shù)部分為0。以此類推,直到你在p = 0和f = 0,編碼數(shù)字1。
</>復(fù)制代碼
例如 1.xxxx...11 * 2e52 = 1xxxx...11 -> 1.xxxx...11 * 2e51 = 1xxxx...1.1 = 01xxxx...1
其次,對(duì)于全部53位,我們?nèi)匀恍枰硎玖恪?如何做到這一點(diǎn)在下一節(jié)中解釋。 請(qǐng)注意,由于符號(hào)是多帶帶存儲(chǔ)的,因此整數(shù)的幅度(絕對(duì)值)為53位。
3.The exponent3-1 Common exponent
根據(jù)標(biāo)準(zhǔn),64位浮點(diǎn)數(shù)的指數(shù)部分的長(zhǎng)度是11個(gè)二進(jìn)制位,意味著指數(shù)部分的最大值是2047(2的11次方減1)。也就是說,64位浮點(diǎn)數(shù)的指數(shù)部分的值最大為2047。
因?yàn)橹笖?shù)位兩個(gè)指數(shù)值是保留的:最低的一個(gè)0和最高的一個(gè)2047(下文會(huì)有講解),為了支持負(fù)數(shù)指數(shù)部分,進(jìn)行二進(jìn)制數(shù)偏移,1023 ---> 0,0以下的全為負(fù)數(shù)。因此JavaScript 指數(shù)部分能夠表示的數(shù)值范圍為(-1023,1024),指數(shù)部分超出這個(gè)范圍的數(shù)無法表示。
(負(fù)數(shù)表示:取補(bǔ)碼 = 反碼 +1;符號(hào)位不變)
如果一個(gè)數(shù)大于等于2的1024次方,那么就會(huì)發(fā)生“正向溢出”,即 JavaScript 無法表示這么大的數(shù),這時(shí)就會(huì)返回Infinity。
</>復(fù)制代碼
Math.pow(2, 1024) // Infinity
如果一個(gè)數(shù)小于等于2的-1075次方(指數(shù)部分最小值-1023,再加上小數(shù)部分的52位),那么就會(huì)發(fā)生為“負(fù)向溢出”,即 JavaScript 無法表示這么小的數(shù),這時(shí)會(huì)直接返回0。
</>復(fù)制代碼
Math.pow(2, -1075) // 0
下面是一個(gè)實(shí)際的例子。
</>復(fù)制代碼
var x = 0.5;
for(var i = 0; i < 25; i++) {
x = x * x;
}
x // 0
上面代碼中,對(duì)0.5連續(xù)做25次平方,由于最后結(jié)果太接近0,超出了可表示的范圍,JavaScript 就直接將其轉(zhuǎn)為0。
JavaScript 提供Number對(duì)象的MAX_VALUE和MIN_VALUE屬性,返回可以表示的具體的最大值和最小值。
</>復(fù)制代碼
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MIN_VALUE // 5e-324
3-2 Special exponent
指數(shù)位兩個(gè)指數(shù)值是保留的:
</>復(fù)制代碼
最低的一個(gè)(0)和最高的一個(gè)(2047)
2047的指數(shù)用于無窮大和NaN(非數(shù)字)值。
IEEE 754標(biāo)準(zhǔn)有許多NaN值,但JavaScript都將它們表示為單個(gè)值NaN。
指數(shù)0用于兩種情況:
(1)如果有效數(shù)位值是0,那么整數(shù)就是0。由于符號(hào)是分開存儲(chǔ)的,我們同時(shí)具有-0和+0。
(2)0的指數(shù)也用于表示非常小的數(shù)字(接近零)。 然后該有效數(shù)位的值必須是非零的,如果是正數(shù),則通過計(jì)算該數(shù)字:
</>復(fù)制代碼
%0.f × 2^(e ? 1022)
這種表示被稱為非規(guī)范化。 先前討論的表示被稱為標(biāo)準(zhǔn)化。 可以以規(guī)范化方式表示的最小的正數(shù)(非零)數(shù)是:
</>復(fù)制代碼
%1.0 × 2^(e - 1022)
最大的非正規(guī)化數(shù)字是:
</>復(fù)制代碼
%0.1 × 2^(e - 1022)
因此,在標(biāo)準(zhǔn)化和非標(biāo)準(zhǔn)化數(shù)字之間可以完美切換。
特?cái)?shù)值列表:
+0:sign=0,e=0,f=0
-0:singn=1,e=0,f=0
Infinity:sign=0,e=2047(全是1)
-Infinity:sign=1,e=2047(全是1)
NaN:e=2047(全是1),f>0(f不全是0)
3-3 Summary:exponents
由于p = e - 1023, 指數(shù)部分的范圍是:
?1023 < p < 1024
4.Decimal fraction并非所有小數(shù)都可以用JavaScript精確表示,如下所示:
????
</>復(fù)制代碼
0.1 + 0.2 // 0.30000000000000004
小數(shù)部分0.1和0.2都不能精確地表示為二進(jìn)制浮點(diǎn)數(shù)。但是,與實(shí)際值的偏差通常太小而不能顯示。
加法導(dǎo)致偏差變得可見??匆粋€(gè)例子:
</>復(fù)制代碼
0.1 + 1 - 1 // 0.10000000000000009
????
表示十進(jìn)制分?jǐn)?shù)1/10來說是個(gè)挑戰(zhàn),困難的部分是分母10。其分母的因子分解是2×5,指數(shù)只允許你用2的冪除整數(shù),所以沒有辦法得到因子5。相反,將二進(jìn)制小數(shù)表示為小數(shù)部分總是可能的。
4-1 Comparing decimal fractions
由于浮點(diǎn)數(shù)計(jì)算的結(jié)果不能保證精確,因此,當(dāng)你使用具有小數(shù)值的浮點(diǎn)數(shù)數(shù)輸入時(shí),不應(yīng)直接比較它們。 相反,考慮舍入誤差的上限。 這樣的上界稱為機(jī)器epsilon。 雙精度的標(biāo)準(zhǔn)epsilon值是2^(-53)。
</>復(fù)制代碼
var epsEqu = function () { // IIFE, keeps EPSILON private
var EPSILON = Math.pow(2, -53);
return function epsEqu(x, y) {
return Math.abs(x - y) < EPSILON;
};
}();
上面的運(yùn)行結(jié)果:
</>復(fù)制代碼
0.1 + 0.2 === 0.3 //false
epsEqu(0.1+0.2, 0.3)//true
5.The maximum integer
有效數(shù)含52位,但是有效數(shù)位前總有一個(gè)默認(rèn)1,因此除零之外的所有53位數(shù)都可以表示,并且它有一個(gè)特殊值來表示零(連同零的一部分)。
所以浮點(diǎn)數(shù)能精確表示的范圍是: [2^(-53),2^53]
</>復(fù)制代碼
> Math.pow(2, 53)
9007199254740992
> Math.pow(2, 53) - 1
9007199254740991
> Math.pow(2, 53) - 2
9007199254740990
當(dāng)超出這個(gè)范圍時(shí),不能保證準(zhǔn)確顯示:
</>復(fù)制代碼
> Math.pow(2, 53) + 1
9007199254740992
你可能疑惑,最高的整數(shù)不應(yīng)該是是2^53-1的嘛?通常x位:表示最低數(shù)字是0,最高數(shù)字是2^X-1。例如,最高的8位數(shù)字是255.在JavaScript中,最高分?jǐn)?shù)確實(shí)用于數(shù)字2^53-1,但可以準(zhǔn)確表示2^53,這要?dú)w功于指數(shù)的幫助--2^53是一個(gè)小數(shù)部分f = 0,指數(shù)p = 53(轉(zhuǎn)換后):
</>復(fù)制代碼
%1.f × 2p = %1.0 × 2^53 = 2^53
為什么有些大于2^53的數(shù)能被精確表示呢?看下面例子:
</>復(fù)制代碼
> Math.pow(2, 53)
9007199254740992
> Math.pow(2, 53) + 1 // not OK
9007199254740992
> Math.pow(2, 53) + 2 // OK
9007199254740994
> Math.pow(2, 53) * 2 // OK
18014398509481984
2^53×2能正常表示是因?yàn)橹笖?shù)可以使用。 每乘以2只是將指數(shù)遞增1并且不影響有效數(shù)位。 因此,就最大有效數(shù)位值而言,乘以2的冪不是問題。 為了明白為什么我們可以加2到2^53,但不是1,我們擴(kuò)展了前面的表,其中53和54的附加位以及p = 53和p = 54的行:
查看行(p = 53),很明顯JavaScript數(shù)字可以將位53設(shè)置為1.但是由于小數(shù)f只有52位,所以位0必須為零。 因此,只有偶數(shù)x可以在2^53≤x<2^54的范圍內(nèi)表示。在行(p = 54)中,該間距增加到4的倍數(shù),在2^54≤x<2^55的范圍內(nèi):
</>復(fù)制代碼
> Math.pow(2, 54)
18014398509481984
> Math.pow(2, 54) + 1
18014398509481984
> Math.pow(2, 54) + 2
18014398509481984
> Math.pow(2, 54) + 3
18014398509481988
> Math.pow(2, 54) + 4
18014398509481988
...
6.IEEE 754 exceptions
IEEE 754標(biāo)準(zhǔn)描述了五個(gè)特殊情況,其中一個(gè)不能計(jì)算精確的值:
(1)Invalid(不合法):執(zhí)行了不合法操作。例如,計(jì)算負(fù)數(shù)的平方根。返回NaN。
</>復(fù)制代碼
Math.sqrt(-1) // 為NaN
(2)Division by zero(除以零):返回正負(fù)無窮。
</>復(fù)制代碼
3/0; // 無窮
-5/0; //-無窮
(3)Overflow(上溢):結(jié)果太大而無法表示。這意味著指數(shù)太高(p≥1024)。根據(jù)符號(hào),有正面和負(fù)面溢出。返回正負(fù)無窮。
</>復(fù)制代碼
Math.pow(2,2048; // Infinity
-Math.pow(2,2048; // -Infinity
(4)Underflow(下溢):結(jié)果太接近零來表示。這意味著指數(shù)太低(p≤-1023)。返回非規(guī)格化的值或零。
</>復(fù)制代碼
Math.pow(2,-2048); // 0
(5)Inexact(不精確):操作產(chǎn)生了不準(zhǔn)確的結(jié)果 - 要保留的分?jǐn)?shù)有太多有效數(shù)字。返回一個(gè)舍入結(jié)果。
</>復(fù)制代碼
0.1 + 0.2; // 0.30000000000000004
9007199254740992 + 1; //9007199254740992
#3和#4是關(guān)于指數(shù),#5是關(guān)于有效數(shù)。 #3和#5之間的區(qū)別非常微妙:在第五個(gè)例子中,我們超過了有效數(shù)的上限(這將是整數(shù)計(jì)算中的溢出)。但只有超過指數(shù)的上限才稱為IEEE 754中的溢出。
三 后記讓我們回顧一下前言中的三個(gè)問題:
問題一:為什么0.1+0.2===0.3 //false
經(jīng)過對(duì)正文部分的閱讀,你已經(jīng)知道浮點(diǎn)精確度的問題,對(duì)于超出浮點(diǎn)精確度的部分計(jì)算機(jī)是無法管理的。
下面讓我們回到計(jì)算機(jī)對(duì)十進(jìn)制數(shù)用二進(jìn)制數(shù)表示方法上:
對(duì)于二進(jìn)制小數(shù),小數(shù)點(diǎn)右邊能表達(dá)的值是 1/2, 1/4, 1/8, 1/16, 1/32, 1/64, 1/128 ... 1/(2^n)
現(xiàn)在問題來了, 計(jì)算機(jī)只能用這些個(gè) 1/(2^n) 之和來表達(dá)十進(jìn)制的小數(shù)。
我們來試一試如何表達(dá)十進(jìn)制的 0.2 吧。
</>復(fù)制代碼
0.01 = 1/4 = 0.25 ,太大
0.001 =1/8 = 0.125 , 又太小
0.0011 = 1/8 + 1/16 = 0.1875 , 逼近0.2了
0.00111 = 1/8 + 1/16 + 1/32 = 0.21875 , 又大了
0.001101 = 1/8+ 1/16 + 1/64 = 0.203125 還是大
0.0011001 = 1/8 + 1/16 + 1/128 = 0.1953125 這結(jié)果不錯(cuò)
0.00110011 = 1/8+1/16+1/128+1/256 = 0.19921875
已經(jīng)很逼近了,計(jì)算機(jī)不可能提供無限的空間讓程序去存儲(chǔ)這些二進(jìn)制小數(shù)的, 就這樣吧。 用二進(jìn)制小數(shù)沒法精確表達(dá)10進(jìn)制小數(shù)!
對(duì)于不能精確表示的小數(shù)部分的計(jì)算,無法保證一致的正確結(jié)果(小數(shù)結(jié)果非常接近十進(jìn)制正確值了,但是會(huì)取相對(duì)靠近這個(gè)正確值的能用二進(jìn)制表示的那個(gè)十進(jìn)制數(shù))。
那么如何解決算數(shù)比較的問題?
</>復(fù)制代碼
var epsEqu = function () { // IIFE, keeps EPSILON private
var EPSILON = Math.pow(2, -53);
return function epsEqu(x, y) {
return Math.abs(x - y) < EPSILON
};
}();
問題二: 浮點(diǎn)數(shù)精確表示的范圍問題以及超出精確范圍后哪些能精確表示的問題
由正文部分的內(nèi)容,我們知道了總共有53(52+1)位有效數(shù)位,所以
浮點(diǎn)數(shù)精確表示的范圍:[-2e53,2e53]
超出精確范圍后哪些能精確表示:
+-*/ 計(jì)算有效數(shù)位超出范圍的位上含有1的則不能精確表示計(jì)算結(jié)果(1會(huì)被舍棄,影響結(jié)果),超出位上的值全是0的可以準(zhǔn)確的表示計(jì)算結(jié)果(指數(shù)位數(shù)值變化后舍棄有效數(shù)字位最后一位0,對(duì)結(jié)果無影響)。
一般來說如果是53位,那么最大值應(yīng)該是2e53-1,但是如上面解釋的那樣,2e53是可以被精確表示的,所以浮點(diǎn)數(shù)精確表示的范圍:[-2e53,2e53]。
</>復(fù)制代碼
Math.pow(2, 53)
9007199254740992
Math.pow(2, 53) + 1 // not OK
9007199254740992
Math.pow(2, 53) + 2 // OK
9007199254740994
Math.pow(2, 53) * 2 // OK
18014398509481984
參考鏈接:
浮點(diǎn)數(shù)計(jì)算不精確
阮一峰--數(shù)值
Javascript and more
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/94666.html
摘要:大小寫的不同分別表示不同的變量。本質(zhì)由一組無序的名值對(duì)組成的。字符串中第一個(gè)小數(shù)點(diǎn)有效,第二個(gè)無效,后面的字符串會(huì)被忽略。注意雙引號(hào)開頭,必須以雙引號(hào)結(jié)尾,單引號(hào)也是如此轉(zhuǎn)義字符表示非打印字符或具有其他用途的字符。 JavaScript高級(jí)程序設(shè)計(jì)(第3版)讀書筆記 1.區(qū)分大小寫: 變量、函數(shù)名和操作符都要區(qū)分大小寫。大小寫的不同分別表示不同的變量。 2.標(biāo)識(shí)符: 變量、函數(shù)、屬性...
摘要:高程讀書筆記第三章語(yǔ)法中的一切變量函數(shù)名和操作符都區(qū)分大小寫。建議無論在任何情況下都指定基數(shù)函數(shù)與函數(shù)類似。返回對(duì)象的字符串?dāng)?shù)值或布爾值表示。 JS高程讀書筆記--第三章 語(yǔ)法 ECMAScript中的一切(變量、函數(shù)名和操作符)都區(qū)分大小寫。 不能把關(guān)鍵字、保留字、true、false和null用做標(biāo)識(shí)符。 嚴(yán)格模式是為JavaScript定義了一種不同的解析與執(zhí)行模型。在嚴(yán)格模式...
摘要:將任一數(shù)值與執(zhí)行按位與操作,其結(jié)果都為。中應(yīng)用判斷奇偶性偶數(shù)奇數(shù)按位異或規(guī)則每一位都不同,結(jié)果才為將任一數(shù)值與進(jìn)行異或操作,其結(jié)果為。 位運(yùn)算在算法中很有用,速度可以比四則運(yùn)算快很多。 To2orTo10 JS中十進(jìn)制轉(zhuǎn)二進(jìn)制: (val).toString(2)JS中二進(jìn)制轉(zhuǎn)十進(jìn)制: parseInt(val, 2) JS中規(guī)定安全整數(shù)的范圍是-2^53~2^53,所以大于90071...
摘要:下面就讓我們來一起深入了解下,為以后的策馬奔騰做好鋪墊。整數(shù)整數(shù),可以通過十進(jìn)制,八進(jìn)制,十六進(jìn)制的字面值來表示。對(duì)前面定義的八進(jìn)制和十六進(jìn)制數(shù)值進(jìn)行運(yùn)算浮點(diǎn)數(shù)浮點(diǎn)數(shù)其實(shí)就是我們通常所說的小數(shù),所以一定有個(gè)小數(shù)點(diǎn)。 Number 類型作為 JS 的基本數(shù)據(jù)類型之一,被應(yīng)用在程序中的各種場(chǎng)景,其重要性就如數(shù)字對(duì)于我們?nèi)粘I?。下面就讓我們來一起深入了解下,為以后的策馬奔騰做好鋪墊。 定義...
摘要:大數(shù)值的精度問題能夠被安全呈現(xiàn)的最大整數(shù)是,即。我們可以將需要比較的兩個(gè)值進(jìn)行相減,再與這個(gè)機(jī)器精度進(jìn)行比較,如果在誤差范圍內(nèi),我們也視為兩個(gè)值是相等的。 近期在項(xiàng)目中有出現(xiàn)大數(shù)值的訂單號(hào)9148368244236619在調(diào)用接口時(shí)自動(dòng)變成9148368244236620的情況,導(dǎo)致請(qǐng)求失誤。本文特意總結(jié)了出現(xiàn)這種情況的原因,以及js精度相關(guān)的情況。 jquery[.data()]方法...
摘要:系統(tǒng)內(nèi)部會(huì)自動(dòng)調(diào)用函數(shù)。因此除了以下五個(gè)值,其他都是自動(dòng)轉(zhuǎn)為??兆址詣?dòng)轉(zhuǎn)換為字符串遇到預(yù)期為字符串的地方,就會(huì)將非字符串的值自動(dòng)轉(zhuǎn)為字符串。字符串的自動(dòng)轉(zhuǎn)換,主要發(fā)生在字符串的加法運(yùn)算時(shí)。 URL后面#號(hào)是代表什么? # 代表頁(yè)面中的某個(gè)位置,也稱為地址hash值 #XX 作用當(dāng)前可視區(qū)域移動(dòng)到頁(yè)面xx位置 # 僅對(duì)瀏覽器起作用,對(duì)服務(wù)器無用,所以HTTP請(qǐng)求不包括#(#之后的字...
閱讀 2301·2021-10-09 09:41
閱讀 1754·2019-08-30 15:53
閱讀 999·2019-08-30 15:52
閱讀 3451·2019-08-30 11:26
閱讀 779·2019-08-29 16:09
閱讀 3434·2019-08-29 13:25
閱讀 2269·2019-08-26 16:45
閱讀 1939·2019-08-26 11:51