JS神奇的或0(|0)

原子切割员發表於2024-06-24

按照常識,位運算x|0,要麼等於x,要麼等於0
那麼在JS的世界你的認知就要被顛覆了
下面請看

不帶或0運算:
(window.crypto.getRandomValues(new Uint32Array(1))[0] * 0x10000 )
168546249998336
(window.crypto.getRandomValues(new Uint32Array(1))[0] * 0x10000 )
18707488702464
(window.crypto.getRandomValues(new Uint32Array(1))[0] * 0x10000 )
15579009253376
(window.crypto.getRandomValues(new Uint32Array(1))[0] * 0x10000 )
194841754140672
(window.crypto.getRandomValues(new Uint32Array(1))[0] * 0x10000 )
262611854950400
(window.crypto.getRandomValues(new Uint32Array(1))[0] * 0x10000 )
171394313420800

帶或0運算:
(window.crypto.getRandomValues(new Uint32Array(1))[0] * 0x10000 )|0
-1037238272
(window.crypto.getRandomValues(new Uint32Array(1))[0] * 0x10000 )|0
511180800
(window.crypto.getRandomValues(new Uint32Array(1))[0] * 0x10000 )|0
1204224000
(window.crypto.getRandomValues(new Uint32Array(1))[0] * 0x10000 )|0
-2026438656

可以看到明顯的帶或0運算與不帶或0運算的結果無論是位數還是符號位都有不同。
那這中間到底發生了什麼?

這裡找一個數字為例:117063531626496
要想驗證這個問題,思路如下:
1,對比變更前後的數字的二進位制格式
2,找到是否有數字表示的安全邊界

首先按照思路1,我們看一下這個數字和這個數字或0後的二進位制格式分別是什麼:

117063531626496的二進位制格式:

var num = 117063531626496; num.toString(2);
輸出:'11010100111011111111010001110000000000000000000'

117063531626496 | 0
輸出:-96993280

-96993280的二進位制格式:
var num = -96993280; num.toString(2);
'-101110010000000000000000000'

對比對比:

11010100111011111111010001110000000000000000000
                   -101110010000000000000000000

除了後面的0位數相同,沒有找到明顯的線索

那麼我們按照思路2,來看一下原因:
透過官網對於js的number的定義,是64位的統一型別
但是我們透過 Number.MAX_SAFE_INTEGER可以看到number的安全最大值是:9007199254740991

透過轉為2進位制,可以發現這個數字是個54位的1:
var num = 9007199254740991; num.toString(2)
'11111111111111111111111111111111111111111111111111111'

那這個值可以正常地或0嗎?實際上還是不行

9007199254740991|0
-1
9007199254740990|0
-2

那這個邊界到底是多少呢?對於其它語言Integer的預設最大值一般為2的32次方-1,也就是2147483647
這次再來試一下:

2147483647|0
2147483647
如果對這個值再+1,重試呢
2147483648|0
-2147483648

可以發現這個邊界就是32位整數的最大值。
超過這個值的數字再與0進行位運算則可能得到一個錯誤的結果。

相關文章