Skip to content
Open
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
20 changes: 20 additions & 0 deletions Zend/zend_float.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

#include "zend_portability.h"

#include <math.h>

BEGIN_EXTERN_C()

/*
Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion ext/standard/math.c
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ PHP_FUNCTION(exp)
Z_PARAM_DOUBLE(num)
ZEND_PARSE_PARAMETERS_END();

RETURN_DOUBLE(exp(num));
RETURN_DOUBLE(zend_exp(num));
}
/* }}} */

Expand Down
48 changes: 48 additions & 0 deletions ext/standard/tests/math/exp_fpu_precision.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
--TEST--
exp(): results must not lose precision when the FPU is clamped to double precision
--INI--
serialize_precision=-1
--FILE--
<?php
/* On i386 the x87 FPU is the platform default and zend_init_fpu() clamps it 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.
*
* Each expected value below is the correctly rounded double, printed as the
* shortest decimal that round-trips back to it. */

$x = 3;

echo "-- runtime calls --\n";
var_dump(exp($x));
var_dump(exp($x + 2));
var_dump(exp($x + 3));
var_dump(exp($x + 4));
var_dump(exp($x + 5));
var_dump(exp($x + 6));
var_dump(exp($x + 7));
var_dump(exp($x + 9));
var_dump(exp($x + 12));
var_dump(exp($x + 17));

echo "-- constant argument gives the same result --\n";
var_dump(exp(10) === exp($x + 7));
var_dump(exp(20) === exp($x + 17));
?>
--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)
Loading