获取数字的重要一半

Getting the significant half of a Number

我正在尝试将值小于或等于 Number.MAX_SAFE_INTEGER 的数字分解为两个 32 位 uint 值。

我对获取高值的第一个想法就是值 >>> 32 但这首先将值转换为 32b uint,因此结果为 0。

另一个想法是

var high = Number(BigInt(value) >> 32n);

但是,使用 BigInt 相对较慢,我想尽可能避免这种情况

然后我尝试了 Math.floor(value / 2 ** 32) ,它比 BigInt 快得多,而且看起来很准确,但我需要保证它是准确的。如果该值介于 2 ** 32 和 Number.MAX_SAFE_INTEGER 之间,是否有任何不准确的可能性?这是一个浮点计算,所以我很怀疑。

还有其他快速获取 53b 整数高值的方法吗?

编辑:如果您的计算机速度非常快,可以测试第三种方法

for (let v = 0, high = 0; high <= 2 ** 21; high++) {
    for (let low = 0; low < 2 ** 32; low++) {
        if (Math.trunc(v++ / 2 ** 32) != high) throw "Error at " + v;
    }
}

在我的笔记本电脑上,这需要 6 年半的时间。

提取位

鉴于value是一个整数,其大小不超过Number.MAX_SAFE_INTEGER(253−1),则二进制数的低32位对于 value 可以通过以下方式获得:

value % 4294967296

高位可以通过以下方式获得:

Math.trunc(value / 4294967296)

(4,294,967,296 是 232。由于我不经常使用 JavaScript,所以我不会对在源代码中编写它的最佳方式发表意见。当然 4294967296 是正确的,但它并不意味着它是 232 的事实。(1<<32) 可能是一个选项。)

请注意,如果 value 为正,则通过这种方式获得的结果将为正(或零),如果 value 为负,则为负。如果 value 为负时需要不同的东西,则必须进行一些调整。

理由

JavaScript 是 ECMAScript 的一个实现。对于此处的参考,我使用 the 2020 specification of ECMA-262.

Number类型上的%操作符提供了Number::remainder操作(6.1.6,Table2),具体在6.1.6.1.6 .对于手头的情况(有限股息和除数,除数非零):

… the floating-point remainder r from a dividend n and a divisor d is defined by the mathematical relation r = n - (d × q) where q is an integer that is negative only if n/d is negative and positive only if n/d is positive, and whose magnitude is as large as possible without exceeding the magnitude of the true mathematical quotient of n and d. r is computed and rounded to the nearest representable value using IEEE 754-2019 roundTiesToEven mode.

关于取整的文字对于余数运算来说是多余的。由于 r 的大小不能大于 nd 中较小的一个,它表示为Number 格式至少和 each 一样精细,因此 Number 格式能够准确表示 r

所以 value % 4294967296 准确地给出了 value 的低 32 位。

Math.trunc(value / 4294967296)中,除法是二的幂。在Number格式中,value表示为f•2e,对于一些有效数f(这里包括符号)和一些指数e。那么value / 4294967296的数学结果就是f•2e−32。由于value是一个整数,e远离Number格式的指数下界(−1022),e−32 也离它很远,所以没有发生下溢。也就是说f•2e−32是可以精确表示的,所以计算时没有舍入误差value / 4294967296。然后 Math.trunc 取整数部分,产生 value 的高位,没有错误。