許多的語言都有好幾種不同的數(shù)字類型,比如C++,分別有int,float和double,外加各種變種long int, unsigned等等,實(shí)在是繁多,不過,JavaScript中,就只有一種數(shù)字類型——number
盲憎。
比如,我們?cè)跒g覽器的console中敲下下列代碼.
![](http://static.zybuluo.com/oshimayuko/xg6jyryno3k0g85tkam05ozt/image_1b6b9pi19l3n36a1kg81eb21bf49.png)
實(shí)際上,所有的數(shù)字類型,在JavaScript中,都是雙倍精度的浮點(diǎn)數(shù)類型,就和前面提到的C++的double
一樣.按照標(biāo)準(zhǔn),就是每一個(gè)數(shù)字,都是使用64位(8字節(jié))來存儲(chǔ).
要理解JavaScript中數(shù)字存儲(chǔ)特性,我們首先需要復(fù)習(xí)一下,在計(jì)算機(jī)中,是如何使用二進(jìn)制來保存浮點(diǎn)數(shù)的.
浮點(diǎn)數(shù)的二進(jìn)制表示
現(xiàn)今計(jì)算機(jī)浮點(diǎn)數(shù)的表示,都是采用了IEEE754標(biāo)準(zhǔn),一個(gè)由Intel主導(dǎo)開發(fā)的標(biāo)準(zhǔn),有興趣的同學(xué)可以了解一下上個(gè)世紀(jì)80年代的各種不同公司對(duì)于浮點(diǎn)數(shù)的表示,不過現(xiàn)在都已經(jīng)按照標(biāo)準(zhǔn)來進(jìn)行處理了.
任意一個(gè)浮點(diǎn)數(shù)都可以表示成如下的形式.
![](http://static.zybuluo.com/oshimayuko/y8ca619p212easivetfao8pk/image_1b6ba4e96ed71ae6rau1o8o1b6jm.png)
- 符號(hào)位(sign) (-1)^s表示的是符號(hào)位,當(dāng)s=0時(shí),V是正數(shù),當(dāng)s=1時(shí),v為負(fù)數(shù).
- 有效位(significand) M表示有效數(shù)字,范圍是[1,2)或者是[0,1)
-
指數(shù)(exponent) 2^E表示指數(shù)位.E的作用是對(duì)浮點(diǎn)數(shù)加權(quán)。
這種表示方式非常適合用于處理非常大或者非常接近0的數(shù)字.
了解的公式的表示,接下來,我們就可以對(duì)浮點(diǎn)數(shù)的位按照上述的三個(gè)變量進(jìn)行劃分,分別對(duì)其進(jìn)行編碼: - 1個(gè)單獨(dú)的符號(hào)位s用于編碼符號(hào)s.
- k位階碼字段,用以編碼指數(shù)E.
- n位冪字段,用以編碼尾數(shù)M.
下面,我們用C語言中的單精度浮點(diǎn)數(shù)(float)以及雙精度浮點(diǎn)數(shù)(double)進(jìn)行說明.
單精度浮點(diǎn)數(shù)使用32bit來進(jìn)行存儲(chǔ),分別s=1, k=8以及n=23.其存儲(chǔ)結(jié)構(gòu)如下圖所示
![](http://static.zybuluo.com/oshimayuko/uqug02fhnss64zgjvhdgmim1/image_1b6dbsggg7ncgqcpnv1s5q1rde9.png)
32位的存儲(chǔ)空間被分割成了三個(gè)部分,也就是對(duì)應(yīng)上面所說的s,k以及n.
雙精度浮點(diǎn)數(shù)則是使用32位進(jìn)行存儲(chǔ),分別是s=1, k=11, n=52.
![](http://static.zybuluo.com/oshimayuko/3grgu4b2hx5jjb79veamdk1o/image_1b6j0416r1jf911363q1d6o2qvm.png)
為了簡(jiǎn)單起見,接下來使用單精度浮點(diǎn)數(shù)來進(jìn)行說明.
單精度浮點(diǎn)數(shù)一共表示4種類型的數(shù)值,分別是:
-
規(guī)范化(Normalized)
-
非規(guī)范化(Denormalized)
-
無窮(Infinity)
- 非數(shù)字(NaN)
嚴(yán)格來說,Infinity以及NaN屬于同一類,因?yàn)樗麄兊膋相同,都是1,只是n上面的值不一樣,一個(gè)是全是0,另外一個(gè)全是1.
具體上就不展開講了,以后會(huì)另外開一篇新的文章來進(jìn)行詳細(xì)講述計(jì)算機(jī)底層對(duì)于浮點(diǎn)數(shù)的存取.現(xiàn)在仍舊是Javascript的主場(chǎng).我們要知道一點(diǎn)就是,根據(jù)浮點(diǎn)數(shù)的二進(jìn)制表示形式,有一部分值是無法完全使用二進(jìn)制準(zhǔn)確表示的.就好比1/3這個(gè)值,在十進(jìn)制上面,我們只能不斷的用0.3333333來無限循環(huán)表示,但是計(jì)算機(jī)表示一個(gè)浮點(diǎn)數(shù)的位是有限的,所以需要對(duì)多余的位進(jìn)行截取,這一點(diǎn)就是誤差的來源.
知道了計(jì)算機(jī)中浮點(diǎn)數(shù)的二進(jìn)制表示,我們都不難理解一些問題了,比如,JavaScript中的數(shù)字范圍是從-2^53
到2^53
.因此,JavaScript中的數(shù)字是非常適合用以做數(shù)學(xué)運(yùn)算的,因?yàn)榉秶浅5拇?不容易導(dǎo)致溢出的問題.
位運(yùn)算操作
許多的數(shù)學(xué)運(yùn)算,都是以浮點(diǎn)數(shù)的形式直接計(jì)算的,比如:
0.1 * 1.9 // 0.19
-99 + 100 // 1
21 - 12.3 //8.7
但是,對(duì)于位運(yùn)算則是比較特別,對(duì)于位運(yùn)算,JavaScript會(huì)首先把浮點(diǎn)數(shù)轉(zhuǎn)換成32位的整型來進(jìn)行處理,而不是直接對(duì)浮點(diǎn)型進(jìn)行操作.準(zhǔn)確的來說,是轉(zhuǎn)換成32位
,大端序
, 補(bǔ)碼
的整型.(這里涉及到比較多操作系統(tǒng)的概念).
舉個(gè)例子
8 | 1 // 9
這個(gè)看起來很簡(jiǎn)單的操作,8與1進(jìn)行或運(yùn)算,實(shí)際上經(jīng)歷了好幾步的運(yùn)算,才得到結(jié)果9,并不是一下子得出結(jié)果的.
正如前面所說,8和1一開始是64位的double,但是,他們?cè)谖贿\(yùn)算的時(shí)候,會(huì)被轉(zhuǎn)換成32位的整型,對(duì)于數(shù)字8,則會(huì)轉(zhuǎn)換成00000000000000000000000000001000
,我們可以驗(yàn)證一下
(8).toString(2); // "1000"
同理,1則會(huì)轉(zhuǎn)換成00000000000000000000000000000001
,把這兩個(gè)數(shù)分別按位進(jìn)行或運(yùn)算
00000000000000000000000000001000
00000000000000000000000000000001
--------------------------------
00000000000000000000000000001001
就會(huì)得到1001這一個(gè)數(shù)字,我們使用parseInt("1001", 2)
對(duì)其進(jìn)行轉(zhuǎn)換,結(jié)果就是9.
所有的位運(yùn)算,都是按照上面所說的這一種方式來進(jìn)行處理的.
- 把操作數(shù)轉(zhuǎn)換成32位整型
- 按位進(jìn)行位運(yùn)算
- 把得到的結(jié)果轉(zhuǎn)換會(huì)JavaScript浮點(diǎn)型屬性.
因?yàn)檫@些轉(zhuǎn)換都依賴于JavaScript的引擎,所以有部分引擎經(jīng)過優(yōu)化,就會(huì)把部分算術(shù)運(yùn)算的操作數(shù)用整型來存儲(chǔ),從而避免了一些無謂的轉(zhuǎn)換.
浮點(diǎn)數(shù)運(yùn)算,可靠嗎?
說了這么多,什么64位雙精度浮點(diǎn)數(shù),計(jì)算機(jī)儲(chǔ)存的方式等等,那么,JavaScript中的浮點(diǎn)數(shù)運(yùn)算,可靠嗎?先來看一個(gè)例子:
![](http://static.zybuluo.com/oshimayuko/v6n16tqll81awl0hzm8lw0mx/image_1b6bh6l3710201fah18gl1nc81krk13.png)
為什么上面得出的結(jié)果不是0.3,而是后面跟了一大堆0,后面還有個(gè)4?前面我們談到了浮點(diǎn)數(shù)的二進(jìn)制存儲(chǔ)方式,我們知道,計(jì)算機(jī)僅僅可以使用二進(jìn)制無限的接近部分浮點(diǎn)數(shù),但是無法完全接近,即便64位浮點(diǎn)數(shù)以及可以很高精度的接近了,但是仍舊會(huì)產(chǎn)生誤差.浮點(diǎn)數(shù)的算術(shù)運(yùn)算僅僅可以產(chǎn)生一個(gè)近似值,這個(gè)近似值接近于真實(shí)值.
有一個(gè)比較悲觀的事實(shí)就是,這些錯(cuò)誤會(huì)隨著一系列的計(jì)算而累加起來,從而導(dǎo)致偏差越來越多,結(jié)果越來越不精確.
舉個(gè)例子,我們都知道
(x + y) + z = x + (y + z)
但是,這一點(diǎn)在計(jì)算機(jī)中,可不一定會(huì)成立.
![](http://static.zybuluo.com/oshimayuko/8817qp09tom2lgxz12i6sz0o/image_1b6bhlvsk15jg1cev1lt2qnoi2g1g.png)
在部分計(jì)算機(jī)無法精確表達(dá)的浮點(diǎn)數(shù)產(chǎn)生的時(shí)候,就會(huì)導(dǎo)致誤差的產(chǎn)生.
這種誤差短期內(nèi)還好,但是長(zhǎng)期來說,是不可忍受的,假設(shè)是銀行或者金融機(jī)構(gòu)使用node作為服務(wù)端進(jìn)行處理,日積月累,就會(huì)發(fā)生許多的問題.
浮點(diǎn)數(shù)運(yùn)算是不準(zhǔn)確的,那么我們應(yīng)該怎么避免這個(gè)問題呢?
一個(gè)很簡(jiǎn)單的方法,就是避免浮點(diǎn)數(shù)的運(yùn)算,把所有的運(yùn)算都使用整型來進(jìn)行.因?yàn)檎偷倪\(yùn)算是沒有省略近似的.例如,上面那個(gè)例子,我們換成這個(gè)
![](http://static.zybuluo.com/oshimayuko/vsu9gl4jac4xn7b07aj5hh90/image_1b6ij6ajj1i8vmr56lr1dru16fc9.png)
如果使用整型來進(jìn)行算術(shù)運(yùn)算,就不會(huì)產(chǎn)生上面的問題了,從而也保證了運(yùn)算的精確性.
總結(jié)
- Javascript的數(shù)字類型是雙精度浮點(diǎn)型.
- 在Javascript中,整型是double的一個(gè)子集,而不是一個(gè)獨(dú)立的數(shù)據(jù)類型.
- Javascript在進(jìn)行位運(yùn)算的時(shí)候會(huì)把數(shù)字轉(zhuǎn)換成32位整型來進(jìn)行處理.
- 浮點(diǎn)數(shù)運(yùn)算是不準(zhǔn)確的,擁有許多的局限性.