JavaScript的Number採用64位元的IEEE 754標準來表示整數和浮點數數值,其中整數的安全範圍在-253 - 1到253 - 1之間。換句話說,Node.js既不能直接使用到32位元的整數,同時也無法使用64位元的整數。因此,如果要拿Node.js來做一些稍微複雜的計算,就需要撰寫額外的程式來處理資料型態的部份。像是32位元整數經常會遇到的「溢位」,想要在Node.js上重現就比較麻煩。



以下C語言程式示範了32位元的整數遇到溢位的情形:

#include<stdio.h>
#include<stdlib.h>

int main(int argc, char **argv) {
    int32_t a = 2147483647;
    a *= 2;
    printf("%d\n", a);
    return 0;
}

輸出結果為:

-2

32位元的整數所能表示的最大值為231 - 1 = 214748364710 = 011111111111111111111111111111112。程式第5行宣告了變數a。並指派了一個最大值給變數a儲存。接著在程式第6行讓a儲存的數值乘2了。如果熟悉位元運算的話,可以很快地將「乘2」這個動作對應成向左位移1個位元,也就是說111111111111111111111111111111112向左位移1個位元之後,原本在最左邊的最大位元(MSB)0會因為左移而被移除,而最小位元(LSB)會被填上0,所以位移後最終的數值是111111111111111111111111111111102。由於有號數是使用2的補數來表示,因此111111111111111111111111111111102換算成10進制數值後會是-210。若要使用JavaScript來得到像上述這樣進行乘法計算卻遇到溢位又要考慮補數的結果,可以利用Math所提供的imul函數來完成。

console.log(Math.imul(2147483647, 2));

輸出結果為:

-2

然而網路上還流傳著一種做法,那就是將最後的運算結果去OR一個0。程式如下:

console.log((2147483647 * 2) | 0);

輸出結果為:

-2

這裡必須要注意到的是,「將最後的運算結果去OR一個0」雖然在上面的範例中算出來的答案和Math所提供的imul函數相同,但它並不是一個正確的32位元整數的解決方案,因為它在很多情況下算出來的答案不是對的。舉例:

console.log(Math.imul(1412337538, 515419545));
console.log((1412337538 * 515419545) | 0);

輸出結果為:

675192498
675192448

使用以下C程式來驗證結果:

#include<stdio.h>
#include<stdlib.h>

int main(int argc, char **argv) {
    int32_t a = 1412337538;
    a *= 515419545;
    printf("%d\n", a);
    return 0;
}

輸出結果為:

675192498

既然JavaScript可以直接使用Math所提供的imul函數來模擬32位元整數的乘法,想必加、減、除也有對應的函數囉?很遺憾,加、減、除法並沒有這樣的函數可以使用,所以說在JavaScript上要進行32位元整數運算是一件麻煩的事情。利用C/C++或是Rust語言實作的函數庫來進行32位元的整數運算才會容易許多。

int32

「int32」是一個使用Rust函式庫開發的Node.js的模組,使用Rust程式語言原生的u32i32型別來直接進行32位元的整數運算。

GitHub:

https://github.com/magiclen/node-int32

npm:

https://www.npmjs.com/package/int32

安裝

直接使用npm指令進行安裝:

npm install int32

用法

初始化
const int32 = require('int32');
import int32 from 'int32';
函數的用法
add
let n = int32.add(1, 2); // 3 
subtract
let n = int32.subtract(1, 2); // -1 
multiply
let n = int32.subtract(2, 6); // 12 
divide
let n = int32.divide(6, 4); // 1 
pow
let n = int32.pow(2, 3); // 8 
shiftLeft
let n = int32.shiftLeft(5, 2); // 20 
shiftRight
let n1 = int32.shiftRight(5, 2); // 1 
let n2 = int32.shiftRight(6, 1); // 3 
let n3 = int32.shiftRight(-5, 1); // -3 
shiftRightUnsigned
let n = int32.shiftRightUnsigned(-5, 1); // 2147483645 
rotateLeft
let n = int32.rotateLeft(0b10000000000000000000000100000000, 1); // 0b00000000000000000000001000000001
rotateRight
let n = int32.rotateRight(0b00000000000000000000000100000001, 8); // 0b00000001000000000000000000000001