[php-src] PR #23578: Fix pow(), ** and fpow() losing precision on 32-bit x86

From: Date: Sat, 05 Sep 2026 04:31:49 +0000
Subject: [php-src] PR #23578: Fix pow(), ** and fpow() losing precision on 32-bit x86
Groups: php.git-pulls 
Request: Send a blank email to git-pulls+get-38317@lists.php.net to get a copy of this message
Pull Request: https://github.com/php/php-src/pull/23578 Author: marc-mabe ### Problem On 32-bit x86, the ** operator, pow() and fpow() return results that are off by one or more ULP compared to every other platform: ```php var_dump(10 ** -2); // i386: float(0.010000000000000002) // other: float(0.01) var_dump(5 ** -4); // i386: float(0.0016000000000000007) // other: float(0.0016) var_dump(fpow(10.0, -2.0)); // i386: float(0.010000000000000002) // other: float(0.01) var_dump(10 ** -2 === 0.01); // i386: false, everywhere else: true ``` The results are not merely different, they are less accurate: measured against a high-precision reference, the i386 values are not correctly rounded while the x86-64 and arm64 ones are. This makes exponentiation silently architecture-dependent, so float comparisons against literals fail, cached or serialized values differ between machines, and the error propagates into anything built on top. ### Cause init_executor() calls zend_init_fpu(), which clamps the x87 FPU to double precision (a 53-bit mantissa) for the whole request so that PHP's own double arithmetic behaves like IEEE-754 binary64 rather than carrying x87's 64-bit excess precision. libm's pow(), however, computes on that same x87 unit and relies on the extended range internally in order to round correctly to double. While the clamp is in place it loses that headroom, so pow() inside PHP is less accurate than the very same libm pow() is outside PHP. ### Fix zend_pow() in Zend/zend_float.h restores extended precision for the duration of the libm call and truncates the result back to double on return, using the XPFPA macros already in that header. Both call sites go through it: - safe_pow() in Zend/zend_operators.c, which backs the ** operator and pow() - PHP_FUNCTION(fpow) in ext/standard/math.c, which called libm pow() directly and so did not benefit from a fix to safe_pow() alone Wherever XPFPA_HAVE_CW is 0 — x86-64, arm64, and every other target without x87 precision control — zend_pow() compiles down to a plain pow() call, so no other platform is affected in behaviour or code generation. The i386 failure is exactly the precision assertions; the reference points and the IEEE-754 cases pass in both states. ### Notes * I came across this by my work on #19079 where some tests were failing because of these differences * This PR is mostly LLM work - I manually verified * This is the first PR - PRs for exp, expm1 and sinh will follow

« previous php.git-pulls (#38317) next »