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