Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ext/standard/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ public function getFunctions(): array
new frexp(),
new fdiv(),
...(CompilerVersion::supportsFpow() ? [new fpow(), new fmin(), new fmax()] : []),
...(CompilerVersion::supportsNextafter() ? [new nextafter()] : []),
new intval(),
new floatval(),
new doubleval(),
Expand Down
34 changes: 34 additions & 0 deletions ext/standard/VmMath.php
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,40 @@ public static function fpow(float $num, float $exponent): float
return \pow($num, $exponent);
}

/**
* nextafter() — IEEE next representable float toward $next (php-src ext/standard/math.c; #9241).
*/
public static function nextafter(float $num, float $next): float
{
if (\function_exists('nextafter')) {
return \nextafter($num, $next);
}
if (\is_nan($num)) {
return $num;
}
if (\is_nan($next)) {
return $next;
}
if ($num === $next) {
return $next;
}
if (0.0 === $num || -0.0 === $num) {
if ($next > 0.0) {
return \unpack('d', \pack('P', 1))[1];
}

return \unpack('d', \pack('P', 0x8000000000000001))[1];
}
$bits = \unpack('P', \pack('d', $num))[1];
if (($num > 0.0) === ($next > $num)) {
++$bits;
} else {
--$bits;
}

return \unpack('d', \pack('P', $bits))[1];
}

/** IEEE fmin pair (php-src ext/standard/math.c zend_fmin; #11728). */
public static function fminPair(float $a, float $b): float
{
Expand Down
66 changes: 66 additions & 0 deletions ext/standard/nextafter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\ext\standard;

use PHPCompiler\Frame;
use PHPCompiler\Func\Internal;
use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\Variable as JITVariable;
use PHPLLVM\Value;

/**
* nextafter() — IEEE next representable float (PHP 8.4, ext/standard/math.c).
*
* php-src: ext/standard/math.c — PHP_FUNCTION(nextafter)
*/
final class nextafter extends Internal
{
private const FUNCTION = 'nextafter';

public function __construct()
{
parent::__construct(self::FUNCTION);
}

public function execute(Frame $frame): void
{
if (2 !== \count($frame->calledArgs)) {
throw new \LogicException(self::FUNCTION.'() requires exactly two arguments');
}
$num = VmMath::parseDoubleBuiltinArg(
$frame->calledArgs[0]->resolveIndirect(),
self::FUNCTION,
1,
'num'
);
$next = VmMath::parseDoubleBuiltinArg(
$frame->calledArgs[1]->resolveIndirect(),
self::FUNCTION,
2,
'next'
);
if (null === $frame->returnVar) {
return;
}
$frame->returnVar->float(VmMath::nextafter($num, $next));
}

public function call(Context $context, JITVariable ...$args): Value
{
if (2 !== \count($args)) {
throw new \LogicException(self::FUNCTION.'() requires exactly two arguments');
}
[$num, $next] = JitFdiv::lowerOperands(
$context,
$args[0],
$args[1],
self::FUNCTION,
'num',
'next'
);

return $context->builder->call($context->lookupFunction('nextafter'), $num, $next);
}
}
6 changes: 6 additions & 0 deletions lib/CompilerVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,12 @@ public static function supportsFpow(): bool
return version_compare(self::VERSION, '8.4.0', '>=');
}

/** PHP 8.4+ nextafter() IEEE next representable float (ext/standard/math.c; #9241). */
public static function supportsNextafter(): bool
{
return version_compare(self::VERSION, '8.4.0', '>=');
}

/**
* PHP 8.4+ RoundingMode builtin enum (ext/standard/basic_functions.stub.php; #5934, #14846).
*
Expand Down
4 changes: 4 additions & 0 deletions test/aot/AotTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ public static function providePHPTests(): \Generator
&& (str_contains($name, 'fpow') || str_contains($name, 'fmin') || str_contains($name, 'fmax'))) {
continue;
}
if (!CompilerVersion::supportsNextafter()
&& str_contains($name, 'nextafter')) {
continue;
}
if (!CompilerVersion::supportsRoundingModeEnum()
&& (str_contains($name, 'rounding_mode') || str_contains($name, 'bcround'))
&& !str_contains($name, 'rounding_mode_reference_profile')) {
Expand Down
7 changes: 6 additions & 1 deletion test/compliance/JITTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ public static function providePHPTests(): \Generator
&& !str_contains($name, 'php84_math_string_builtins_phantom')) {
continue;
}
if (!CompilerVersion::supportsNextafter()
&& str_contains($name, 'nextafter')
&& !str_contains($name, 'php84_math_string_builtins_phantom')) {
continue;
}
if (!CompilerVersion::supportsRoundingModeEnum()
&& (str_contains($name, 'rounding_mode') || str_contains($name, 'bcround'))
&& !str_contains($name, 'rounding_mode_reference_profile')) {
Expand Down Expand Up @@ -68,7 +73,7 @@ public static function providePHPTests(): \Generator
&& str_contains($name, 'get_declared_exclude_deprecated_reference_profile')) {
continue;
}
if ((CompilerVersion::supportsStrIncrement() || CompilerVersion::supportsFpow())
if ((CompilerVersion::supportsStrIncrement() || CompilerVersion::supportsFpow() || CompilerVersion::supportsNextafter())
&& str_contains($name, 'php84_math_string_builtins_phantom')) {
continue;
}
Expand Down
7 changes: 6 additions & 1 deletion test/compliance/VMTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public static function providePHPTests(): \Generator
&& !str_contains($name, 'php84_math_string_builtins_phantom')) {
continue;
}
if (!CompilerVersion::supportsNextafter()
&& str_contains($name, 'nextafter')
&& !str_contains($name, 'php84_math_string_builtins_phantom')) {
continue;
}
if (!CompilerVersion::supportsRoundingModeEnum()
&& (str_contains($name, 'rounding_mode') || str_contains($name, 'bcround'))
&& !str_contains($name, 'rounding_mode_reference_profile')) {
Expand Down Expand Up @@ -63,7 +68,7 @@ public static function providePHPTests(): \Generator
&& str_contains($name, 'get_declared_exclude_deprecated_reference_profile')) {
continue;
}
if ((CompilerVersion::supportsStrIncrement() || CompilerVersion::supportsFpow())
if ((CompilerVersion::supportsStrIncrement() || CompilerVersion::supportsFpow() || CompilerVersion::supportsNextafter())
&& str_contains($name, 'php84_math_string_builtins_phantom')) {
continue;
}
Expand Down
19 changes: 19 additions & 0 deletions test/compliance/cases/stdlib/nextafter_basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
stdlib nextafter() — IEEE next representable float (PHP 8.4, ext/standard/math.c, #9241)
--FILE--
<?php
declare(strict_types=1);

var_export(function_exists('nextafter'));
echo "\n";
var_export(nextafter(1.0, 2.0));
echo "\n";
var_export(nextafter(1.0, 0.0));
echo "\n";
var_export(nextafter(0.0, 1.0) > 0.0);
echo "\n";
--EXPECT--
true
1.0000000000000002
0.9999999999999999
true
13 changes: 13 additions & 0 deletions test/compliance/cases/stdlib/nextafter_basic_jit.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
stdlib nextafter() JIT/AOT — IEEE next representable float (#9241)
--FILE--
<?php
declare(strict_types=1);

var_export(nextafter(1.0, 2.0));
echo "\n";
var_export(nextafter(1.0, 0.0));
echo "\n";
--EXPECT--
1.0000000000000002
0.9999999999999999
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ stdlib str_increment/str_decrement/fpow — not advertised on PHP 8.2 reference
--FILE--
<?php
$bad = array_filter(
['str_increment', 'str_decrement', 'fpow'],
['str_increment', 'str_decrement', 'fpow', 'nextafter'],
static fn (string $fn): bool => function_exists($fn)
);
echo [] === $bad ? "ok\n" : "fail\n";
Expand Down
13 changes: 13 additions & 0 deletions test/fixtures/aot/cases/nextafter_basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
AOT: nextafter() IEEE next representable float (#9241, ext/standard/math.c)
--FILE--
<?php
declare(strict_types=1);

var_export(nextafter(1.0, 2.0));
echo "\n";
var_export(nextafter(1.0, 0.0));
echo "\n";
--EXPECT--
1.0000000000000002
0.9999999999999999
27 changes: 27 additions & 0 deletions test/repro/maintainer_gap_nextafter_basic.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

/**
* Maintainer repro: nextafter() must exist on PHP 8.4+ profile (#9241).
*
* php-src: ext/standard/math.c — PHP_FUNCTION(nextafter)
*/

if (!\function_exists('nextafter')) {
echo "skip: nextafter not registered on reference profile\n";
exit(0);
}

$towardInf = nextafter(1.0, 2.0);
$towardZero = nextafter(1.0, 0.0);
if (1.0000000000000002 !== $towardInf) {
echo "fail: toward +inf expected 1.0000000000000002 got {$towardInf}\n";
exit(1);
}
if ($towardZero >= 1.0 || $towardZero <= 0.0) {
echo "fail: toward 0.0 expected (0,1) got {$towardZero}\n";
exit(1);
}

echo "ok\n";
5 changes: 5 additions & 0 deletions test/unit/CompilerVersionBuiltinAdvertisementTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ public function testFpowWithheldOnReferenceProfileUntilStable84(): void
$this->assertFalse(CompilerVersion::supportsFpow());
}

public function testNextafterWithheldOnReferenceProfileUntilStable84(): void
{
$this->assertFalse(CompilerVersion::supportsNextafter());
}

public function testRoundingModeEnumWithheldOnReferenceProfile(): void
{
$this->assertFalse(CompilerVersion::supportsRoundingModeEnum());
Expand Down
5 changes: 5 additions & 0 deletions test/unit/CompilerVersionGateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ public function testSupportsFpowFalseOnReferenceProfile(): void
$this->assertFalse(CompilerVersion::supportsFpow());
}

public function testSupportsNextafterFalseOnReferenceProfile(): void
{
$this->assertFalse(CompilerVersion::supportsNextafter());
}

public function testSupportsRoundingModeEnumFalseOnReferenceProfile(): void
{
$this->assertFalse(CompilerVersion::supportsRoundingModeEnum());
Expand Down