aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/BitOps
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-04-07 00:26:55 +0900
committernsfisis <nsfisis@gmail.com>2025-04-07 01:52:27 +0900
commit28b496687c0eb36a035a8686794fa45434957e28 (patch)
treef660f257eb72a040f693882a502adb1416e42257 /src/BitOps
parent7bc9e73c59e6ec7e4f9feef785e08dc56fbb09eb (diff)
downloadphp-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.php73
-rw-r--r--src/BitOps/FloatTraits.php2
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;