From: Christian Seiler Date: Tue, 2 Dec 2008 16:03:30 +0000 (+0000) Subject: - Changed floating point behaviour to consistently use double precision on X-Git-Tag: BEFORE_HEAD_NS_CHANGES_MERGE~24 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=2230e803d28f6f392d7bc7cf484fdab597404866;p=php - Changed floating point behaviour to consistently use double precision on all platforms and with all compilers. --- diff --git a/Zend/Zend.m4 b/Zend/Zend.m4 index bbb3411a4a..df5436b992 100644 --- a/Zend/Zend.m4 +++ b/Zend/Zend.m4 @@ -115,6 +115,8 @@ AC_ZEND_BROKEN_SPRINTF AC_CHECK_FUNCS(finite isfinite isinf isnan) ZEND_FP_EXCEPT + +ZEND_CHECK_FLOAT_PRECISION ]) diff --git a/Zend/acinclude.m4 b/Zend/acinclude.m4 index 1c6ebd9baa..1dcc57c3c6 100644 --- a/Zend/acinclude.m4 +++ b/Zend/acinclude.m4 @@ -105,3 +105,172 @@ int main(void) AC_DEFUN([AM_SET_LIBTOOL_VARIABLE],[ LIBTOOL='$(SHELL) $(top_builddir)/libtool $1' ]) + +dnl x87 floating point internal precision control checks +dnl See: http://wiki.php.net/rfc/rounding +AC_DEFUN([ZEND_CHECK_FLOAT_PRECISION],[ + AC_MSG_CHECKING([for usable _FPU_SETCW]) + AC_LINK_IFELSE([[ + #include + #include + #include + + double div (double a, double b) { + fpu_control_t fpu_oldcw, fpu_cw; + volatile double result; + + _FPU_GETCW(fpu_oldcw); + fpu_cw = (fpu_oldcw & ~_FPU_EXTENDED & ~_FPU_SINGLE) | _FPU_DOUBLE; + _FPU_SETCW(fpu_cw); + result = a / b; + _FPU_SETCW(fpu_oldcw); + return result; + } + + int main (int argc, char **argv) { + double d = div (2877.0, 1000000.0); + char buf[255]; + sprintf(buf, "%.30f", d); + // see if the result is actually in double precision + return strncmp(buf, "0.00287699", 10) == 0 ? 0 : 1; + } + ]], [ac_cfp_have__fpu_setcw=yes], [ac_cfp_have__fpu_setcw=no]) + if test "$ac_cfp_have__fpu_setcw" = "yes" ; then + AC_DEFINE(HAVE__FPU_SETCW, 1, [whether _FPU_SETCW is present and usable]) + AC_MSG_RESULT(yes) + else + AC_MSG_RESULT(no) + fi + + AC_MSG_CHECKING([for usable fpsetprec]) + AC_LINK_IFELSE([[ + #include + #include + #include + + double div (double a, double b) { + fp_prec_t fpu_oldprec; + volatile double result; + + fpu_oldprec = fpgetprec(); + fpsetprec(FP_PD); + result = a / b; + fpsetprec(fpu_oldprec); + return result; + } + + int main (int argc, char **argv) { + double d = div (2877.0, 1000000.0); + char buf[255]; + sprintf(buf, "%.30f", d); + // see if the result is actually in double precision + return strncmp(buf, "0.00287699", 10) == 0 ? 0 : 1; + } + ]], [ac_cfp_have_fpsetprec=yes], [ac_cfp_have_fpsetprec=no]) + if test "$ac_cfp_have_fpsetprec" = "yes" ; then + AC_DEFINE(HAVE_FPSETPREC, 1, [whether fpsetprec is present and usable]) + AC_MSG_RESULT(yes) + else + AC_MSG_RESULT(no) + fi + + AC_MSG_CHECKING([for usable _controlfp]) + AC_LINK_IFELSE([[ + #include + #include + #include + + double div (double a, double b) { + unsigned int fpu_oldcw; + volatile double result; + + fpu_oldcw = _controlfp(0, 0); + _controlfp(_PC_53, _MCW_PC); + result = a / b; + _controlfp(fpu_oldcw, _MCW_PC); + return result; + } + + int main (int argc, char **argv) { + double d = div (2877.0, 1000000.0); + char buf[255]; + sprintf(buf, "%.30f", d); + // see if the result is actually in double precision + return strncmp(buf, "0.00287699", 10) == 0 ? 0 : 1; + } + ]], [ac_cfp_have__controlfp=yes], [ac_cfp_have__controlfp=no]) + if test "$ac_cfp_have__controlfp" = "yes" ; then + AC_DEFINE(HAVE__CONTROLFP, 1, [whether _controlfp is present usable]) + AC_MSG_RESULT(yes) + else + AC_MSG_RESULT(no) + fi + + AC_MSG_CHECKING([for usable _controlfp_s]) + AC_LINK_IFELSE([[ + #include + #include + #include + + double div (double a, double b) { + unsigned int fpu_oldcw, fpu_cw; + volatile double result; + + _controlfp_s(&fpu_cw, 0, 0); + fpu_oldcw = fpu_cw; + _controlfp_s(&fpu_cw, _PC_53, _MCW_PC); + result = a / b; + _controlfp_s(&fpu_cw, fpu_oldcw, _MCW_PC); + return result; + } + + int main (int argc, char **argv) { + double d = div (2877.0, 1000000.0); + char buf[255]; + sprintf(buf, "%.30f", d); + // see if the result is actually in double precision + return strncmp(buf, "0.00287699", 10) == 0 ? 0 : 1; + } + ]], [ac_cfp_have__controlfp_s=yes], [ac_cfp_have__controlfp_s=no]) + if test "$ac_cfp_have__controlfp_s" = "yes" ; then + AC_DEFINE(HAVE__CONTROLFP_S, 1, [whether _controlfp_s is present and usable]) + AC_MSG_RESULT(yes) + else + AC_MSG_RESULT(no) + fi + + AC_MSG_CHECKING([whether FPU control word can be manipulated by inline assembler]) + AC_LINK_IFELSE([[ + #include + #include + + double div (double a, double b) { + unsigned int oldcw, cw; + volatile double result; + + __asm__ __volatile__ ("fnstcw %0" : "=m" (*&oldcw)); + cw = (oldcw & ~0x0 & ~0x300) | 0x200; + __asm__ __volatile__ ("fldcw %0" : : "m" (*&cw)); + + result = a / b; + + __asm__ __volatile__ ("fldcw %0" : : "m" (*&oldcw)); + + return result; + } + + int main (int argc, char **argv) { + double d = div (2877.0, 1000000.0); + char buf[255]; + sprintf(buf, "%.30f", d); + // see if the result is actually in double precision + return strncmp(buf, "0.00287699", 10) == 0 ? 0 : 1; + } + ]], [ac_cfp_have_fpu_inline_asm_x86=yes], [ac_cfp_have_fpu_inline_asm_x86=no]) + if test "$ac_cfp_have_fpu_inline_asm_x86" = "yes" ; then + AC_DEFINE(HAVE_FPU_INLINE_ASM_X86, 1, [whether FPU control word can be manipulated by inline assembler]) + AC_MSG_RESULT(yes) + else + AC_MSG_RESULT(no) + fi +]) diff --git a/Zend/tests/float_prec_001.phpt b/Zend/tests/float_prec_001.phpt new file mode 100644 index 0000000000..d412c32efc --- /dev/null +++ b/Zend/tests/float_prec_001.phpt @@ -0,0 +1,10 @@ +--TEST-- +Double precision is used for floating point calculations +--FILE-- + +--EXPECT-- +bool(true) +unicode(10) "0.00287699" diff --git a/Zend/zend_float.h b/Zend/zend_float.h new file mode 100644 index 0000000000..833fc22769 --- /dev/null +++ b/Zend/zend_float.h @@ -0,0 +1,355 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2007 Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Christian Seiler | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef ZEND_FLOAT_H +#define ZEND_FLOAT_H + +#define ZEND_FLOAT_DECLARE XPFPA_DECLARE +#define ZEND_FLOAT_ENSURE() XPFPA_SWITCH_DOUBLE() +#define ZEND_FLOAT_RESTORE() XPFPA_RESTORE() +#define ZEND_FLOAT_RETURN(val) XPFPA_RETURN_DOUBLE(val) + +/* Copy of the contents of xpfpa.h (which is under public domain) + See http://wiki.php.net/rfc/rounding for details. + + Cross Platform Floating Point Arithmetics + + This header file defines several platform-dependent macros that ensure + equal and deterministic floating point behaviour across several platforms, + compilers and architectures. + + The current macros are currently only used on x86 and x86_64 architectures, + on every other architecture, these macros expand to NOPs. This assumes that + other architectures do not have an internal precision and the operhand types + define the computational precision of floating point operations. This + assumption may be false, in that case, the author is interested in further + details on the other platform. + + For further details, please visit: + http://www.christian-seiler.de/projekte/fpmath/ + + Version: 20081026 */ + +/* + Implementation notes: + + x86_64: + - Since all x86_64 compilers use SSE by default, it is probably unecessary + to use these macros there. We define them anyway since we are too lazy + to differentiate the architecture. Also, the compiler option -mfpmath=i387 + justifies this decision. + + General: + - It would be nice if one could detect whether SSE if used for math via some + funky compiler defines and if so, make the macros go to NOPs. Any ideas + on how to do that? + + MS Visual C: + - Since MSVC users tipically don't use autoconf or CMake, we will detect + MSVC via compile time define. +*/ + +// MSVC detection (MSVC people usually don't use autoconf) +#ifdef _MSC_VER +# if _MSC_VER >= 1500 + // Visual C++ 2008 or higher, supports _controlfp_s +# define HAVE__CONTROLFP_S +# else + // Visual C++ (up to 2005), supports _controlfp +# define HAVE__CONTROLFP +# endif // MSC_VER >= 1500 + // Tell MSVC optimizer that we access FP environment +# pragma fenv_access (on) +#endif // _MSC_VER + +#ifdef HAVE__CONTROLFP_S + +// float.h defines _controlfp_s +# include + +# define XPFPA_DECLARE \ + unsigned int _xpfpa_fpu_oldcw, _xpfpa_fpu_cw; + +# define XPFPA_SWITCH_DOUBLE() do { \ + _controlfp_s(&_xpfpa_fpu_cw, 0, 0); \ + _xpfpa_fpu_oldcw = _xpfpa_fpu_cw; \ + _controlfp_s(&_xpfpa_fpu_cw, _PC_53, _MCW_PC); \ + } while (0) +# define XPFPA_SWITCH_SINGLE() do { \ + _controlfp_s(&_xpfpa_fpu_cw, 0, 0); \ + _xpfpa_fpu_oldcw = _xpfpa_fpu_cw; \ + _controlfp_s(&_xpfpa_fpu_cw, _PC_24, _MCW_PC); \ + } while (0) +// NOTE: This only sets internal precision. MSVC does NOT support double- +// extended precision! +# define XPFPA_SWITCH_DOUBLE_EXTENDED() do { \ + _controlfp_s(&_xpfpa_fpu_cw, 0, 0); \ + _xpfpa_fpu_oldcw = _xpfpa_fpu_cw; \ + _controlfp_s(&_xpfpa_fpu_cw, _PC_64, _MCW_PC); \ + } while (0) +# define XPFPA_RESTORE() \ + _controlfp_s(&_xpfpa_fpu_cw, _xpfpa_fpu_oldcw, _MCW_PC) +// We do NOT use the volatile return trick since _controlfp_s is a function +// call and thus FP registers are saved in memory anyway. However, we do use +// a variable to ensure that the expression passed into val will be evaluated +// *before* switching back contexts. +# define XPFPA_RETURN_DOUBLE(val) \ + do { \ + double _xpfpa_result = (val); \ + XPFPA_RESTORE(); \ + return _xpfpa_result; \ + } while (0) +# define XPFPA_RETURN_SINGLE(val) \ + do { \ + float _xpfpa_result = (val); \ + XPFPA_RESTORE(); \ + return _xpfpa_result; \ + } while (0) +// This won't work, but we add a macro for it anyway. +# define XPFPA_RETURN_DOUBLE_EXTENDED(val) \ + do { \ + long double _xpfpa_result = (val); \ + XPFPA_RESTORE(); \ + return _xpfpa_result; \ + } while (0) + +#elif defined(HAVE__CONTROLFP) + +// float.h defines _controlfp +# include + +# define XPFPA_DECLARE \ + unsigned int _xpfpa_fpu_oldcw; + +# define XPFPA_SWITCH_DOUBLE() do { \ + _xpfpa_fpu_oldcw = _controlfp(0, 0); \ + _controlfp(_PC_53, _MCW_PC); \ + } while (0) +# define XPFPA_SWITCH_SINGLE() do { \ + _xpfpa_fpu_oldcw = _controlfp(0, 0); \ + _controlfp(_PC_24, _MCW_PC); \ + } while (0) +// NOTE: This will only work as expected on MinGW. +# define XPFPA_SWITCH_DOUBLE_EXTENDED() do { \ + _xpfpa_fpu_oldcw = _controlfp(0, 0); \ + _controlfp(_PC_64, _MCW_PC); \ + } while (0) +# define XPFPA_RESTORE() \ + _controlfp(_xpfpa_fpu_oldcw, _MCW_PC) +// We do NOT use the volatile return trick since _controlfp is a function +// call and thus FP registers are saved in memory anyway. However, we do use +// a variable to ensure that the expression passed into val will be evaluated +// *before* switching back contexts. +# define XPFPA_RETURN_DOUBLE(val) \ + do { \ + double _xpfpa_result = (val); \ + XPFPA_RESTORE(); \ + return _xpfpa_result; \ + } while (0) +# define XPFPA_RETURN_SINGLE(val) \ + do { \ + float _xpfpa_result = (val); \ + XPFPA_RESTORE(); \ + return _xpfpa_result; \ + } while (0) +// This will only work on MinGW +# define XPFPA_RETURN_DOUBLE_EXTENDED(val) \ + do { \ + long double _xpfpa_result = (val); \ + XPFPA_RESTORE(); \ + return _xpfpa_result; \ + } while (0) + +#elif defined(HAVE__FPU_SETCW) // glibc systems + +// fpu_control.h defines _FPU_[GS]ETCW +# include + +# define XPFPA_DECLARE \ + fpu_control_t _xpfpa_fpu_oldcw, _xpfpa_fpu_cw; + +# define XPFPA_SWITCH_DOUBLE() do { \ + _FPU_GETCW(_xpfpa_fpu_oldcw); \ + _xpfpa_fpu_cw = (_xpfpa_fpu_oldcw & ~_FPU_EXTENDED & ~_FPU_SINGLE) | _FPU_DOUBLE; \ + _FPU_SETCW(_xpfpa_fpu_cw); \ + } while (0) +# define XPFPA_SWITCH_SINGLE() do { \ + _FPU_GETCW(_xpfpa_fpu_oldcw); \ + _xpfpa_fpu_cw = (_xpfpa_fpu_oldcw & ~_FPU_EXTENDED & ~_FPU_DOUBLE) | _FPU_SINGLE; \ + _FPU_SETCW(_xpfpa_fpu_cw); \ + } while (0) +# define XPFPA_SWITCH_DOUBLE_EXTENDED() do { \ + _FPU_GETCW(_xpfpa_fpu_oldcw); \ + _xpfpa_fpu_cw = (_xpfpa_fpu_oldcw & ~_FPU_SINGLE & ~_FPU_DOUBLE) | _FPU_EXTENDED; \ + _FPU_SETCW(_xpfpa_fpu_cw); \ + } while (0) +# define XPFPA_RESTORE() \ + _FPU_SETCW(_xpfpa_fpu_oldcw) +// We use a temporary volatile variable (in a new block) in order to ensure +// that the optimizer does not mis-optimize the instructions. Also, a volatile +// variable ensures truncation to correct precision. +# define XPFPA_RETURN_DOUBLE(val) \ + do { \ + volatile double _xpfpa_result = (val); \ + XPFPA_RESTORE(); \ + return _xpfpa_result; \ + } while (0) +# define XPFPA_RETURN_SINGLE(val) \ + do { \ + volatile float _xpfpa_result = (val); \ + XPFPA_RESTORE(); \ + return _xpfpa_result; \ + } while (0) +# define XPFPA_RETURN_DOUBLE_EXTENDED(val) \ + do { \ + volatile long double _xpfpa_result = (val); \ + XPFPA_RESTORE(); \ + return _xpfpa_result; \ + } while (0) + +#elif defined(HAVE_FPSETPREC) // FreeBSD + +// fpu_control.h defines _FPU_[GS]ETCW +# include + +# define XPFPA_DECLARE \ + fp_prec_t _xpfpa_fpu_oldprec; + +# define XPFPA_SWITCH_DOUBLE() do { \ + _xpfpa_fpu_oldprec = fpgetprec(); \ + fpsetprec(FP_PD); \ + } while (0) +# define XPFPA_SWITCH_SINGLE() do { \ + _xpfpa_fpu_oldprec = fpgetprec(); \ + fpsetprec(FP_PS); \ + } while (0) +# define XPFPA_SWITCH_DOUBLE_EXTENDED() do { \ + _xpfpa_fpu_oldprec = fpgetprec(); \ + fpsetprec(FP_PE); \ + } while (0) +# define XPFPA_RESTORE() \ + fpsetprec(_xpfpa_fpu_oldprec) +// We use a temporary volatile variable (in a new block) in order to ensure +// that the optimizer does not mis-optimize the instructions. Also, a volatile +// variable ensures truncation to correct precision. +# define XPFPA_RETURN_DOUBLE(val) \ + do { \ + volatile double _xpfpa_result = (val); \ + XPFPA_RESTORE(); \ + return _xpfpa_result; \ + } while (0) +# define XPFPA_RETURN_SINGLE(val) \ + do { \ + volatile float _xpfpa_result = (val); \ + XPFPA_RESTORE(); \ + return _xpfpa_result; \ + } while (0) +# define XPFPA_RETURN_DOUBLE_EXTENDED(val) \ + do { \ + volatile long double _xpfpa_result = (val); \ + XPFPA_RESTORE(); \ + return _xpfpa_result; \ + } while (0) + +#elif defined(HAVE_FPU_INLINE_ASM_X86) + +/* + Custom x86 inline assembler implementation. + + This implementation does not use predefined wrappers of the OS / compiler + but rather uses x86/x87 inline assembler directly. Basic instructions: + + fnstcw - Store the FPU control word in a variable + fldcw - Load the FPU control word from a variable + + Bits (only bits 8 and 9 are relevant, bits 0 to 7 are for other things): + 0x0yy: Single precision + 0x1yy: Reserved + 0x2yy: Double precision + 0x3yy: Double-extended precision + + We use an unsigned int for the datatype. glibc sources add __mode__ (__HI__) + attribute to it (HI stands for half-integer according to docs). It is unclear + what the does exactly and how portable it is. + + The assembly syntax works with GNU CC, Intel CC and Sun CC. +*/ + +# define XPFPA_DECLARE \ + unsigned int _xpfpa_fpu_oldcw, _xpfpa_fpu_cw; + +# define XPFPA_SWITCH_DOUBLE() do { \ + __asm__ __volatile__ ("fnstcw %0" : "=m" (*&_xpfpa_fpu_oldcw)); \ + _xpfpa_fpu_cw = (_xpfpa_fpu_oldcw & ~0x100) | 0x200; \ + __asm__ __volatile__ ("fldcw %0" : : "m" (*&_xpfpa_fpu_cw)); \ + } while (0) +# define XPFPA_SWITCH_SINGLE() do { \ + __asm__ __volatile__ ("fnstcw %0" : "=m" (*&_xpfpa_fpu_oldcw)); \ + _xpfpa_fpu_cw = (_xpfpa_fpu_oldcw & ~0x300); \ + __asm__ __volatile__ ("fldcw %0" : : "m" (*&_xpfpa_fpu_cw)); \ + } while (0) +# define XPFPA_SWITCH_DOUBLE_EXTENDED() do { \ + __asm__ __volatile__ ("fnstcw %0" : "=m" (*&_xpfpa_fpu_oldcw)); \ + _xpfpa_fpu_cw = _xpfpa_fpu_oldcw | 0x300; \ + __asm__ __volatile__ ("fldcw %0" : : "m" (*&_xpfpa_fpu_cw)); \ + } while (0) +# define XPFPA_RESTORE() \ + __asm__ __volatile__ ("fldcw %0" : : "m" (*&_xpfpa_fpu_oldcw)) +// We use a temporary volatile variable (in a new block) in order to ensure +// that the optimizer does not mis-optimize the instructions. Also, a volatile +// variable ensures truncation to correct precision. +# define XPFPA_RETURN_DOUBLE(val) \ + do { \ + volatile double _xpfpa_result = (val); \ + XPFPA_RESTORE(); \ + return _xpfpa_result; \ + } while (0) +# define XPFPA_RETURN_SINGLE(val) \ + do { \ + volatile float _xpfpa_result = (val); \ + XPFPA_RESTORE(); \ + return _xpfpa_result; \ + } while (0) +# define XPFPA_RETURN_DOUBLE_EXTENDED(val) \ + do { \ + volatile long double _xpfpa_result = (val); \ + XPFPA_RESTORE(); \ + return _xpfpa_result; \ + } while (0) + +#else // FPU CONTROL + +/* + This is either not an x87 FPU or the inline assembly syntax was not + recognized. In any case, default to NOPs for the macros and hope the + generated code will behave as planned. +*/ +# define XPFPA_DECLARE /* NOP */ +# define XPFPA_SWITCH_DOUBLE() /* NOP */ +# define XPFPA_SWITCH_SINGLE() /* NOP */ +# define XPFPA_SWITCH_DOUBLE_EXTENDED() /* NOP */ +# define XPFPA_RESTORE() /* NOP */ +# define XPFPA_RETURN_DOUBLE(val) return (val) +# define XPFPA_RETURN_SINGLE(val) return (val) +# define XPFPA_RETURN_DOUBLE_EXTENDED(val) return (val) + +#endif // FPU CONTROL + +#endif diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index bd0c84671a..06985eda66 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -30,6 +30,7 @@ #include "zend_multiply.h" #include "zend_strtod.h" #include "zend_exceptions.h" +#include "zend_float.h" #include "unicode/uchar.h" #include "unicode/ucol.h" @@ -1294,6 +1295,7 @@ ZEND_API void multi_convert_to_string_ex(int argc, ...) /* {{{ */ ZEND_API int add_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ */ { + ZEND_FLOAT_DECLARE zval op1_copy, op2_copy; int converted = 0; @@ -1306,7 +1308,9 @@ ZEND_API int add_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ * if ((Z_LVAL_P(op1) & LONG_SIGN_MASK) == (Z_LVAL_P(op2) & LONG_SIGN_MASK) && (Z_LVAL_P(op1) & LONG_SIGN_MASK) != (lval & LONG_SIGN_MASK)) { + ZEND_FLOAT_ENSURE(); ZVAL_DOUBLE(result, (double) Z_LVAL_P(op1) + (double) Z_LVAL_P(op2)); + ZEND_FLOAT_RESTORE(); } else { ZVAL_LONG(result, lval); } @@ -1314,15 +1318,21 @@ ZEND_API int add_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ * } case TYPE_PAIR(IS_LONG, IS_DOUBLE): + ZEND_FLOAT_ENSURE(); ZVAL_DOUBLE(result, ((double)Z_LVAL_P(op1)) + Z_DVAL_P(op2)); + ZEND_FLOAT_RESTORE(); return SUCCESS; case TYPE_PAIR(IS_DOUBLE, IS_LONG): + ZEND_FLOAT_ENSURE(); ZVAL_DOUBLE(result, Z_DVAL_P(op1) + ((double)Z_LVAL_P(op2))); + ZEND_FLOAT_RESTORE(); return SUCCESS; case TYPE_PAIR(IS_DOUBLE, IS_DOUBLE): + ZEND_FLOAT_ENSURE(); ZVAL_DOUBLE(result, Z_DVAL_P(op1) + Z_DVAL_P(op2)); + ZEND_FLOAT_RESTORE(); return SUCCESS; case TYPE_PAIR(IS_ARRAY, IS_ARRAY): { @@ -1356,6 +1366,7 @@ ZEND_API int add_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ * ZEND_API int sub_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ */ { + ZEND_FLOAT_DECLARE zval op1_copy, op2_copy; int converted = 0; @@ -1368,7 +1379,9 @@ ZEND_API int sub_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ * if ((Z_LVAL_P(op1) & LONG_SIGN_MASK) != (Z_LVAL_P(op2) & LONG_SIGN_MASK) && (Z_LVAL_P(op1) & LONG_SIGN_MASK) != (lval & LONG_SIGN_MASK)) { + ZEND_FLOAT_ENSURE(); ZVAL_DOUBLE(result, (double) Z_LVAL_P(op1) - (double) Z_LVAL_P(op2)); + ZEND_FLOAT_RESTORE(); } else { ZVAL_LONG(result, lval); } @@ -1376,15 +1389,21 @@ ZEND_API int sub_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ * } case TYPE_PAIR(IS_LONG, IS_DOUBLE): + ZEND_FLOAT_ENSURE(); ZVAL_DOUBLE(result, ((double)Z_LVAL_P(op1)) - Z_DVAL_P(op2)); + ZEND_FLOAT_RESTORE(); return SUCCESS; case TYPE_PAIR(IS_DOUBLE, IS_LONG): + ZEND_FLOAT_ENSURE(); ZVAL_DOUBLE(result, Z_DVAL_P(op1) - ((double)Z_LVAL_P(op2))); + ZEND_FLOAT_RESTORE(); return SUCCESS; case TYPE_PAIR(IS_DOUBLE, IS_DOUBLE): + ZEND_FLOAT_ENSURE(); ZVAL_DOUBLE(result, Z_DVAL_P(op1) - Z_DVAL_P(op2)); + ZEND_FLOAT_RESTORE(); return SUCCESS; default: @@ -1403,6 +1422,7 @@ ZEND_API int sub_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ * ZEND_API int mul_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ */ { + ZEND_FLOAT_DECLARE zval op1_copy, op2_copy; int converted = 0; @@ -1411,21 +1431,29 @@ ZEND_API int mul_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ * case TYPE_PAIR(IS_LONG, IS_LONG): { long overflow; + ZEND_FLOAT_ENSURE(); ZEND_SIGNED_MULTIPLY_LONG(Z_LVAL_P(op1),Z_LVAL_P(op2), Z_LVAL_P(result),Z_DVAL_P(result),overflow); + ZEND_FLOAT_RESTORE(); Z_TYPE_P(result) = overflow ? IS_DOUBLE : IS_LONG; return SUCCESS; } case TYPE_PAIR(IS_LONG, IS_DOUBLE): + ZEND_FLOAT_ENSURE(); ZVAL_DOUBLE(result, ((double)Z_LVAL_P(op1)) * Z_DVAL_P(op2)); + ZEND_FLOAT_RESTORE(); return SUCCESS; case TYPE_PAIR(IS_DOUBLE, IS_LONG): + ZEND_FLOAT_ENSURE(); ZVAL_DOUBLE(result, Z_DVAL_P(op1) * ((double)Z_LVAL_P(op2))); + ZEND_FLOAT_RESTORE(); return SUCCESS; case TYPE_PAIR(IS_DOUBLE, IS_DOUBLE): + ZEND_FLOAT_ENSURE(); ZVAL_DOUBLE(result, Z_DVAL_P(op1) * Z_DVAL_P(op2)); + ZEND_FLOAT_RESTORE(); return SUCCESS; default: @@ -1444,6 +1472,7 @@ ZEND_API int mul_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ * ZEND_API int div_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ */ { + ZEND_FLOAT_DECLARE zval op1_copy, op2_copy; int converted = 0; @@ -1456,13 +1485,17 @@ ZEND_API int div_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ * return FAILURE; /* division by zero */ } else if (Z_LVAL_P(op2) == -1 && Z_LVAL_P(op1) == LONG_MIN) { /* Prevent overflow error/crash */ + ZEND_FLOAT_ENSURE(); ZVAL_DOUBLE(result, (double) LONG_MIN / -1); + ZEND_FLOAT_RESTORE(); return SUCCESS; } if (Z_LVAL_P(op1) % Z_LVAL_P(op2) == 0) { /* integer */ ZVAL_LONG(result, Z_LVAL_P(op1) / Z_LVAL_P(op2)); } else { + ZEND_FLOAT_ENSURE(); ZVAL_DOUBLE(result, ((double) Z_LVAL_P(op1)) / Z_LVAL_P(op2)); + ZEND_FLOAT_RESTORE(); } return SUCCESS; @@ -1472,7 +1505,9 @@ ZEND_API int div_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ * ZVAL_BOOL(result, 0); return FAILURE; /* division by zero */ } + ZEND_FLOAT_ENSURE(); ZVAL_DOUBLE(result, Z_DVAL_P(op1) / (double)Z_LVAL_P(op2)); + ZEND_FLOAT_RESTORE(); return SUCCESS; case TYPE_PAIR(IS_LONG, IS_DOUBLE): @@ -1481,7 +1516,9 @@ ZEND_API int div_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ * ZVAL_BOOL(result, 0); return FAILURE; /* division by zero */ } + ZEND_FLOAT_ENSURE(); ZVAL_DOUBLE(result, (double)Z_LVAL_P(op1) / Z_DVAL_P(op2)); + ZEND_FLOAT_RESTORE(); return SUCCESS; case TYPE_PAIR(IS_DOUBLE, IS_DOUBLE): @@ -1490,7 +1527,9 @@ ZEND_API int div_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ * ZVAL_BOOL(result, 0); return FAILURE; /* division by zero */ } + ZEND_FLOAT_ENSURE(); ZVAL_DOUBLE(result, Z_DVAL_P(op1) / Z_DVAL_P(op2)); + ZEND_FLOAT_RESTORE(); return SUCCESS; default: diff --git a/Zend/zend_strtod.c b/Zend/zend_strtod.c index 473c6f2bbc..1c319d040c 100644 --- a/Zend/zend_strtod.c +++ b/Zend/zend_strtod.c @@ -95,6 +95,7 @@ #include #include #include +#include #ifdef ZTS #include @@ -2045,6 +2046,7 @@ ret1: ZEND_API double zend_strtod (CONST char *s00, char **se) /* {{{ */ { + ZEND_FLOAT_DECLARE int bb2, bb5, bbe, bd2, bd5, bbbits, bs2, c, dsign, e, e1, esign, i, j, k, nd, nd0, nf, nz, nz0, sign; CONST char *s, *s0, *s1; @@ -2057,6 +2059,8 @@ ZEND_API double zend_strtod (CONST char *s00, char **se) /* {{{ */ CONST char decimal_point = '.'; + ZEND_FLOAT_ENSURE(); + sign = nz0 = nz = 0; value(rv) = 0.; @@ -2587,7 +2591,7 @@ ret: } _THREAD_PRIVATE_MUTEX_UNLOCK(pow5mult_mutex); - return result; + ZEND_FLOAT_RETURN(result); } /* }}} */