diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-04-07 00:26:55 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-04-07 01:52:27 +0900 |
| commit | 28b496687c0eb36a035a8686794fa45434957e28 (patch) | |
| tree | f660f257eb72a040f693882a502adb1416e42257 /src/BitOps | |
| parent | 7bc9e73c59e6ec7e4f9feef785e08dc56fbb09eb (diff) | |
| download | php-waddiwasi-28b496687c0eb36a035a8686794fa45434957e28.tar.gz php-waddiwasi-28b496687c0eb36a035a8686794fa45434957e28.tar.zst php-waddiwasi-28b496687c0eb36a035a8686794fa45434957e28.zip | |
feat: do not use FFI to cast integers to f32
Diffstat (limited to 'src/BitOps')
| -rw-r--r-- | src/BitOps/BinaryConversion.php | 73 | ||||
| -rw-r--r-- | src/BitOps/FloatTraits.php | 2 |
2 files changed, 75 insertions, 0 deletions
diff --git a/src/BitOps/BinaryConversion.php b/src/BitOps/BinaryConversion.php index 14b33c9..ae4e403 100644 --- a/src/BitOps/BinaryConversion.php +++ b/src/BitOps/BinaryConversion.php @@ -196,6 +196,16 @@ final readonly class BinaryConversion } /** + * @param numeric-string $x + */ + public static function convertBigIntToF32(string $x): float + { + // PHP's (float) cast is through f64, so we have to convert it to f32 directly. + // i64 => f32 is not equal to i64 => f64 => f32. + return self::reinterpretI32AsF32(self::convertBigIntToF32Bits($x)); + } + + /** * @return non-empty-string */ private static function packInt(PackIntSpecifiers $spec, int $x): string @@ -237,6 +247,69 @@ final readonly class BinaryConversion return $result[1]; } + /** + * @param numeric-string $value + */ + private static function convertBigIntToF32Bits(string $value): int + { + if ($value === '0') { + return 0; + } + + // Sign + if (bccomp($value, '0') < 0) { + $sign = Signedness::Signed; + $value = bcsub('0', $value); + } else { + $sign = Signedness::Unsigned; + } + + // Exponent + if (bccomp($value, "9223372036854775807") <= 0) { + $e = strlen(decbin((int)$value)) - 1; + } else { + for ($i = 63; ; $i++) { + if (bccomp($value, bcpow('2', (string)$i)) < 0) { + $e = $i - 1; + break; + } + } + assert(isset($e)); + } + + // Infinity + if ($e >= 128) { + return FloatTraits::getF32SignBit($sign) | FloatTraits::F32_INFINITY_BITS; + } + + // Mantissa + $p = bcpow('2', (string)$e); // p = 2^e + $numerator = bcmul(bcsub($value, $p), (string)(1 << 23)); // (value - p) * 2^23 + $quotient = (int)bcdiv($numerator, $p, scale: 0); + $remainder = bcmod($numerator, $p); + + // Round + $half = bcdiv($p, '2', scale: 0); + if (bccomp($remainder, $half) > 0) { + $quotient += 1; + } elseif ($remainder === $half) { + // Half to even + if ($quotient % 2 === 1) { + $quotient += 1; + } + } + if ($quotient >= (1 << 23)) { + $quotient -= (1 << 23); + $e += 1; + // Infinity + if ($e >= 128) { + return FloatTraits::getF32SignBit($sign) | FloatTraits::F32_INFINITY_BITS; + } + } + + return FloatTraits::getF32SignBit($sign) | (($e + 127) << FloatTraits::F32_MANTISSA_BITS) | $quotient; + } + private static function isLittleEndian(): bool { return pack("s", ord("a"))[0] === "a"; diff --git a/src/BitOps/FloatTraits.php b/src/BitOps/FloatTraits.php index dca9e18..75eebcb 100644 --- a/src/BitOps/FloatTraits.php +++ b/src/BitOps/FloatTraits.php @@ -17,6 +17,8 @@ final readonly class FloatTraits public const int F32_SIGN_SIGNED = 0b10000000_00000000_00000000_00000000; public const int F32_EXPONENT_NAN = 0b01111111_10000000_00000000_00000000; + public const int F32_INFINITY_BITS = 0b01111111_10000000_00000000_00000000; + public const int F64_EXPONENT_BITS = 11; public const int F64_MANTISSA_BITS = 52; |
