From 04c528609a1849d19bed744f9f2bfcc0b7eab0ae Mon Sep 17 00:00:00 2001 From: Christian Seiler Date: Tue, 2 Dec 2008 16:19:10 +0000 Subject: [PATCH] - MFH: Changed floating point behaviour to consistently use double precision on all platforms and with all compilers. --- Zend/Zend.m4 | 2 + Zend/acinclude.m4 | 169 ++++++++++++++++ Zend/tests/float_prec_001.phpt | 10 + Zend/zend_float.h | 355 +++++++++++++++++++++++++++++++++ Zend/zend_operators.c | 39 ++++ Zend/zend_strtod.c | 6 +- 6 files changed, 580 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/float_prec_001.phpt create mode 100644 Zend/zend_float.h diff --git a/Zend/Zend.m4 b/Zend/Zend.m4 index 6de6b0cf13a..904f1886816 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 1c6ebd9baaa..d507f1780ee 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 00000000000..ffddab473fd --- /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) +string(10) "0.00287699" diff --git a/Zend/zend_float.h b/Zend/zend_float.h new file mode 100644 index 00000000000..ee37328c643 --- /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 b050a8d94d3..db60de52295 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" #define LONG_SIGN_MASK (1L << (8*sizeof(long)-1)) @@ -775,6 +776,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; @@ -787,7 +789,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); } @@ -795,15 +799,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): { @@ -837,6 +847,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; @@ -849,7 +860,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); } @@ -857,15 +870,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: @@ -884,6 +903,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; @@ -892,21 +912,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: @@ -924,6 +952,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; @@ -936,13 +965,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; @@ -952,7 +985,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): @@ -961,7 +996,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): @@ -970,7 +1007,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 bc2ca97b19b..4a3e976d259 100644 --- a/Zend/zend_strtod.c +++ b/Zend/zend_strtod.c @@ -93,6 +93,7 @@ #include #include +#include #ifdef ZTS #include @@ -2032,6 +2033,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; @@ -2044,6 +2046,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.; @@ -2574,7 +2578,7 @@ ret: } _THREAD_PRIVATE_MUTEX_UNLOCK(pow5mult_mutex); - return result; + ZEND_FLOAT_RETURN(result); } ZEND_API double zend_hex_strtod(const char *str, char **endptr)