diff --git a/Zend/tests/pow_fpu_precision.phpt b/Zend/tests/pow_fpu_precision.phpt new file mode 100644 index 000000000000..74779b7ece9a --- /dev/null +++ b/Zend/tests/pow_fpu_precision.phpt @@ -0,0 +1,73 @@ +--TEST-- +The ** operator must not lose precision when the FPU is clamped to double precision +--INI-- +serialize_precision=-1 +--FILE-- + +--EXPECT-- +-- runtime -- +float(0.01) +float(0.001) +float(0.0001) +float(0.04) +float(0.008) +float(0.0016) +-- compile-time constant folding -- +float(0.01) +float(0.001) +float(0.0001) +float(0.04) +float(0.008) +float(0.0016) +-- exact powers of two are unaffected -- +float(0.5) +float(0.0009765625) +-- identical to the decimal literal -- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/Zend/zend_float.h b/Zend/zend_float.h index 12aa02005c2a..33a130c0cc04 100644 --- a/Zend/zend_float.h +++ b/Zend/zend_float.h @@ -20,6 +20,8 @@ #include "zend_portability.h" +#include + BEGIN_EXTERN_C() /* @@ -413,4 +415,22 @@ END_EXTERN_C() #endif /* FPU CONTROL */ +/* zend_init_fpu() clamps the x87 FPU to double precision (53-bit mantissa) for + * the whole request. libm's pow() relies on the FPU running at extended + * precision internally, so while that clamp is in place its result is off by + * one or more ULP compared to platforms whose FPU has no precision control. + * Restore extended precision for the call and truncate the result back to + * double. Compiles down to a plain pow() everywhere XPFPA_HAVE_CW is 0, which + * includes x86-64 and arm64. */ +static zend_always_inline double zend_pow(double base, double exponent) +{ +#if XPFPA_HAVE_CW + XPFPA_DECLARE + XPFPA_SWITCH_DOUBLE_EXTENDED(); + XPFPA_RETURN_DOUBLE(pow(base, exponent)); +#else + return pow(base, exponent); +#endif +} + #endif diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index b1b1a39a536a..7873d6c6fa15 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -1347,7 +1347,7 @@ static double safe_pow(double base, double exponent) zend_power_base_0_exponent_lt_0_error(); } - return pow(base, exponent); + return zend_pow(base, exponent); } static zend_result ZEND_FASTCALL pow_function_base(zval *result, zval *op1, zval *op2) /* {{{ */ diff --git a/ext/standard/math.c b/ext/standard/math.c index e13f153f63e2..c2cb23ca4c5b 100644 --- a/ext/standard/math.c +++ b/ext/standard/math.c @@ -1485,7 +1485,7 @@ PHP_FUNCTION(fpow) Z_PARAM_DOUBLE(exponent) ZEND_PARSE_PARAMETERS_END(); - RETURN_DOUBLE(pow(base, exponent)); + RETURN_DOUBLE(zend_pow(base, exponent)); } /* }}} */ diff --git a/ext/standard/tests/math/fpow_fpu_precision.phpt b/ext/standard/tests/math/fpow_fpu_precision.phpt new file mode 100644 index 000000000000..f29272d8635e --- /dev/null +++ b/ext/standard/tests/math/fpow_fpu_precision.phpt @@ -0,0 +1,75 @@ +--TEST-- +fpow(): results must not lose precision when the FPU is clamped to double precision +--INI-- +serialize_precision=-1 +--FILE-- + +--EXPECT-- +-- precision -- +float(0.01) +float(0.001) +float(0.0001) +float(0.04) +float(0.008) +float(0.0016) +-- exact powers of two are unaffected -- +float(0.5) +float(0.0009765625) +-- identical to the decimal literal -- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +-- matches the ** operator -- +bool(true) +bool(true) +-- IEEE-754 semantics are preserved -- +float(INF) +float(-INF) +float(1) +float(1) +float(1) +float(NAN) diff --git a/ext/standard/tests/math/pow_fpu_precision.phpt b/ext/standard/tests/math/pow_fpu_precision.phpt new file mode 100644 index 000000000000..f4ad4a76450a --- /dev/null +++ b/ext/standard/tests/math/pow_fpu_precision.phpt @@ -0,0 +1,73 @@ +--TEST-- +pow() must not lose precision when the FPU is clamped to double precision +--INI-- +serialize_precision=-1 +--FILE-- + +--EXPECT-- +-- runtime -- +float(0.01) +float(0.001) +float(0.0001) +float(0.04) +float(0.008) +float(0.0016) +-- compile-time constant folding -- +float(0.01) +float(0.001) +float(0.0001) +float(0.04) +float(0.008) +float(0.0016) +-- exact powers of two are unaffected -- +float(0.5) +float(0.0009765625) +-- identical to the decimal literal -- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true)