aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/NextAfter.php128
1 files changed, 128 insertions, 0 deletions
diff --git a/src/NextAfter.php b/src/NextAfter.php
new file mode 100644
index 0000000..e2b7706
--- /dev/null
+++ b/src/NextAfter.php
@@ -0,0 +1,128 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Nsfisis\NextAfter;
+
+use function assert;
+use function is_infinite;
+use function is_nan;
+use function pack;
+use function unpack;
+use const INF;
+use const NAN;
+use const PHP_FLOAT_MIN;
+use const PHP_INT_SIZE;
+
+final class NextAfter
+{
+ private function __construct()
+ {
+ }
+
+ /**
+ * @phpstan-pure
+ */
+ public static function nextAfter(float $x, float $y): float
+ {
+ if (is_nan($x) || is_nan($y)) {
+ return NAN;
+ }
+ if ($x === $y) {
+ return $y;
+ }
+ return $x < $y ? self::nextUp($x) : self::nextDown($x);
+ }
+
+ /**
+ * @phpstan-pure
+ */
+ public static function nextUp(float $x): float
+ {
+ if (is_nan($x)) {
+ return NAN;
+ }
+ if (is_infinite($x) && $x > 0) {
+ return INF;
+ }
+ if ($x === 0.0) {
+ return PHP_FLOAT_MIN;
+ }
+ $u = self::floatToInt($x);
+ return $x > 0.0 ? self::intToFloat($u + 1) :
+ self::intToFloat($u - 1);
+
+ }
+
+ /**
+ * @phpstan-pure
+ */
+ public static function nextDown(float $x): float
+ {
+ if (is_nan($x)) {
+ return NAN;
+ }
+ if (is_infinite($x) && $x < 0) {
+ return -INF;
+ }
+ if ($x === 0.0) {
+ return -PHP_FLOAT_MIN;
+ }
+ $u = self::floatToInt($x);
+ return $x > 0.0 ? self::intToFloat($u - 1) :
+ self::intToFloat($u + 1);
+
+ }
+
+ /**
+ * @phpstan-pure
+ */
+ private static function intToFloat(int $x): float
+ {
+ return self::unpackFloat64(self::packInt64($x));
+ }
+
+ /**
+ * @phpstan-pure
+ */
+ private static function floatToInt(float $x): int
+ {
+ return self::unpackInt64(self::packFloat64($x));
+ }
+
+ /**
+ * @phpstan-pure
+ */
+ private static function unpackFloat64(string $s): float
+ {
+ assert(PHP_FLOAT_DIG === 15); // @phpstan-ignore-line
+ return unpack('d', $s)[1]; // @phpstan-ignore-line
+ }
+
+ /**
+ * @phpstan-pure
+ */
+ private static function packInt64(int $x): string
+ {
+ assert(PHP_INT_SIZE === 8); // @phpstan-ignore-line
+ return pack('q', $x);
+ }
+
+ /**
+ * @phpstan-pure
+ */
+ private static function packFloat64(float $x): string
+ {
+ assert(PHP_FLOAT_DIG === 15); // @phpstan-ignore-line
+ return pack('d', $x);
+ }
+
+ /**
+ * @phpstan-pure
+ */
+ private static function unpackInt64(string $s): int
+ {
+ assert(PHP_INT_SIZE === 8); // @phpstan-ignore-line
+ return unpack('q', $s)[1]; // @phpstan-ignore-line
+ }
+}