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 | |
| 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
| -rw-r--r-- | TODO | 2 | ||||
| -rw-r--r-- | src/BitOps/BinaryConversion.php | 73 | ||||
| -rw-r--r-- | src/BitOps/FloatTraits.php | 2 | ||||
| -rw-r--r-- | src/WebAssembly/Execution/NumericOps.php | 26 |
4 files changed, 80 insertions, 23 deletions
@@ -4,8 +4,6 @@ * Provide sane bindings to PHP * Write PHPDoc for public APIs * Provide high-level APIs -* Eliminate use of FFI, except for optimization - * strtof NOTE: 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; diff --git a/src/WebAssembly/Execution/NumericOps.php b/src/WebAssembly/Execution/NumericOps.php index f7a1e89..7d5e57c 100644 --- a/src/WebAssembly/Execution/NumericOps.php +++ b/src/WebAssembly/Execution/NumericOps.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Nsfisis\Waddiwasi\WebAssembly\Execution; -use FFI; use Nsfisis\Waddiwasi\BitOps\BinaryConversion; use Nsfisis\Waddiwasi\BitOps\FloatOps; use RoundingMode; @@ -70,13 +69,13 @@ final readonly class NumericOps public static function f32ConvertI64S(int $x): float { - return self::castBigIntToF32((string)$x); + return BinaryConversion::convertBigIntToF32((string)$x); } public static function f32ConvertI64U(int $x): float { $x = self::convertS64ToBigUInt($x); - return self::castBigIntToF32($x); + return BinaryConversion::convertBigIntToF32($x); } public static function f32CopySign(float $x, float $y): float @@ -1165,6 +1164,9 @@ final readonly class NumericOps } } + /** + * @return numeric-string + */ public static function convertS64ToBigUInt(int $x): string { if ($x < 0) { @@ -1190,22 +1192,4 @@ final readonly class NumericOps } return (int)$result; } - - - private static function castBigIntToF32(string $x): float - { - // @phpstan-ignore-next-line - return self::ffi()->strtof($x, null); - } - - private static function ffi(): FFI - { - static $ffi; - if (!$ffi) { - $ffi = FFI::cdef( - 'float strtof(const char *restrict nptr, char **restrict endptr);', - ); - } - return $ffi; - } } |
