将JavaScript整数转换为字节数组并重新转换回去

54
function intFromBytes( x ){
    var val = 0;
    for (var i = 0; i < x.length; ++i) {        
        val += x[i];        
        if (i < x.length-1) {
            val = val << 8;
        }
    }
    return val;
}

function getInt64Bytes( x ){
    var bytes = [];
    var i = 8;
    do {
    bytes[--i] = x & (255);
    x = x>>8;
    } while ( i )
    return bytes;
}

我正在尝试将JavaScript中的数字转换为字节数组,然后再转回数字。但是上述函数在处理非常大的数字时会产生错误的输出。

var array = getInt64Bytes(23423423);    
var value = intFromBytes(array);

console.log(value); //Prints 23423423 - correct

var array = getInt64Bytes(45035996273704);  
var value = intFromBytes(array);

console.log(value); //Prints -1030792152 - incorrect

据我所知,JavaScript的浮点数是53位,因此不应该出现溢出问题?alert(Math.pow(2,53))可以正常工作。


6
位移操作始终在有符号的32位整数上执行。 - Crozin
有道理,把它作为答案,我会接受的。谢谢。 - user75832
3
由于这个问题在Google中排名较高,可能值得指出现在有数组缓冲区(array buffers)可用:https://dev59.com/8mUo5IYBdhLWcg3wzB9q - Melvin Sovereign
11个回答

58

在Susanoh13提供的提示下,以下是两个函数可用于将数字从/转换为ByteArray:

longToByteArray = function(/*long*/long) {
    // we want to represent the input as a 8-bytes array
    var byteArray = [0, 0, 0, 0, 0, 0, 0, 0];

    for ( var index = 0; index < byteArray.length; index ++ ) {
        var byte = long & 0xff;
        byteArray [ index ] = byte;
        long = (long - byte) / 256 ;
    }

    return byteArray;
};

byteArrayToLong = function(/*byte[]*/byteArray) {
    var value = 0;
    for ( var i = byteArray.length - 1; i >= 0; i--) {
        value = (value * 256) + byteArray[i];
    }

    return value;
};

1
有时在byteArrayToLong()函数中,byteArray[i]被视为字符串(取决于浏览器),导致计算结果错误。我通过*1解决了这个问题:value = (value * 256) + byteArray[i] * 1; - DrArt
1
你应该指定这是否将字节视为包含有符号或无符号整数。 - Drew Noakes
3
这对负数无效。相反,大的正数被解码。 - ygoe
4
如果 JavaScript 数字是64位无符号整数,那么这段代码将是有效的。然而,它们是64位浮点数,只能精确存储52位整数。这意味着,虽然此代码对许多字节(例如[0,1,2,3,4,5,6,7])有效,但当所有位实际被使用时,它将失败。一些失败的情况包括:(将其转换为数字然后再转回来会得到不同的结果) ([255,255,255,255,255,255,255,255] => [0, 0, 0, 0, 0, 0, 0, 255]), ([1,2,3,4,5,6,7,8] => [0, 2, 3, 4, 5, 6, 7, 8]), ([1,0,0,0,0,0,0,1] => [0, 0, 0, 0, 0, 0, 0, 1])请勿使用此代码。 - yeerk

17

在JavaScript中,位移操作符(>><<)总是在带符号的32位整数上执行。这会导致对于大数值而言发生范围溢出。


1
但如果需要长整型(64位有符号整数),该怎么办? - Timo Kähkönen
1
JS不支持64位长整型。请参考此链接:https://dev59.com/4Gkw5IYBdhLWcg3wyNkW 和 https://dev59.com/bW435IYBdhLWcg3wigux - Crozin
@TimoKähkönen x<<y === x*(2**y) 右侧的解决方案可用于64位整数。 - user4584267
JavaScript(ESNext)现在支持64位数字。可以是有符号和无符号的。 - Derk Jan Speelman
@DerkJanSpeelman 有链接吗?除非你指的是 BigInt,但这不是我所说的64位数字。 - timotheecour
@timotheecour https://tierion.github.io/boltwall/interfaces/_node_modules_typescript_lib_lib_esnext_bigint_d_.bigint64array.html - Derk Jan Speelman

8

尝试使用**表示幂运算符<<>>>表示位移运算符 - intFromBytes仅适用于由正整数生成的数组。

function getInt64Bytes(x) {
  let y= Math.floor(x/2**32);
  return [y,(y<<8),(y<<16),(y<<24), x,(x<<8),(x<<16),(x<<24)].map(z=> z>>>24)
}

function intFromBytes(byteArr) {
    return byteArr.reduce((a,c,i)=> a+c*2**(56-i*8),0)
}

function getInt64Bytes(x) {
  let y= Math.floor(x/2**32);
  return [y,(y<<8),(y<<16),(y<<24), x,(x<<8),(x<<16),(x<<24)].map(z=> z>>>24)
}

function intFromBytes(byteArr) {
    return byteArr.reduce((a,c,i)=> a+c*2**(56-i*8),0)
}


// TEST

let n = 40*2**40 + 245*2**32 + 194*2**24 + 143*2**16 + 92*2**8 + 40;
let b = getInt64Bytes(n);
let i = intFromBytes(b);

console.log(`number      : ${n}`);
console.log(`int to bytes: [${b}]`);
console.log(`bytes to int: ${i}`);


getInt64Bytes 函数中 y 的值需要用 Math.floor 包装,否则对于负整数会出现错误。例如,getInt64Bytes(-Number.MAX_SAFE_INTEGER) 返回 [255, 224, 0, 1, 0, 0, 0, 1],而正确的返回值应该是 [255, 224, 0, 0, 0, 0, 0, 1] - snickle
@snickle 这个程序仅适用于正整数 - Math.floor 无法解决 intFromBytes 中的问题(例如,当我们在代码片段中输入负数时,如 let n= -...)- 目前我没有时间来解决这个问题 - 但请随意创建您的答案,以完善我的答案并解决这个问题。 - Kamil Kiełczewski

5
2022年,你应该使用Buffer来处理字节,使用BigInt来处理大整数。
因此,你的代码可能如下所示:
import { Buffer } from "node:buffer";

/**
 * @param {BigInt} x
 * @returns {Buffer}
 */
function getInt64Bytes(x) {
  const bytes = Buffer.alloc(8);
  bytes.writeBigInt64LE(x);
  return bytes;
}

/**
 * @param {Buffer} x 
 * @returns {BigInt}
 */
function intFromBytes(x) {
  return x.readBigInt64LE();
}

var array = getInt64Bytes(BigInt(23423423));
var value = intFromBytes(array);

console.log(value); // 23423423n

var array = getInt64Bytes(BigInt(45035996273704));
var value = intFromBytes(array);

console.log(value); // 45035996273704n

小端序是异端邪说。 - Pablo LION

4

这是一个类似Brainfuck的Lodash版本。仅供娱乐,请勿使用!

const uintToArray = (uint, size) => _.chunk(_.padStart(uint, size*2,  0).split(''), 2).map((a)=>parseInt(a[0]+a[1]))

3

进行位移运算等同于乘以 2^(位数+1),所以不需要通过左移操作 val = val<<8,你可以使用 val = val*256 来实现。试试看这样是否可行。


1

为了让在 Node.js 版本 16.5 之后来到这里的人知道,缓冲区 API 现在提供了一种方法,可以将各种大小的数字写入/读取到字节数组中,反之亦然,使用的是

writeUIntXXX

APIs。这些涵盖整数、长整型、双精度浮点型、单精度浮点型以及大端和小端格式。因此,我想我们不再需要自己开发解决方案了。


1
<html>
<head>
    <meta charset="utf-8">
    <title>Uint32_To_Byte_Array</title>
    <script>
    function body_Add(Msg)
    {
        document.body.innerHTML = document.body.innerHTML + Msg;
    }
    class Byte 
    {
        constructor(Value) 
        {
            this.Number = new Uint8Array(1);
            this.Number[0] = Value;
        }
        get Get() 
        {
            return this.Number[0];
        }
        set Set(newValue) 
        {
            this.Number[0] = newValue;
        }
    };
    class Uint32
    {
        constructor(Value) 
        {
            this.Number = new Uint32Array(1);
            this.Number[0] = Value;
        }
        get Get() 
        {
            return this.Number[0];
        }
        set Set(newValue) 
        {
            this.Number[0] = newValue;
        }
    };
    var Conversion =
    {
        Uint32_To_Byte_Array: function(Source_Num)
        {
            var Uint32_Num = new Uint32(Source_Num);
            var Byte_Num = new Byte(0);
            var Byte_Arr = new Uint8Array(4);
            for (var i = 0; i < 4; i++)
            {
                if (Source_Num > 255)
                {
                    Uint32_Num.Set = Source_Num / 256;
                    Byte_Num.Set = Source_Num - Uint32_Num.Get * 256;
                }
                else
                {
                    Byte_Num.Set = Uint32_Num.Get;
                    Uint32_Num.Set = 0;
                }
                Byte_Arr[i] = Byte_Num.Get;
                Source_Num = Uint32_Num.Get;
            }
            return(Byte_Arr);
        },
        Byte_Array_To_Uint32: function(Source_Byte_Array, Start_Position)
        {
            var Uint32_Num = new Uint32(0);
            var Multiplier = 1;
            for (let i = 0; i < 4; i++)
            {
                Uint32_Num.Set = Uint32_Num.Get + Source_Byte_Array[Start_Position + i] * Multiplier;
                Multiplier = Multiplier * 256;
            }
            return (Uint32_Num.Get);
        }
    };
    function Load_Page()
    {
        var Numbers = [0,1,257,4294967295];
        Numbers.forEach(Convert);
        function Convert(Item, Index)
        {
            var Uint32_Number = Item;
            var Byte_Array = Conversion.Uint32_To_Byte_Array(Uint32_Number);
            var Uint32_Number_Restored = Conversion.Byte_Array_To_Uint32(Byte_Array, 0);
            body_Add("Conversion: Source number: " + Uint32_Number.toString() + ", Byte array: " + Byte_Array.toString() + ", Restored number: " + Uint32_Number_Restored.toString() + "<br>");
        };
    };
    </script>
</head>
<body onload="Load_Page()"> 
</body>


1
结果:转换:源数字:0,字节数组:0,0,0,0,恢复的数字:0;转换:源数字:1,字节数组:1,0,0,0,恢复的数字:1;转换:源数字:257,字节数组:1,1,0,0,恢复的数字:257;转换:源数字:4294967295,字节数组:255,255,255,255,恢复的数字:4294967295 - Valery Rode
这正是我所需要的!只需要稍作修改 :) 谢谢!! :) - Jure

1
如果您使用的是Node.js,Buffer 是处理JavaScript/TypeScript中任何字节数组/流的正确方式:

https://nodejs.org/api/buffer.html

尽管文档更为全面,但Stack Overflow建议在链接失效时,在此处提供代码片段。因此,以下是该文档中最重要的几个代码示例:
// Creates a Buffer containing the UTF-8-encoded bytes for the string 'tést':
// [0x74, 0xc3, 0xa9, 0x73, 0x74] (in hexadecimal notation)
// [116, 195, 169, 115, 116] (in decimal notation)
const buf6 = Buffer.from('tést');

// Creates a Buffer containing the bytes [1, 2, 3].
const buf4 = Buffer.from([1, 2, 3]);

11
使用缓冲区(Buffer)来处理任何已经是缓冲区或字节数组的字节数据是正确的方法。但主要问题是:如何使用Buffer将整数转换为字节数组?使用Buffer.from(INTEGER)是不被允许的。TypeError [ERR_INVALID_ARG_TYPE]: 参数"value"不能是数字类型。接收到的是数字类型。 at Function.from (buffer.js:215:11) - Alberto Torre

0

这个会工作的,

    let buf;

    if (num < 128)
    {
        buf = Buffer.from([num]);
    }
    else if (num < 256)
    {
        buf = Buffer.from([129, num]);
    }
    else if (num < 65536)
    {
        buf = Buffer.from([130, 256, num % 256]);
    }
    else if (num < 65536)
    {
        buf = Buffer.from([130, num / 256, num % 256]);
    }
    else if (num < 16777216)
    {
        buf = Buffer.from([131, num / 65536, num / 256, num % 256]);
    }


    console.log(buf.toString('hex'));

1
请问您可以解释一下魔数129、130、131的原因或提供一些相关参考资料吗?此外,num<65536有两个分支,我认为这是一个笔误。 - Pablo LION

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接