diff --git a/Zend/zend_float.h b/Zend/zend_float.h index 12aa02005c2a..81148e3b99ef 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 exp() 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 exp() everywhere XPFPA_HAVE_CW is 0, which + * includes x86-64 and arm64. */ +static zend_always_inline double zend_exp(double num) +{ +#if XPFPA_HAVE_CW + XPFPA_DECLARE + XPFPA_SWITCH_DOUBLE_EXTENDED(); + XPFPA_RETURN_DOUBLE(exp(num)); +#else + return exp(num); +#endif +} + #endif diff --git a/ext/standard/math.c b/ext/standard/math.c index e13f153f63e2..2640c99dc067 100644 --- a/ext/standard/math.c +++ b/ext/standard/math.c @@ -667,7 +667,7 @@ PHP_FUNCTION(exp) Z_PARAM_DOUBLE(num) ZEND_PARSE_PARAMETERS_END(); - RETURN_DOUBLE(exp(num)); + RETURN_DOUBLE(zend_exp(num)); } /* }}} */ diff --git a/ext/standard/tests/math/exp_fpu_precision.phpt b/ext/standard/tests/math/exp_fpu_precision.phpt new file mode 100644 index 000000000000..83ac2da96f03 --- /dev/null +++ b/ext/standard/tests/math/exp_fpu_precision.phpt @@ -0,0 +1,48 @@ +--TEST-- +exp(): results must not lose precision when the FPU is clamped to double precision +--INI-- +serialize_precision=-1 +--FILE-- + +--EXPECT-- +-- runtime calls -- +float(20.085536923187668) +float(148.4131591025766) +float(403.4287934927351) +float(1096.6331584284585) +float(2980.9579870417283) +float(8103.083927575384) +float(22026.465794806718) +float(162754.79141900392) +float(3269017.3724721107) +float(485165195.4097903) +-- constant argument gives the same result -- +bool(true) +bool(true)