在JavaScript中将大整数转换为8字节数组

4

我正在尝试在JavaScript中将一个大数字转换为8字节数组。

这里是我要传入的IMEI号码:45035997012373300

var bytes = new Array(7);
for(var k=0;k<8;k++) {
  bytes[k] = value & (255);
  value = value / 256;
}

这最终产生了字节数组:48,47,7,44,0,0,160,0。将其转换回长整型,其值为45035997012373296,比正确值少4。
有什么想法为什么会这样,以及如何修复以序列化成正确的字节?

1
注意,value 将是一个全局变量。 - Elliot Bonneville
你是如何将其转换回长整型的?例如:http://jsfiddle.net/zuqgJ/,这个方法完全可行。 - mellamokb
@Elliot - 这是一个函数内的代码。 - Justin
@mellamokb - 字节数组通过UDP传递到.NET(C#)Windows服务,使用BitConverter类进行反序列化。 - Justin
你能把转换回长整型的代码加上吗? - Ryan
这段代码可以实现 BitConverter.ToInt64(new byte[] {48,47,7,44,0,0,160,0}, 0),返回值为 45035997012373296 - mellamokb
3个回答

3

由于您正在从十进制转换为字节,因此通过将字符串中的数字分成部分来模拟除以256的操作非常容易。有两个我们可以利用的数学规则。

  1. 十进制数的右侧n位数字可以确定2的n次方的可除性。
  2. 10^n总是可被2^n整除。

因此,我们可以取数字并分离出最右侧的8个数字以找到余数(即& 255),将右侧部分除以256,然后单独将数字的左侧部分除以256。左侧部分的余数可以按公式n*10^8 \ 256 = (q*256+r)*10^8 \ 256 = q*256*10^8\256 + r*10^8\256 = q*10^8 + r*5^8移入数字的右侧部分(即最右侧的8个数字),其中\是整数除法,qr分别是n \ 256的商和余数。这会产生以下方法,以处理长度不超过23个数字(15个普通JS精度+此方法产生的额外8个数字)的字符串的整数除法:

function divide256(n)
{
    if (n.length <= 8)
    {
        return (Math.floor(parseInt(n) / 256)).toString();
    }
    else
    {
        var top = n.substring(0, n.length - 8);
        var bottom = n.substring(n.length - 8);
        var topVal = Math.floor(parseInt(top) / 256);
        var bottomVal = Math.floor(parseInt(bottom) / 256);
        var rem = (100000000 / 256) * (parseInt(top) % 256);
        bottomVal += rem;
        topVal += Math.floor(bottomVal / 100000000); // shift back possible carry
        bottomVal %= 100000000;
        if (topVal == 0) return bottomVal.toString();
        else return topVal.toString() + bottomVal.toString();
    }
}

从技术上讲,这可以通过递归地将数字分成8位部分并使用相同的方法分别处理每个部分的除法来实现对任意大小的整数进行256的除法。

以下是一个可行的实现,可以计算出您的示例数字(45035997012373300)的正确字节数组:http://jsfiddle.net/kkX2U/

[52, 47, 7, 44, 0, 0, 160, 0]

你的数组是逆序排列的(从最低位字节到最高位显示)。 - Phrogz
它与原帖中发布的顺序相同,并且在我测试过的C#中,也是BitConverter.ToInt64的正确顺序。 - mellamokb
但是它与我的答案不同,所以一定是错误的。 :) 我没有注意到 OP 的“反转”字节顺序;好观点。 - Phrogz
正如我在OP评论中所提到的,您可以在LINQPad中使用此测试:BitConverter.ToInt64(new byte[] {52,47,7,44,0,0,160,0}, 0)。我假设转换会按照发送的相同顺序进行,并不会在UDP流上以FIFO方式进行。网络/位方面的事情我不是很了解。 - mellamokb

2

您的值与JavaScript中最大整数相比:

45035997012373300  // Yours
 9007199254740992  // JavaScript's biggest integer

JavaScript无法精确表示您的原始值为整数;这就是为什么您的脚本将其分解后给出了一个不精确的表示。

相关:

var diff = 45035997012373300 - 45035997012373298;
// 0 (not 2)

编辑: 如果您可以将您的数字表示为十六进制字符串:

function bytesFromHex(str,pad){
  if (str.length%2) str="0"+str;
  var bytes = str.match(/../g).map(function(s){
    return parseInt(s,16);
  });
  if (pad) for (var i=bytes.length;i<pad;++i) bytes.unshift(0);
  return bytes;
}

var imei = "a000002c072f34";
var bytes = bytesFromHex(imei,8);
// [0,160,0,0,44,7,47,52]

如果您需要将字节按从最不重要到最重要的顺序排序,请在结果上加上 .reverse()

1
@mellamokb 请尝试将值更改为45035997012373301(http://jsfiddle.net/zuqgJ/1/)。 - Phrogz
@Phrogz - 所以有什么解决方法?我可以将IMEI作为JavaScript中的字符串存储,以确保我不失去精度。但我仍然需要一种将其转换为8个字节的方法。 - Justin
@mellamokb(快速编辑代码)我不知道你在说什么。;) - Phrogz
@Phrogz:太晚了..刚错过了5分钟的时间窗口 :P - mellamokb
将答案切换到这个。感谢你们两个的帮助,我最初使用了melllamokb的代码,但它最终在某些大值上无法工作。我将我的代码更改为以十六进制返回IMEI,然后使用Phrogz的代码序列化为字节,现在它运行得很好! - Justin
显示剩余4条评论

0

将IMEI作为十六进制字符串存储(如果可以),然后以这种方式解析字符串,这样您就可以在构建数组时保持精度。如果这个问题还没有得到答案,我回到家里的常规电脑上会提供一个PoC。

类似于:

function parseHexString(str){
   for (var i=0, j=0; i<str.length; i+=2, j++){
      array[j] = parseInt("0x"+str.substr(i, 2));
   }
}

或者接近那个样子什么的...


请注意,您可以简单地使用 parseInt('af',16) // 175 - Phrogz

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