摘要:由于浮點數不是精確的值,所以涉及小數的比較和運算要特別小心。根據標準,位浮點數的指數部分的長度是個二進制位,意味著指數部分的最大值是的次方減。也就是說,位浮點數的指數部分的值最大為。
一 前言
這篇文章主要解決以下三個問題:
問題1:浮點數計算精確度的問題 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: 浮點數精確表示的范圍問題以及超出精確范圍后哪些能精確表示的問題 Math.pow(2, 53); // 9007199254740992 Math.pow(2, 53) + 1; // 9007199254740992 Math.pow(2, 53) + 2; // 9007199254740994 問題3:浮點數可以表示的范圍的問題 Math.pow(2, 1024) // Infinity Math.pow(2, -1075); // 0二 正文
JavaScript 內部,所有數字都是以64位浮點數形式儲存,即使整數也是如此。
所以,1與1.0是相同的,是同一個數。
1 === 1.0 // true
這就是說,JavaScript 語言的底層根本沒有整數,所有數字都是小數(64位浮點數)。容易造成混淆的是,某些運算只有整數才能完成,此時 JavaScript 會自動把64位浮點數,轉成32位整數,然后再進行運算。
由于浮點數不是精確的值,所以涉及小數的比較和運算要特別小心。
0.1 + 0.2 === 0.3 // false 0.3 / 0.1 // 2.9999999999999996 (0.3 - 0.2) === (0.2 - 0.1) // false // 建議的方式:變成整數處理方式 0.1*10+0.2*10===0.3*10 // true1.Javascript numbers
根據國際標準IEEE 754 ,JavaScript 浮點數的64個二進制位,從最左邊開始,是這樣組成的:
第1位:符號位,0表示正數,1表示負數
第2位到第12位(共11位):指數部分
第13位到第64位(共52位):小數部分(即有效數字)
符號位決定了一個數的正負,指數部分決定了數值的大小,小數部分決定了數值的精度。
(-1)^符號位 1.xx...xx 2^指數部分
2.The fractionIEEE 754 規定,有效數字的第一位默認總是1,不保存在64位浮點數之中。
也就是說,有效數字這時總是1.xx...xx的形式,其中xx..xx的部分保存在64位浮點數之中,最長可能為52位。因此,JavaScript 提供的有效數字最長為53個二進制位:
關于有效數字的第一位默認總是1,下面有提及,這里先了解一下: 正規化normalized:有效數位的最高位始終是1 非正規化denormalized: 當數值非常趨近0的時候(指數位全是0),逐漸失去精確度,這時候可以用有效數位的最高位是0. 例如2e-1022.
其中:%表示是二進制表示方式,f表示有效數字位的值,p表示指數位的值
2-1 Representing integers
編碼為整數提供了多少位?
有效數字有53個數字,1個在小數點之前,52個在小數點之后。當p = 52時,我們有一個53位的自然數。唯一的問題是最高位始終為1。也就是說,我們沒有全部位可供我們隨意使用。
分兩步去除這個限制:
首先,如果需要最高位為0的53位數字,0后面是默認的1,則設置p = 51。這樣有效數位的最低位將成為小數點之后的第一個小數數字,整數部分為0。以此類推,直到你在p = 0和f = 0,編碼數字1。
例如 1.xxxx...11 * 2e52 = 1xxxx...11 -> 1.xxxx...11 * 2e51 = 1xxxx...1.1 = 01xxxx...1
其次,對于全部53位,我們仍然需要表示零。 如何做到這一點在下一節中解釋。 請注意,由于符號是多帶帶存儲的,因此整數的幅度(絕對值)為53位。
3.The exponent3-1 Common exponent
根據標準,64位浮點數的指數部分的長度是11個二進制位,意味著指數部分的最大值是2047(2的11次方減1)。也就是說,64位浮點數的指數部分的值最大為2047。
因為指數位兩個指數值是保留的:最低的一個0和最高的一個2047(下文會有講解),為了支持負數指數部分,進行二進制數偏移,1023 ---> 0,0以下的全為負數。因此JavaScript 指數部分能夠表示的數值范圍為(-1023,1024),指數部分超出這個范圍的數無法表示。
(負數表示:取補碼 = 反碼 +1;符號位不變)
如果一個數大于等于2的1024次方,那么就會發生“正向溢出”,即 JavaScript 無法表示這么大的數,這時就會返回Infinity。
Math.pow(2, 1024) // Infinity
如果一個數小于等于2的-1075次方(指數部分最小值-1023,再加上小數部分的52位),那么就會發生為“負向溢出”,即 JavaScript 無法表示這么小的數,這時會直接返回0。
Math.pow(2, -1075) // 0
下面是一個實際的例子。
var x = 0.5; for(var i = 0; i < 25; i++) { x = x * x; } x // 0
上面代碼中,對0.5連續做25次平方,由于最后結果太接近0,超出了可表示的范圍,JavaScript 就直接將其轉為0。
JavaScript 提供Number對象的MAX_VALUE和MIN_VALUE屬性,返回可以表示的具體的最大值和最小值。
Number.MAX_VALUE // 1.7976931348623157e+308 Number.MIN_VALUE // 5e-324
3-2 Special exponent
指數位兩個指數值是保留的:
最低的一個(0)和最高的一個(2047)
2047的指數用于無窮大和NaN(非數字)值。
IEEE 754標準有許多NaN值,但JavaScript都將它們表示為單個值NaN。
指數0用于兩種情況:
(1)如果有效數位值是0,那么整數就是0。由于符號是分開存儲的,我們同時具有-0和+0。
(2)0的指數也用于表示非常小的數字(接近零)。 然后該有效數位的值必須是非零的,如果是正數,則通過計算該數字:
%0.f × 2^(e ? 1022)
這種表示被稱為非規范化。 先前討論的表示被稱為標準化。 可以以規范化方式表示的最小的正數(非零)數是:
%1.0 × 2^(e - 1022)
最大的非正規化數字是:
%0.1 × 2^(e - 1022)
因此,在標準化和非標準化數字之間可以完美切換。
特數值列表:
+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, 指數部分的范圍是:
?1023 < p < 1024
4.Decimal fraction并非所有小數都可以用JavaScript精確表示,如下所示:
????
0.1 + 0.2 // 0.30000000000000004
小數部分0.1和0.2都不能精確地表示為二進制浮點數。但是,與實際值的偏差通常太小而不能顯示。
加法導致偏差變得可見。看一個例子:
0.1 + 1 - 1 // 0.10000000000000009
????
表示十進制分數1/10來說是個挑戰,困難的部分是分母10。其分母的因子分解是2×5,指數只允許你用2的冪除整數,所以沒有辦法得到因子5。相反,將二進制小數表示為小數部分總是可能的。
4-1 Comparing decimal fractions
由于浮點數計算的結果不能保證精確,因此,當你使用具有小數值的浮點數數輸入時,不應直接比較它們。 相反,考慮舍入誤差的上限。 這樣的上界稱為機器epsilon。 雙精度的標準epsilon值是2^(-53)。
var epsEqu = function () { // IIFE, keeps EPSILON private var EPSILON = Math.pow(2, -53); return function epsEqu(x, y) { return Math.abs(x - y) < EPSILON; }; }();
上面的運行結果:
0.1 + 0.2 === 0.3 //false epsEqu(0.1+0.2, 0.3)//true5.The maximum integer
有效數含52位,但是有效數位前總有一個默認1,因此除零之外的所有53位數都可以表示,并且它有一個特殊值來表示零(連同零的一部分)。
所以浮點數能精確表示的范圍是: [2^(-53),2^53]
> Math.pow(2, 53) 9007199254740992 > Math.pow(2, 53) - 1 9007199254740991 > Math.pow(2, 53) - 2 9007199254740990
當超出這個范圍時,不能保證準確顯示:
> Math.pow(2, 53) + 1 9007199254740992
你可能疑惑,最高的整數不應該是是2^53-1的嘛?通常x位:表示最低數字是0,最高數字是2^X-1。例如,最高的8位數字是255.在JavaScript中,最高分數確實用于數字2^53-1,但可以準確表示2^53,這要歸功于指數的幫助--2^53是一個小數部分f = 0,指數p = 53(轉換后):
%1.f × 2p = %1.0 × 2^53 = 2^53
為什么有些大于2^53的數能被精確表示呢?看下面例子:
> 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能正常表示是因為指數可以使用。 每乘以2只是將指數遞增1并且不影響有效數位。 因此,就最大有效數位值而言,乘以2的冪不是問題。 為了明白為什么我們可以加2到2^53,但不是1,我們擴展了前面的表,其中53和54的附加位以及p = 53和p = 54的行:
查看行(p = 53),很明顯JavaScript數字可以將位53設置為1.但是由于小數f只有52位,所以位0必須為零。 因此,只有偶數x可以在2^53≤x<2^54的范圍內表示。在行(p = 54)中,該間距增加到4的倍數,在2^54≤x<2^55的范圍內:
> 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標準描述了五個特殊情況,其中一個不能計算精確的值:
(1)Invalid(不合法):執行了不合法操作。例如,計算負數的平方根。返回NaN。
Math.sqrt(-1) // 為NaN
(2)Division by zero(除以零):返回正負無窮。
3/0; // 無窮 -5/0; //-無窮
(3)Overflow(上溢):結果太大而無法表示。這意味著指數太高(p≥1024)。根據符號,有正面和負面溢出。返回正負無窮。
Math.pow(2,2048; // Infinity -Math.pow(2,2048; // -Infinity
(4)Underflow(下溢):結果太接近零來表示。這意味著指數太低(p≤-1023)。返回非規格化的值或零。
Math.pow(2,-2048); // 0
(5)Inexact(不精確):操作產生了不準確的結果 - 要保留的分數有太多有效數字。返回一個舍入結果。
0.1 + 0.2; // 0.30000000000000004 9007199254740992 + 1; //9007199254740992
#3和#4是關于指數,#5是關于有效數。 #3和#5之間的區別非常微妙:在第五個例子中,我們超過了有效數的上限(這將是整數計算中的溢出)。但只有超過指數的上限才稱為IEEE 754中的溢出。
三 后記讓我們回顧一下前言中的三個問題:
問題一:為什么0.1+0.2===0.3 //false
經過對正文部分的閱讀,你已經知道浮點精確度的問題,對于超出浮點精確度的部分計算機是無法管理的。
下面讓我們回到計算機對十進制數用二進制數表示方法上:
對于二進制小數,小數點右邊能表達的值是 1/2, 1/4, 1/8, 1/16, 1/32, 1/64, 1/128 ... 1/(2^n)
現在問題來了, 計算機只能用這些個 1/(2^n) 之和來表達十進制的小數。
我們來試一試如何表達十進制的 0.2 吧。
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 這結果不錯 0.00110011 = 1/8+1/16+1/128+1/256 = 0.19921875
已經很逼近了,計算機不可能提供無限的空間讓程序去存儲這些二進制小數的, 就這樣吧。 用二進制小數沒法精確表達10進制小數!
對于不能精確表示的小數部分的計算,無法保證一致的正確結果(小數結果非常接近十進制正確值了,但是會取相對靠近這個正確值的能用二進制表示的那個十進制數)。
那么如何解決算數比較的問題?
var epsEqu = function () { // IIFE, keeps EPSILON private var EPSILON = Math.pow(2, -53); return function epsEqu(x, y) { return Math.abs(x - y) < EPSILON }; }();
問題二: 浮點數精確表示的范圍問題以及超出精確范圍后哪些能精確表示的問題
由正文部分的內容,我們知道了總共有53(52+1)位有效數位,所以
浮點數精確表示的范圍:[-2e53,2e53]
超出精確范圍后哪些能精確表示:
+-*/ 計算有效數位超出范圍的位上含有1的則不能精確表示計算結果(1會被舍棄,影響結果),超出位上的值全是0的可以準確的表示計算結果(指數位數值變化后舍棄有效數字位最后一位0,對結果無影響)。
一般來說如果是53位,那么最大值應該是2e53-1,但是如上面解釋的那樣,2e53是可以被精確表示的,所以浮點數精確表示的范圍:[-2e53,2e53]。
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
參考鏈接:
浮點數計算不精確
阮一峰--數值
Javascript and more
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/94666.html
摘要:大小寫的不同分別表示不同的變量。本質由一組無序的名值對組成的。字符串中第一個小數點有效,第二個無效,后面的字符串會被忽略。注意雙引號開頭,必須以雙引號結尾,單引號也是如此轉義字符表示非打印字符或具有其他用途的字符。 JavaScript高級程序設計(第3版)讀書筆記 1.區分大小寫: 變量、函數名和操作符都要區分大小寫。大小寫的不同分別表示不同的變量。 2.標識符: 變量、函數、屬性...
摘要:高程讀書筆記第三章語法中的一切變量函數名和操作符都區分大小寫。建議無論在任何情況下都指定基數函數與函數類似。返回對象的字符串數值或布爾值表示。 JS高程讀書筆記--第三章 語法 ECMAScript中的一切(變量、函數名和操作符)都區分大小寫。 不能把關鍵字、保留字、true、false和null用做標識符。 嚴格模式是為JavaScript定義了一種不同的解析與執行模型。在嚴格模式...
摘要:下面就讓我們來一起深入了解下,為以后的策馬奔騰做好鋪墊。整數整數,可以通過十進制,八進制,十六進制的字面值來表示。對前面定義的八進制和十六進制數值進行運算浮點數浮點數其實就是我們通常所說的小數,所以一定有個小數點。 Number 類型作為 JS 的基本數據類型之一,被應用在程序中的各種場景,其重要性就如數字對于我們日常生活。下面就讓我們來一起深入了解下,為以后的策馬奔騰做好鋪墊。 定義...
閱讀 2296·2021-10-09 09:41
閱讀 1750·2019-08-30 15:53
閱讀 992·2019-08-30 15:52
閱讀 3448·2019-08-30 11:26
閱讀 773·2019-08-29 16:09
閱讀 3428·2019-08-29 13:25
閱讀 2264·2019-08-26 16:45
閱讀 1937·2019-08-26 11:51