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位元的整數運算。

npmjs.com

npm 安裝指令

npm install int32

使用方法

add
import { add } from "int32";

const n = add(1, 2); // 3
subtract
import { subtract } from "int32";

const n = subtract(1, 2); // -1
multiply
import { multiply } from "int32";

const n = multiply(2, 6); // 12
divide
import { divide } from "int32";

const n = divide(6, 4); // 1
pow
import { pow } from "int32";

const n = pow(2, 3); // 8
shiftLeft
import { shiftLeft } from "int32";

const n = shiftLeft(5, 2); // 20
shiftRight
import { shiftRight } from "int32";

const n1 = shiftRight(5, 2); // 1
const n2 = shiftRight(6, 1); // 3
const n3 = shiftRight(-5, 1); // -3
shiftRightUnsigned
import { shiftRightUnsigned } from "int32";

const n = shiftRightUnsigned(-5, 1); // 2147483645
rotateLeft
import { rotateLeft } from "int32";

const n = rotateLeft(0b10000000000000000000000100000000, 1); // 0b00000000000000000000001000000001
rotateRight
import { rotateRight } from "int32";

const n = rotateRight(0b00000000000000000000000100000001, 8); // 0b00000001000000000000000000000001