From 81db22f693d06468d45571a29fc0648a8f5664ce Mon Sep 17 00:00:00 2001 From: stijn Date: Sun, 17 May 2020 12:29:25 +0200 Subject: [PATCH] py/modmath: Work around msvc float bugs in atan2, fmod and modf. Older implementations deal with infinity/negative zero incorrectly. This commit adds generic fixes that can be enabled by any port that needs them, along with new tests cases. --- ports/windows/mpconfigport.h | 8 ++++++++ py/modmath.c | 27 ++++++++++++++++++++++++++- py/mpconfig.h | 15 +++++++++++++++ tests/float/math_domain.py | 4 ++-- 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/ports/windows/mpconfigport.h b/ports/windows/mpconfigport.h index 84c196e11..fa09dda75 100644 --- a/ports/windows/mpconfigport.h +++ b/ports/windows/mpconfigport.h @@ -227,6 +227,14 @@ extern const struct _mp_obj_module_t mp_module_time; #define MP_SSIZE_MAX _I32_MAX #endif +// VC++ 12.0 fixes +#if (_MSC_VER <= 1800) +#define MICROPY_PY_MATH_ATAN2_FIX_INFNAN (1) +#define MICROPY_PY_MATH_FMOD_FIX_INFNAN (1) +#ifdef _WIN64 +#define MICROPY_PY_MATH_MODF_FIX_NEGZERO (1) +#endif +#endif // CL specific definitions diff --git a/py/modmath.c b/py/modmath.c index 5ff892ba1..b312eeb3d 100644 --- a/py/modmath.c +++ b/py/modmath.c @@ -34,6 +34,8 @@ // M_PI is not part of the math.h standard and may not be defined // And by defining our own we can ensure it uses the correct const format. #define MP_PI MICROPY_FLOAT_CONST(3.14159265358979323846) +#define MP_PI_4 MICROPY_FLOAT_CONST(0.78539816339744830962) +#define MP_3_PI_4 MICROPY_FLOAT_CONST(2.35619449019234492885) STATIC NORETURN void math_error(void) { mp_raise_ValueError(MP_ERROR_TEXT("math domain error")); @@ -132,7 +134,17 @@ MATH_FUN_1(asin, asin) // atan(x) MATH_FUN_1(atan, atan) // atan2(y, x) +#if MICROPY_PY_MATH_ATAN2_FIX_INFNAN +mp_float_t atan2_func(mp_float_t x, mp_float_t y) { + if (isinf(x) && isinf(y)) { + return copysign(y < 0 ? MP_3_PI_4 : MP_PI_4, x); + } + return atan2(x, y); +} +MATH_FUN_2(atan2, atan2_func) +#else MATH_FUN_2(atan2, atan2) +#endif // ceil(x) MATH_FUN_1_TO_INT(ceil, ceil) // copysign(x, y) @@ -148,7 +160,14 @@ MATH_FUN_1(fabs, fabs_func) // floor(x) MATH_FUN_1_TO_INT(floor, floor) // TODO: delegate to x.__floor__() if x is not a float // fmod(x, y) +#if MICROPY_PY_MATH_FMOD_FIX_INFNAN +mp_float_t fmod_func(mp_float_t x, mp_float_t y) { + return (!isinf(x) && isinf(y)) ? x : fmod(x, y); +} +MATH_FUN_2(fmod, fmod_func) +#else MATH_FUN_2(fmod, fmod) +#endif // isfinite(x) MATH_FUN_1_TO_BOOL(isfinite, isfinite) // isinf(x) @@ -246,7 +265,13 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_math_frexp_obj, mp_math_frexp); // modf(x) STATIC mp_obj_t mp_math_modf(mp_obj_t x_obj) { mp_float_t int_part = 0.0; - mp_float_t fractional_part = MICROPY_FLOAT_C_FUN(modf)(mp_obj_get_float(x_obj), &int_part); + mp_float_t x = mp_obj_get_float(x_obj); + mp_float_t fractional_part = MICROPY_FLOAT_C_FUN(modf)(x, &int_part); + #if MICROPY_PY_MATH_MODF_FIX_NEGZERO + if (fractional_part == MICROPY_FLOAT_CONST(0.0)) { + fractional_part = copysign(fractional_part, x); + } + #endif mp_obj_t tuple[2]; tuple[0] = mp_obj_new_float(fractional_part); tuple[1] = mp_obj_new_float(int_part); diff --git a/py/mpconfig.h b/py/mpconfig.h index f2b3af1f2..27df3f483 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1128,6 +1128,21 @@ typedef double mp_float_t; #define MICROPY_PY_MATH_ISCLOSE (0) #endif +// Whether to provide fix for atan2 Inf handling. +#ifndef MICROPY_PY_MATH_ATAN2_FIX_INFNAN +#define MICROPY_PY_MATH_ATAN2_FIX_INFNAN (0) +#endif + +// Whether to provide fix for fmod Inf handling. +#ifndef MICROPY_PY_MATH_FMOD_FIX_INFNAN +#define MICROPY_PY_MATH_FMOD_FIX_INFNAN (0) +#endif + +// Whether to provide fix for modf negative zero handling. +#ifndef MICROPY_PY_MATH_MODF_FIX_NEGZERO +#define MICROPY_PY_MATH_MODF_FIX_NEGZERO (0) +#endif + // Whether to provide "cmath" module #ifndef MICROPY_PY_CMATH #define MICROPY_PY_CMATH (0) diff --git a/tests/float/math_domain.py b/tests/float/math_domain.py index 2d4670f75..e63628cf5 100644 --- a/tests/float/math_domain.py +++ b/tests/float/math_domain.py @@ -39,8 +39,8 @@ for name, f, args in ( # double argument functions for name, f, args in ( ("pow", math.pow, ((0, 2), (-1, 2), (0, -1), (-1, 2.3))), - ("fmod", math.fmod, ((1.2, inf), (1.2, 0), (inf, 1.2))), - ("atan2", math.atan2, ((0, 0),)), + ("fmod", math.fmod, ((1.2, inf), (1.2, -inf), (1.2, 0), (inf, 1.2))), + ("atan2", math.atan2, ((0, 0), (-inf, inf), (-inf, -inf), (inf, -inf))), ("copysign", math.copysign, ()), ): for x in args + ((0, inf), (inf, 0), (inf, inf), (inf, nan), (nan, inf), (nan, nan)):