使用 bcmath 在 32 位系统上读取二进制 long

在 32 位系统上,大于 0x7FFFFFFF 的整数不能原始存储,而 0x00000000800000000x7FFFFFFFFFFFFFFF 之间的整数可以原始存储在 64 位系统上,而不能存储在 32 位系统中(signed long long)。但是,由于 64 位系统和许多其他语言支持存储 signed long long 整数,因此有时需要将此整数范围存储在精确值中。有几种方法可以做到这一点,例如创建一个包含两个数字的数组,或者将整数转换为十进制的人类可读形式。这有几个优点,例如向用户呈现的便利性,以及直接用 bcmath 操作它的能力。

所述 pack / unpack 方法可用于为二进制字节和数字的十进制形式之间进行转换(两型 string 的,但一个是二进制,一个是 ASCII),但它们将总是试图将 ASCII 字符串浇铸成一个 32 位的在 32 位系统上的 int。以下代码段提供了另一种选择:

/** Use pack("J") or pack("p") for 64-bit systems */
function writeLong(string $ascii) : string {
    if(bccomp($ascii, "0") === -1) { // if $ascii < 0
        // 18446744073709551616 is equal to (1 << 64)
        // remember to add the quotes, or the number will be parsed as a float literal
        $ascii = bcadd($ascii, "18446744073709551616");
    }

    // "n" is big-endian 16-bit unsigned short. Use "v" for small-endian.
    return pack("n", bcmod(bcdiv($ascii, "281474976710656"), "65536")) .
        pack("n", bcmod(bcdiv($ascii, "4294967296"), "65536")) .
        pack("n", bcdiv($ascii, "65536"), "65536")) .
        pack("n", bcmod($ascii, "65536"));
}

function readLong(string $binary) : string {
    $result = "0";
    $result = bcadd($result, unpack("n", substr($binary, 0, 2)));
    $result = bcmul($result, "65536");
    $result = bcadd($result, unpack("n", substr($binary, 2, 2)));
    $result = bcmul($result, "65536");
    $result = bcadd($result, unpack("n", substr($binary, 4, 2)));
    $result = bcmul($result, "65536");
    $result = bcadd($result, unpack("n", substr($binary, 6, 2)));

    // if $binary is a signed long long
    // 9223372036854775808 is equal to (1 << 63) (note that this expression actually does not work even on 64-bit systems)
    if(bccomp($result, "9223372036854775808") !== -1) { // if $result >= 9223372036854775807
        $result = bcsub($result, "18446744073709551616"); // $result -= (1 << 64)
    }
    return $result;
}