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++語言的N-API來實作32位元整數運算才會容易許多。

int32

「int32」是一個使用Node.js 8之後才支援的N-API所開發的模組,使用C語言原生的「int32_t」型態來直接進行32位元的整數運算。

GitHub:

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

npm:

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

安裝

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

npm install --save int32

用法

初始化

使用「require」函數來引入「int32」模組。利用模組的「Int32」屬性可取得「Int32」物件的建構子。

const int32 = require('int32'); // static functions 
const Int32 = int32.Int32; // instance methods 
靜態函數的用法
add
var n = int32.add(1, 2); // 3 
subtract
var n = int32.subtract(1, 2); // -1 
multiply
var n = int32.subtract(2, 6); // 12 
divide
var n = int32.divide(6, 4); // 1 
shiftLeft
var n = int32.shiftLeft(5, 2); // 20 
shiftRight
var n1 = int32.shiftRight(5, 2); // 1 
var n2 = int32.shiftRight(6, 1); // 3 
var n3 = int32.shiftRight(-5, 1); // -3 
shiftRightUnsigned
var n = int32.shiftRightUnsigned(-5, 1); // 2147483645 
rotateRight
var n = int32.rotateRight(0b00000000000000000000000100000001, 8); // 0b00000001000000000000000000000001
rotateLeft
var n = int32.rotateLeft(0b10000000000000000000000100000000, 1); // 0b00000000000000000000001000000001
operate
var n1 = int32.operate(1, ['+1','*3','-3','/3']); // (((1+1)*3)-3)/3 = 1 
var n2 = int32.operate(0xFFFF0000, ['<8', '>24']); // 11111111111111110000000000000000 << 8 = 11111111000000000000000000000000 
                                                   // 11111111000000000000000000000000 >> 24 = 11111111111111111111111111111111 
                                                   // = -1 
var n3 = int32.operate(0xFFFF0000, ['<8', '>>>24']); // 11111111111111110000000000000000 << 8 = 11111111000000000000000000000000 
                                                     // 11111111000000000000000000000000 >>> 24 = 00000000000000000000000011111111 
                                                     // = 255 
物件方法的用法
建立物件實體
var i32 = new Int32(1);
物件方法

物件方法的種類和靜態函數大致相同,請參考以下範例:

var n1 = i32.add(1).multiply(3).subtract(3).divide(3).getValue(); // 1 
i32.set(0xFFFF0000);
var n2 = i32.shiftLeft(8).shiftRight(24).getValue(); // -1 
i32.set(0xFFFF0000);
var n3 = i32.shiftLeft(8).shiftRightUnsigned(24).getValue(); // -255 
i32.set(0b00000000000000000000000100000001);
var n4 = i32.rotateRight(8); // 0b00000001000000000000000000000001