6

在其他可以表示 64 位整数的语言中,可以很容易地做到这一点......

如何将一个 64 位整数存储在两个 32 位整数中并再次转换回来

如何在 Ruby 中将一个 64 位整数存储在两个 32 位整数中

// convert 64-bit n to two 32-bit x and y
x = (n & 0xFFFFFFFF00000000) >> 32
y =  n & 0xFFFFFFFF

但是 JavaScript 不能表示 64 位整数。它只能表示 52 位整数,没有问题。

现在这意味着不可能将一个 64 位整数转换为两个 32 位整数,因为一开始甚至不可能有一个 64 位整数

但是,我们还剩下 52 位。我的问题是:我们如何将 JavaScript 中的这个 52 位整数拆分为两个 32 位整数(20 个高位和 32 个低位)

有人可以建议像上面这样的位操作代码在 JavaScript 中进行 20 位和 32 位拆分吗?

相关: 如何将位运算产生的 32 位 JavaScript 数字转换回 64 位数字

4

3 回答 3

14

在我们开始之前

首先,您的链接在声明“任何小于 2 52 [...] 的整数都可以安全地放入 JavaScript 数字方面存在一点错误。”虽然在技术上是正确的,但它并不是一个严格的界限:可以验证无需太多麻烦,javascript 数字可以存储最多 2 53(但不是 2 53 +1)的每个正整数。

一些代码

事不宜迟,您请求的函数将 52 位数字拆分为低 32 位和高 20 位:

function to_int52(hi, lo) {
    /* range checking */
    if ((lo !== lo|0) && (lo !== (lo|0)+4294967296))
        throw new Error ("lo out of range: "+lo);
    if (hi !== hi|0 && hi >= 1048576)
        throw new Error ("hi out of range: "+hi);

    if (lo < 0)
    lo += 4294967296;

    return hi * 4294967296 + lo;
}

function from_int52(i) {
    var lo = i | 0;
    if (lo < 0)
    lo += 4294967296;

    var hi = i - lo;
    hi /= 4294967296;
    if ((hi < 0) || (hi >= 1048576)
        throw new Error ("not an int52: "+i);
    return { lo: lo, hi: hi };
}

在哪里拆分

我不建议使用这些。Javascript 位运算是有符号的(@dandavis:JS 没有UInt32s),当我们真正想要正值时,符号位会让人头疼。Plus V8 对可以存储为 31 位的(有符号)整数进行了优化。结合这两个事实,您应该拆分不超过 30 位,这是适合 V8 小整数(“smi”)的最大正大小。

这是将数字拆分为 30 个低位和 22 个高位的代码:

function int52_30_get(i) {
    var lo = i & 0x3fffffff;
    var hi = (i - lo) / 0x40000000;
    return { lo: lo, hi: hi };
}

不过,您可能不想创建对象。这些应该被内联(如果你真的很关心函数):

function int52_30_get_lo(i) {
    return i & 0x3fffffff;
}

function int52_30_get_hi(i) {
    return (i - (i & 0x3fffffff)) / 0x40000000;
}

并从低部分和高部分创建数字:

function int52_30_new_safe(hi, lo) {
    return (hi & 0x3fffff) * 0x40000000 + (lo & 0x3fffffff);
}

如果您真的确定 hi 和 lo 在范围内,则可以跳过屏蔽:

function int52_30_new(hi, lo) {
    return hi * 0x40000000 + lo;
}

分别设置高低部分:

/* set high part of i to hi */
i = (hi & 0x3fffff) * 0x40000000 + (i & 0x3fffffff);

/* set low part of i to lo */
i += (lo & 0x3fffffff) - (i & 0x3fffffff);

如果您确定 hi 和 lo 在范围内:

/* set high part of i to hi */
i = hi * 0x40000000 + (i & 0x3fffffff);

/* set low part of i to lo */
i += lo - (i & 0x3fffffff);

(这些不是函数,因为它们修改了i.)

为了更有趣,一个提取任意位域的函数:

function int52_30_get_bits(i, lsb, nbits) {
    while (lsb >= 32) {
        i /= 4294967296;
        lsb -= 32;
    }
    return (i / (1<<lsb)) & ((1<<nbits)-1);
}

(nbits 必须 <= 31。当 nbits 为 32 时的故障模式很有趣,这是由于 << 的 rhs 操作数只有 5 个低位是重要的,这是 javascript 规范与 x86 ISA 共享的缺陷。)

超过 52 位?

完全可以使用符号位将 53 位二进制数存储为从 -2 53到 2 53 -1 的整数。我还没有这样做,但它应该很容易。之后它开始变得有点毛茸茸,你最终会遇到这样一个事实,即在你到达 2 64之前没有足够的浮点数来循环(很多是 NaN) 。将 63 个二进制数字打包成浮点数在理论上应该是可行的,但留给读者作为练习:)

其他方法

另一种方法是使用类型化数组并创建一个 Float 视图和一个 Int 视图:这使您可以直接操作浮点的底层二进制表示。但是你必须开始担心字节顺序之类的问题。

所有建议字符串操作的人都疯了。

于 2013-10-09T14:15:36.410 回答
4

那么你可以像这样在数字上做到这一点:

function numeric(n) {
    return { 
        hi: Math.floor(n / 4294967296), 
        lo: (n & 0xFFFFFFFF) >>> 0
    }
}

或者字符串版本可以是:

function strings(n) { 
    s = n.toString(16);

    if (s.length > 8) { 

        return { 
            hi: parseInt( s.toString(16).slice(0, s.length - 8), 16),
            lo: parseInt( s.toString(16).slice(s.length - 8), 16)
        }
    } else { 
        return { hi: 0, lo: n } 
    }

}

或者可能 ...

function stringPad(n) { 
    s = "00000000000"+n.toString(16);
    return { 
        hi: parseInt( s.toString(16).slice(0, s.length - 8), 16),
        lo: parseInt( s.toString(16).slice(s.length - 8), 16)
    }
}

现在,哪个更快。为了找出答案,我在这里设置了一个测试平台:http: //jsfiddle.net/SpaceDog/ZTJ2p/(你也可以使用你最喜欢的 JS 分析器)。

结果(对于 100000 次调用):

Function: numeric completed in 146 ms
Function: strings completed in 379 ms
Function: stringPad completed in 459 ms

我会认为字符串更快,并想知道它是否是 parseInt 调用,但不是:

Function: stringPadNoParse completed in 386 ms

现在,这不是很精确,因为它取决于很多其他事情(同样,探查器可能会更好),但数字版本似乎更快,我已经运行了几次来测试。

但也许有人会来提供另一种方法。

于 2013-10-09T12:10:05.770 回答
1

类型化数组可用于获取双精度值的两半

var buf = new ArrayBuffer(8);
(new Float64Array(buf))[0] = f;
fl = (new Uint32Array(buf))[0];
fh = (new Uint32Array(buf))[1];

现在你有了尾数。如果需要,只需提取并移位以获得整数部分

var exp = ((fh >> 20) & 0x7FF) - 1023;
var mant_h = (fh & 0xFFFFF) | (1 << 20);
var mant_l = fl;
if (exp > 52)
    throw new Error ("overflow int53 range");
else if (exp >= 32)
{
    L = mant_h >> (exp - 32);
    H = 0;
}
else if (exp >= 0)
{
    L = (mant_l >> exp) | (mant_h << (32 - exp));
    H = mant_h >> exp;
}

但是,如果您可以使用类型化数组,那么您应该从一开始就使用具有 2 个元素的 32 位数组作为整数,无需处理 double 和所有相关的麻烦。这样你就有两个 32 位的值,而不仅仅是 20 位

使用相反的方向,您可以将 64 位 int 存储在 JavaScript 变量中

请注意,您可以在 Javascript 中使用 64 位 intBigInt64ArrayBigUint64Array. 您可以使用它并将其拆分为两个 32 位部分而不是双部分,但它的效率将低于 32 位数组

从 ECMAScript® 2020 开始,还有一个新的BigInt 类型用于任意精度整数(带有BigInt对象或n后缀)。大多数主流浏览器已经支持它

const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER);
// ↪ 9007199254740991

const maxPlusOne = previousMaxSafe + 1n;
// ↪ 9007199254740992n

虽然它可以处理任意精度的整数,但它引入该标准的理由之一是有效地使用 64 位或 128 位系统对象句柄或 ID,所以我认为它应该对 64 位有很好的支持整数

于 2018-08-15T17:33:40.290 回答