本文最先在個人我的博客《咀嚼之味》發佈:http://jerryzou.comjavascript
若是你的第一門編程語言不是 JavaScript,而是 C++ 或 Java,那麼一開始你大概會看不慣 JavaScript 的數字類型。在 JavaScript 中的數字類型是不區分什麼 Int,Float,Double,Decimal 的。咳咳,我說的固然是在 ES6 以前的 JS,在 ES6 的新標準中提出了像 Int8Array 這樣新的數據類型。不過這不是本文敘述的重點,暫且就不談啦。本文將更着重地談 JS 的數字類型以及做用於它的位操做符,而關於包裝對象 Number 的更多瞭解能夠看拔赤翻譯的《JavaScript設計模式》java
實際上,JavaScript的數字類型的本質就是一個基於 IEEE 754 標準的雙精度 64 位的浮點數。按照標準,它的數據結構如圖示這樣:由1位符號位,11位指數部分以及52位尾數部分構成。git
在浮點數中,數字一般被表示爲:github
(-1)sign × mantissa × 2exponent編程
而爲了將尾數規格化,並作到儘可能提升精確度,就須要把尾數精確在 [1,2)
的區間內,這樣即可省去前導的1。好比:設計模式
11.101 × 23 = 1.1101 × 24
0.1001 × 25 = 1.001 × 24安全
而且標準規定指數部分使用 0x3ff 做爲偏移量,也就有了雙精度浮點數的通常公式:markdown
(-1)sign × 1.mantissa × 2exponent - 0x3ff數據結構
舉一些例子,應該能幫助你理解這個公式:app
3ff0 0000 0000 0000 = 1 c000 0000 0000 0000 = -2 3fd5 5555 5555 5555 ~ 1/3 0000 0000 0000 0000 = 0 8000 0000 0000 0000 = -0 7ff0 0000 0000 0000 = 無窮大 ( 1/0 ) fff0 0000 0000 0000 = 負無窮大 ( 1/-0 ) 7fef ffff ffff ffff ~ 1.7976931348623157 x 10^308 (= Number.MAX_VALUE) 433f ffff ffff ffff = 2^53 - 1 (= Number.MAX_SAFE_INTEGER) c33f ffff ffff ffff = -2^53 + 1 (= Number.MIN_SAFE_INTEGER)
得益於尾數省略的一位「1」,使用雙精度浮點數來表示的最大安全整數爲 -253+1 到 253-1 之間,因此若是你僅僅使用 JavaScript 中的數字類型進行一些整數運算,那麼你也能夠近似地將這一數字類型理解爲 53 位整型。
熟悉 C 或者 C++ 的同窗必定對位操做符不陌生。位操做符最主要的應用大概就是做爲標誌位與掩碼。這是一種節省存儲空間的高明手段,在曾經內存的大小以 KB 爲單位計算時,每多一個變量就是一份額外的開銷。而使用位操做符的掩碼則在很大程度上緩解了這個問題:
#define LOG_ERRORS 1 // 0001 #define LOG_WARNINGS 2 // 0010 #define LOG_NOTICES 4 // 0100 #define LOG_INCOMING 8 // 1000 unsigned char flags; flags = LOG_ERRORS; // 0001 flags = LOG_ERRORS | LOG_WARNINGS | LOG_INCOMING; // 1011
由於標誌位通常只須要 1 bit,就能夠保存,並無必要爲每一個標誌位都定義一個變量。因此按上面這種方式只使用一個變量,卻能夠保存大量的信息——無符號的 char 能夠保存 8 個標誌位,而無符號的 int 則能夠同時表示 32 個標誌位。
惋惜位操做符在 JavaScript 中的表現就比較詭異了,由於 JavaScript 沒有真正意義上的整型。看看以下代碼的運行結果吧:
var a, b; a = 2e9; // 2000000000 a << 1; // -294967296 // fxck!我只想裝了個逼用左移1位給 a * 2,可是結果是什麼鬼!!! a = parseInt('100000000', 16); // 4294967296 b = parseInt('1111', 2); // 15 a | b; // 15 // 啊啊啊,爲毛個人 a 絲絕不起做用,JavaScript真是門弔詭的語言!!!
好吧,雖然我說過你們能夠近似地認爲,JS 的數字類型能夠表示 53 位的整型。但事實上,位操做符並非這麼認爲的。在 ECMAScript® Language Specification 中是這樣描述位操做符的:
The production A : A @ B, where @ is one of the bitwise operators in the productions above, is evaluated as follows:
- Let lref be the result of evaluating A.
- Let lval be GetValue(lref).
- Let rref be the result of evaluating B.
- Let rval be GetValue(rref).
- Let lnum be ToInt32(lval).
- Let rnum be ToInt32(rval).
- Return the result of applying the bitwise operator @ to lnum and rnum. The result is a signed 32 bit integer.
須要注意的是第5和第6步,按照ES標準,兩個須要運算的值會被先轉爲有符號的32位整型。因此超過32位的整數會被截斷,而小數部分則會被直接捨棄。
而反過來考慮,咱們在什麼狀況下須要用到位操做符?使用左移來代替 2 的冪的乘法?Naive啊,等遇到像第一個例子的問題,你就要抓狂了。並且對一個浮點數進行左移操做是否比直接乘 2 來得效率高,這也是個值得商榷的問題。
那用來表示標誌位呢?首先,如今的內存大小已經不值得咱們用精簡幾個變量來減小存儲空間了;其次呢,使用標誌位也會使得代碼的可讀性大大降低。再者,在 JavaScript 中使用位操做符的地方畢竟太少,若是你執意使用位操做符,將來維護這段代碼的人又對 JS 中的位操做符的坑不熟悉,這也會形成不利的影響。
因此,我對你們的建議是,儘可能在 JavaScript 中別使用位操做符。