Skip to content

Commit 5be30f2

Browse files
committed
Support 32-bit PHP in fromFloatExact()
1 parent c6620fe commit 5be30f2

3 files changed

Lines changed: 49 additions & 40 deletions

File tree

src/BigDecimal.php

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Override;
1717

1818
use function assert;
19+
use function chr;
1920
use function in_array;
2021
use function ini_set;
2122
use function intdiv;
@@ -167,7 +168,7 @@ public static function ten(): BigDecimal
167168
* Note that BigDecimal has no concept of negative zero, so `-0.0` and `0.0` both convert to zero.
168169
*
169170
* @throws InvalidArgumentException If the value is NaN or infinite.
170-
* @throws UnsupportedPlatformException If PHP is not 64-bit, or if the platform uses a non-IEEE-754 double format.
171+
* @throws UnsupportedPlatformException If the platform uses a non-IEEE-754 double format.
171172
*
172173
* @pure
173174
*/
@@ -180,36 +181,64 @@ public static function fromFloatExact(float $value): BigDecimal
180181
throw InvalidArgumentException::cannotConvertFloat($value > 0 ? 'INF' : '-INF');
181182
}
182183

183-
if (PHP_INT_SIZE < 8) {
184-
throw UnsupportedPlatformException::require64BitPhp();
185-
}
186-
187184
if (pack('E', 1.0) !== "\x3f\xf0\x00\x00\x00\x00\x00\x00") {
188185
throw UnsupportedPlatformException::unsupportedFloatFormat();
189186
}
190187

191-
// Extract the raw IEEE-754 bit pattern as a 64-bit integer (big-endian).
192-
/** @var array{bits: int} $unpacked */
193-
$unpacked = unpack('Jbits', pack('E', $value));
194-
$bits = $unpacked['bits'];
188+
if (PHP_INT_SIZE >= 8) {
189+
// 64-bit: extract the IEEE-754 bit pattern as a 64-bit integer.
190+
/** @var array{1: int} $unpacked */
191+
$unpacked = unpack('J', pack('E', $value));
192+
$bits = $unpacked[1];
195193

196-
// Fields: [sign(1)|exp(11)|mantissa(52)]
197-
$signBit = ($bits >> 63) & 1;
198-
$expBits = ($bits >> 52) & 0x7FF;
199-
$mantissa = $bits & 0xFFFFFFFFFFFFF;
194+
// Bits: [sign(1)|exp(11)|mantissa(52)]
195+
$signBit = ($bits >> 63) & 1;
196+
$expBits = ($bits >> 52) & 0x7FF;
197+
$mantissa = $bits & 0xFFFFFFFFFFFFF;
200198

201-
// Zero (covers both 0.0 and -0.0).
202-
if ($expBits === 0 && $mantissa === 0) {
203-
return BigDecimal::zero();
199+
// Zero (covers both 0.0 and -0.0).
200+
if ($expBits === 0 && $mantissa === 0) {
201+
return BigDecimal::zero();
202+
}
203+
204+
if ($expBits === 0) {
205+
$significand = BigInteger::of($mantissa);
206+
} else {
207+
$significand = BigInteger::of(0x10000000000000 | $mantissa);
208+
}
209+
} else {
210+
// 32-bit: extract the IEEE-754 bit pattern as 8 bytes.
211+
$packed = pack('E', $value);
212+
213+
// Get the first 16 bits as an integer.
214+
/** @var array{1: int} $unpacked */
215+
$unpacked = unpack('n', $packed);
216+
$header = $unpacked[1];
217+
218+
// Bits: [sign(1)|exp(11)|mantissa(4)] in header (bytes 0-1) + 48 bits of mantissa in bytes 2-7
219+
$signBit = ($header >> 15) & 1;
220+
$expBits = ($header >> 4) & 0x7FF;
221+
$mantissaBytes = chr($header & 0x0F) . substr($packed, 2);
222+
223+
// Zero (covers both 0.0 and -0.0).
224+
if ($expBits === 0 && $mantissaBytes === "\x00\x00\x00\x00\x00\x00\x00") {
225+
return BigDecimal::zero();
226+
}
227+
228+
$mantissa = BigInteger::fromBytes($mantissaBytes, false);
229+
230+
if ($expBits === 0) {
231+
$significand = $mantissa;
232+
} else {
233+
// Implicit leading 1-bit (2^52).
234+
$significand = $mantissa->plus(BigInteger::of(1)->shiftedLeft(52));
235+
}
204236
}
205237

206238
if ($expBits === 0) {
207239
// Subnormal: no implicit leading 1-bit; effective exponent = -1074.
208-
$significand = BigInteger::of($mantissa);
209240
$baseExp = -1074;
210241
} else {
211-
// Normal: implicit leading 1-bit (2^52).
212-
$significand = BigInteger::of(0x10000000000000 | $mantissa);
213242
$baseExp = $expBits - 1075; // biased exp - 1023 (bias) - 52 (mantissa shift)
214243
}
215244

src/Exception/UnsupportedPlatformException.php

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,6 @@ public function __construct(string $message)
2121
parent::__construct($message);
2222
}
2323

24-
/**
25-
* @internal
26-
*
27-
* @pure
28-
*/
29-
public static function require64BitPhp(): self
30-
{
31-
return new self('This feature requires 64-bit PHP.');
32-
}
33-
3424
/**
3525
* @internal
3626
*

tests/BigDecimalTest.php

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
use Brick\Math\Exception\NegativeNumberException;
1212
use Brick\Math\Exception\NumberFormatException;
1313
use Brick\Math\Exception\RoundingNecessaryException;
14-
use Brick\Math\Exception\UnsupportedPlatformException;
1514
use Brick\Math\RoundingMode;
1615
use Generator;
1716
use LogicException;
@@ -32,7 +31,6 @@
3231
use const PHP_FLOAT_MIN;
3332
use const PHP_INT_MAX;
3433
use const PHP_INT_MIN;
35-
use const PHP_INT_SIZE;
3634

3735
/**
3836
* Unit tests for class BigDecimal.
@@ -3985,15 +3983,7 @@ public function testDirectCallToUnserialize(): void
39853983
#[DataProvider('providerFromFloatExact')]
39863984
public function testFromFloatExact(float $value, string $expected): void
39873985
{
3988-
if (PHP_INT_SIZE !== 8) {
3989-
$this->expectException(UnsupportedPlatformException::class);
3990-
}
3991-
3992-
$actual = BigDecimal::fromFloatExact($value);
3993-
3994-
if (PHP_INT_SIZE === 8) {
3995-
self::assertBigDecimalEquals($expected, $actual);
3996-
}
3986+
self::assertBigDecimalEquals($expected, BigDecimal::fromFloatExact($value));
39973987
}
39983988

39993989
public static function providerFromFloatExact(): Generator

0 commit comments

Comments
 (0)