摘要:譯者注規范化就是把小數點放在第一個非零數字的后面總結當指數的范圍是十進制分數不是所有的十進制分數都能夠非常精確的表示例如和都不能夠被精確的表示成二進制浮點數。相同的,也不能被精確表示成一個十進制分數,它大概能被表示成。
在JavaScript中所有的數字都是浮點數,本篇文章將介紹這些浮點數在JavaScript內部是怎樣被轉為64位二進制的。
我們會特別考慮整數的處理,所以讀完本篇之后,你會理解為什么會有以下結果發生:
> 9007199254740992 + 1 9007199254740992 > 9007199254740992 + 2 90071992547409941. JavaScript的數字
JavaScript數字全部是浮點數。 根據?IEEE 754標準中的64位二進制(binary64), 也稱作雙精度規范(double precision)來儲存。從命名中可以看出,這些數字將以二進制形式,使用64個字節來存儲。這些字節按照以下規則分配:
0 - 51 字節是 分數f(fraction?)
52 - 62 字節是 指數(exponent?)
63 字節 是 標志位 (sign)
標志位 (s, sign) | 指數(e, exponent?) | 分數(f, fraction?) |
(1 bit) | (11 bit) | (52 bit) |
63 | 62 | 51 |
52 | 0 |
他們按照以下規則表示一個數字: 如果標志位是0, 表示這個數字為正數,否則為負數。粗略來說,分數f用來表示數字的‘數碼’(0-9),指數表示這個數字的‘點’在哪里。接下來我們會使用二進制(雖然這并不是通常的浮點數表示方式)。并用一個%作為前綴來標識。雖然JavaScript數字是以二進制保存的,但輸出(打?。r通常是以10進制顯示. 接下來的例子,我們也會沿用這一規則。
2. 分數f下表是一種表示非負浮點數的方法:
尾數 (小數點后面的數,significand?或 mantissa ) 以自然數字的形式保存‘數碼’,指數決定需要往左(負指數)或者右(正指數)移多少位。再忽略位數,這個JavaScript數字就是 有理數1.f乘以2p。
譯者注: 這里指數用p而不是e來表示是因為e是一個偏移量,第三點會詳細說明
比如以下例子:
f?= %101,?p?= 2 | Number: %1.101 × 22?= %110.1 |
f?= %101,?p?= ?2 | Number: %1.101 × 2?2 = %0.01101 |
f?= 0,?p?= 0 | Number: %1.0 × 20?= %1 |
需要多少位來編碼一個整數呢? 尾數共有53個數碼,1個在‘點’的前面,52個在后面,如果p=52,我們就有一個53位的自然數,現在的問題是最高位總是為1,也就是說我們不能隨便的使用所有的位。要去掉這個限制,我們需要2步,首先. 如果需要最高位是0,第二位是1的53位的數字,將p設置為51,這時分數f最低位變成了‘點’后面的第一個數碼,也就是整數0。按照這個規律,直到指數p=0,分數f=0,這就是數字1的編碼。
52 | 51 | 50 | ... | 1 | 0 | (bits) | |
p=52 | 1 | f51 | f50 | ... | f1 | f0 | |
p=51 | 0 | 1 | f51 | ... | f2 | f1 | f0=0 |
... | ... | ... | ... | ... | ... | ... | ... |
p=0 | 0 | 0 | 0 | ... | 0 | 1 | f51=0, etc. |
其次,對于完整的53位數字,我們還需要表示0,我們將在下一段詳細介紹。
需要注意的是,我們可以表示完整的53位整數,因為標志位是另外儲存的。
指數占11位,它可以表示0-2047(211-1), 為了支持負指數,JavaScript使用偏移二進制來編碼: 1023表示0,小于它的為負,大于它的為正。這就意味著,減去1023才能得到正常點數字。因此我們之前使用的變量p就等于e-1023,也就是尾數乘以2e-1023
例如:
%00000000000 0 → ?1023 (最小的數字) %01111111111 1023 → 0 %11111111111 2047 → 1024 (最大的數字) %10000000000 1024 → 1 %01111111110 1022 → ?1
如果需要一個負數,只需要顛倒一下它的位數,再減一
3.1 特殊的指數有2個指數是保留位。最小的0,和最大的2047. 指數2047表示無窮大(infinity)和 NaN(非數字)值。IEEE 754標準有很多非數字值, 但是JavaScript把他們都表示為NaN。指數為0時有兩個意思。1. 如果分數f也是0,表示這個數字就是0.因為標志位是多帶帶存儲的。所以我們有+0和-0;
然后指數0也可以用來表示非常小的數字(接近0)。此時分數f必須為非0,而且,如果這個數字是由%0.f?× 2?1022算出來的,這個表示方式叫做非規范化,而之前我們討論的表示方式叫規范化。最小的非0正數可以被規范化為: %1.0 × 2?1022。 最大的非規范化數字為: %0.1 × 2?1022, 所以,從規范化到非規范化是過渡是平滑的。
譯者注: 規范化就是把小數點放在第一個非零數字的后面
(?1)s?× %1.f?× 2e?1023 | normalized, 0 |
(?1)s?× %0.f?× 2e?1022 | denormalized,?e?= 0,?f?> 0 |
(?1)s?× 0 | e?= 0,?f?= 0 |
NaN | e?= 2047,?f?> 0 |
(?1)s?× ∞ (infinity) | e?= 2047,?f?= 0 |
當p?=?e?? 1023, 指數的范圍是?1023 4. 十進制分數
不是所有的十進制分數都能夠非常精確的表示, 例如:
> 0.1 + 0.2 0.30000000000000004
0.1和0.2都不能夠被精確的表示成二進制浮點數。但是這個偏差通常非常非常小,小到不能夠被表示出來,加法可以使這個偏差變得可見:
> 0.1 + 1 - 1 0.10000000000000009
表示0.1相當于表示一個分數110,難的部分在于分母是10,10素數分解是2*5. 而指數只能分解2,所以沒有辦法得到5。相同的, 1/3也不能被精確表示成一個十進制分數,它大概能被表示成0.333333。
但相對的。要用十進制表示一個2進制分數卻是永遠可行的,值需要使用足夠的2(每個10都有1個2)。
%0.001 =?1/8?=?1/2 × 2 × 2?=?5 × 5 × 5/(2×5) × (2×5) × (2×5)?=?125/10 × 10 × 10?= 0.1254.1 對比十進制分數
因此,當你要處理10進制分數,不要直接去比較他們,先想一想,它可能會有一個上限,比如有一個上限叫做機器最小數?machine 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. 最大的整數
“x 是最大的整數”這句話是什么意思呢?它的意思是說,任意整數n在?0 ≤?n?≤?x?范圍內都是可以被表示的。也就是說如果大于x,將無法表示。比如253?。任何比它小的數字都可以被表示。
> Math.pow(2, 53) 9007199254740992 > Math.pow(2, 53) - 1 9007199254740991 > Math.pow(2, 53) - 2 9007199254740990 但比它大的就不行 > Math.pow(2, 53) + 1 9007199254740992
關于253?這個上限,有一些很令人驚奇的表現。我們將用一些問題來解釋這些現象。你要記住的是,這個上限是分數f的上限,指數e部分其實還有空間。
為什么是53位呢?你有53位來表示數的大小,除去標志位。但是分數f卻是由52位組成的,這是為什么呢。從前面的文章可以看出,指數e從第53位開始,它會移動分數f,所以這個53位的數字(除了0)可以被表示出來,并且有一個特別的數字去表示0(并且分數f也是0).
為什么最大的數不是253?1??通常來說,x位就說明最小數是0,最大值是2x?1.?比如8位數字最大是255。而在JavaScript里,最大的分數f確實是253?1,但253?也可以被表示出來,因為有指數e的幫助。它只要讓分數f等于0,指數e等于53即可。
%1.f?× 2p?= %1.0 × 253?= 253
為什么大于253就不能表示了呢?例如:
> 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
253×2?可以表示正確,因為指數e還可以用,乘以2僅僅需要指數e加一,而不影響分數f。所以乘以2的冪不是問題,只要分數f沒有超過上限,那為什么2加253也可以表示正確,1卻不可以呢,我們擴大一下之前的,加上53 和54位來看看。
54 | 53 | 52 | 51 | 50 | ... | 2 | 1 | 0 | (bits) | |
p=54 | 1 | f51 | f50 | f49 | f48 | ... | f0 | 0 | 0 | |
p=53 | 1 | f51 | f50 | f49 | ... | f1 | f0 | 0 | ||
p=52 | 1 | f51 | f50 | ... | f2 | f1 | f0 |
看p=53的那一行,它應該是一個JavaScript數字,53位設置成了1,但是因為它的分數f只有52位,而0位必須位0,而只有253?≤?x?< 254中的偶數數字x可以被表示。在p=54時,這個空間增加到乘以4,在?254?≤?x?< 255: 中。
> 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 180143985094819886. IEEE 754 的例外
IEEE 754標準描述了5中例外?, 當出現這些例外,就無法算出準確的數字。
1. 無效 : 進行一個無效操作。例如,給一個負數開平方,返回NaN
> Math.sqrt(-1) NaN
2. 除以0 : 返回正或者負的infinity(無窮大)
> 3 / 0 Infinity > -5 / 0 -Infinity
3. 溢出(overflow) : 結果太大,無法表示。這時是指數已經太大,?(p?≥ 1024).根據標志位,正或者負溢出,返回正或者負的infinity(無窮大)。
> Math.pow(2, 2048) Infinity > -Math.pow(2, 2048) -Infinity
4. 潛流(underflow): 結果太接近于0,這時是指數已經太小(p?≤ ?1023).?返回一個非規范化的數字,或者0.
> Math.pow(2, -2048) 0
5. 不精確(Inexact): 一個操作返回不精確的結果 - 有太多有意義的數字需要分數f去存,那就返回一個四舍五入的結果
> 0.1 + 0.2 0.30000000000000004 > 9007199254740992 + 1 9007199254740992
上面的第三點和第四點是關于指數的,第五點是關于分數f的,第三點和第五點的差別非常小,第五點的第二個例子,我們已經接近了分數f的最大值(這也可以算是一個溢出操作)。但根據?IEEE 754只有超過了指數的范圍才算溢出。
7. 結論本篇文章中,我們觀察了JavaScript是怎樣把浮點數存進64位中的。它之所以這么做是根據?IEEE 754 標準中的雙精度。因為我們常常忘記,JavaScript對于分母質因分解不僅包含2的數字 是無法精確表示的。比如0.5(1/2),是可以精確表示的,但0.6(3/5)就不能。我們很容易忘記一個整數是由標志位,分數f,指數3部分組成,然后就會面對Math.pow(2, 53) + 2?可以計算正確,而Math.pow(2, 53) + 1會計算錯誤的問題。
8. 資源和引用? “IEEE Standard 754 Floating-Point” - Steve Hollasch.
? “Data Types and Scaling (Fixed-Point Blockset)” in the MATLAB documentation.
? “IEEE 754-2008” on Wikipedia
本文也同時是JavaScript 數字系列?, 它包含:
JavaScript中的數字顯示
JavaScript中的NaN 和 Infinity
JavaScript的兩種0
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/108771.html
摘要:但這個數值并不安全從到中間的數字并不連續,而是離散的。對象中的常量表示與可表示的大于的最小的浮點數之間的差值。絕對值的最大安全值。尋找奇怪現象的原因為什么結果是與的逼近算法類似。 js 中的 number 為何很怪異 聲明:需要讀者對二進制有一定的了解 對于 JavaScript 開發者來說,或多或少都遇到過 js 在處理數字上的奇怪現象,比如: > 0.1 + 0.2 0.30000...
摘要:例如指數實際值為,在單精度浮點數中的指數域編碼值為,即采用指數的實際值加上固定的偏移值的辦法表示浮點數的指數,好處是可以用長度為個比特的無符號整數來表示所有的指數取值,這使得兩個浮點數的指數大小的比較更為容易。 自己整理、設計的,轉載請注明原帖。先從這個demo看起:http://alvarto.github.io/Visu... 數軸 showImg(http://segmentfa...
閱讀 736·2023-04-25 19:28
閱讀 1397·2021-09-10 10:51
閱讀 2392·2019-08-30 15:55
閱讀 3416·2019-08-26 13:55
閱讀 3005·2019-08-26 13:24
閱讀 3331·2019-08-26 11:46
閱讀 2759·2019-08-23 17:10
閱讀 1422·2019-08-23 16:57