今天聊一點計算機的基礎(chǔ)——浮點數(shù),具體我們探討 IEEE754 格式的浮點數(shù),大多數(shù)語言都采用該格式來表示小數(shù)吹散,通常又分為單精度和雙精度浮點數(shù)造锅。
在 JavaScript 中皂贩,不區(qū)分整形和小數(shù),唯一表示數(shù)值的類型 Number 就是采用 IEEE754 格式中的雙精度浮點數(shù)來表示的会傲。
因為單精度和雙精度浮點數(shù)在原理上完全一致混卵,所以本文只集中分析雙精度浮點數(shù)。我會依次說一下原理恶阴、定義和一些結(jié)論诈胜。
原理
我們都知道任何一個數(shù)都可以用科學計數(shù)法,例如:
1.234 * 10 ^ 2 // => 123.4
其中冯事,^
代表指數(shù)焦匈,下同。
同理昵仅,我們也可以不局限與 10 進制缓熟,采用任意進制 R,指數(shù)用 e 表示摔笤,m 表示基數(shù)够滑,正負數(shù)用 (-1) ^ S
表示,其中 S 為 0 或 1吕世,結(jié)果用 N 表示彰触,于是:
N = (-1) ^ S * m * R ^ e
這就是我們的原理,十分簡單寞冯。另外很容易得出渴析,對了任何非 0 的數(shù),m 都可以限定在大于等于 1 且小于 R 的范圍內(nèi)吮龄,即 [1, R)俭茧。當 R = 2 的時候,m 取值為 [1, 2)漓帚∧刚可以如下表示:
m = 1 + M (0 <= M < 1)
定義
雙精度浮點數(shù)就是采用上面的原理,底數(shù) R = 2
尝抖。
雙精度浮點數(shù)用 8 個字節(jié)表示毡们,也就是 64 bit。例如數(shù)值 100.25 在內(nèi)存中的情況如下:
0,10000000101,100100010~0 // ~代表了若干個 0
為了分析昧辽,我將 64 bit 分為 3 個部分衙熔。
第一部分是符號位(S),占 1 bit搅荞,代表符號红氯,正數(shù) 0框咙,負數(shù)1。當前例子中痢甘,符號位為 0 表示這是一個正數(shù)喇嘱。
第三部分是尾數(shù)位(M),占 52 bit塞栅,代表基數(shù)的小數(shù)部分者铜,采用二進制表示。當前例子中 100100010~0
換算成十進制的小數(shù)為 1/2 + 1/16 + 1/256 = 0.56640625
放椰。
第二部分是階碼位(E)作烟,占 11 bit,代表指數(shù)部分庄敛。
因為基數(shù)始終是
1.xxx
的小數(shù)俗壹,所以為了表示 0,規(guī)定當階碼和尾碼全是 0 的時候藻烤,表示值 0 绷雏。因為前面還有符號位,所有有 +0 和 -0 之分怖亭。同時規(guī)定涎显,當階碼全為 1 ,尾碼全為0 的時候兴猩,表示 ∞期吓。同理,有 +∞ 和 -∞倾芝。
因為
2^11 = 2048
讨勤,排除上面兩種特殊情況,能表示的值為1~2046
晨另,但是為了表示小數(shù)潭千,我們指數(shù)需要用到負數(shù),因此我們設(shè)置一個偏碼值1023
借尿,指數(shù)的實際值e = E - 1023
刨晴,例如上面實例中[10000000101]2 = 1029
,因此實際的指數(shù)值e = 6 = 1029 - 1023
;
講解完畢路翻,關(guān)于浮點數(shù)的格式推導(dǎo)出真實值的公式也就如下:
(-1) ^ S * (1 + M) * 2 ^ (E - 1023) = N
對應(yīng)我們上面的示例:
(-1) ^ 0 * (1 + 0.56640625) * 2 ^ (1029 - 1023) = 1.56640625 * 2 ^ 6 = 100.25
是不是很簡單狈癞,沒看懂的童鞋還可以再看2遍。
結(jié)論
指數(shù) e 的取值范圍為
(1-1023) ~ (2046 - 1023)
茂契,也就是-1022 ~ 1023
蝶桶。基數(shù)的小數(shù)部分 M 的取數(shù)值范圍為
0 ~ (1 - 1 / (2 ^ 52))
,大約是0 ~ 1
掉冶。
意味著真竖,能表示數(shù)值的范圍是-(1 + 1 - 1 / (2 ^ 52)) * 2 ^ 1023 ~ (1 + 1 - 1 / (2 ^ 52)) * 2 ^ 1023
儡蔓,即-1.79e+308 ~ 1.79e+308
。其中最大值對應(yīng)的是 JavaScript 中的Number.MAX_VALUE
疼邀。當我們用上面的公式表示一個數(shù)的時候,一旦確定了指數(shù) e 召锈,同時也確定了在該指數(shù)下所表示值的最小精度旁振,即
2 ^ (e - 52)
。因此雙精度浮點數(shù)能夠表示的最小精度為2 ^ ( -1022 - 52) = 2 ^ -1074 = 5e-324
涨岁,也就是我們 JavaScript 中Number.MIN_Value
的值拐袜。同理,當我們要精確的表示一個整數(shù)時梢薪,也就是我們精度值要小于 1蹬铺,簡單倒推可得此時的e<= 52
,也就是說秉撇,當e = 52
時我們依舊能夠精確的表示一個整數(shù)甜攀。因此 JavaScript 能夠精確表示一個整數(shù)的最大值為(1 + (1 - 1 / (2 ^ 52))) * 2 ^ 52 = 2 ^ 53 - 1 = 9007199254740991
,能精確表示一個整數(shù)的最小值自然就是-9007199254740991
琐馆,對應(yīng)我們 JavaScript 中的Number. Number.MAX_SAFE_INTEGER
和Number.MIN_SAFE_INTEGER
规阀。
綜上,我們依次闡述了雙精度浮點數(shù)的原理瘦麸、定義和一些結(jié)論谁撼。而單精度浮點數(shù)只有在階碼和尾數(shù)的位數(shù)有所不同,原理完全一致滋饲,自然不用贅述厉碟。
最后,依舊是如果發(fā)現(xiàn)什么沒有闡述清楚或者有問題的地方屠缭,歡迎反饋箍鼓。