diff --git a/lib/libm/fmodf.c b/lib/libm/fmodf.c new file mode 100644 index 0000000000..69a9ad9109 --- /dev/null +++ b/lib/libm/fmodf.c @@ -0,0 +1,70 @@ +/*****************************************************************************/ +/*****************************************************************************/ +// fmodf from musl-0.9.15 +/*****************************************************************************/ +/*****************************************************************************/ + +#include "libm.h" + +float fmodf(float x, float y) +{ + union {float f; uint32_t i;} ux = {x}, uy = {y}; + int ex = ux.i>>23 & 0xff; + int ey = uy.i>>23 & 0xff; + uint32_t sx = ux.i & 0x80000000; + uint32_t i; + uint32_t uxi = ux.i; + + if (uy.i<<1 == 0 || isnan(y) || ex == 0xff) + return (x*y)/(x*y); + if (uxi<<1 <= uy.i<<1) { + if (uxi<<1 == uy.i<<1) + return 0*x; + return x; + } + + /* normalize x and y */ + if (!ex) { + for (i = uxi<<9; i>>31 == 0; ex--, i <<= 1); + uxi <<= -ex + 1; + } else { + uxi &= -1U >> 9; + uxi |= 1U << 23; + } + if (!ey) { + for (i = uy.i<<9; i>>31 == 0; ey--, i <<= 1); + uy.i <<= -ey + 1; + } else { + uy.i &= -1U >> 9; + uy.i |= 1U << 23; + } + + /* x mod y */ + for (; ex > ey; ex--) { + i = uxi - uy.i; + if (i >> 31 == 0) { + if (i == 0) + return 0*x; + uxi = i; + } + uxi <<= 1; + } + i = uxi - uy.i; + if (i >> 31 == 0) { + if (i == 0) + return 0*x; + uxi = i; + } + for (; uxi>>23 == 0; uxi <<= 1, ex--); + + /* scale result up */ + if (ex > 0) { + uxi -= 1U << 23; + uxi |= (uint32_t)ex << 23; + } else { + uxi >>= -ex + 1; + } + uxi |= sx; + ux.i = uxi; + return ux.f; +} diff --git a/lib/libm/math.c b/lib/libm/math.c index 07a563e58c..623bedb3de 100644 --- a/lib/libm/math.c +++ b/lib/libm/math.c @@ -117,7 +117,6 @@ float acoshf(float x) { return 0.0; } float asinhf(float x) { return 0.0; } float atanhf(float x) { return 0.0; } float tanf(float x) { return 0.0; } -float fmodf(float x, float y) { return 0.0; } float tgammaf(float x) { return 0.0; } float lgammaf(float x) { return 0.0; } float erff(float x) { return 0.0; } diff --git a/py/builtin.c b/py/builtin.c index 9810270fed..5b70531aec 100644 --- a/py/builtin.c +++ b/py/builtin.c @@ -242,13 +242,32 @@ STATIC mp_obj_t mp_builtin_dir(mp_uint_t n_args, const mp_obj_t *args) { MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_dir_obj, 0, 1, mp_builtin_dir); STATIC mp_obj_t mp_builtin_divmod(mp_obj_t o1_in, mp_obj_t o2_in) { + // TODO handle big int if (MP_OBJ_IS_SMALL_INT(o1_in) && MP_OBJ_IS_SMALL_INT(o2_in)) { mp_int_t i1 = MP_OBJ_SMALL_INT_VALUE(o1_in); mp_int_t i2 = MP_OBJ_SMALL_INT_VALUE(o2_in); + if (i2 == 0) { + zero_division_error: + nlr_raise(mp_obj_new_exception_msg(&mp_type_ZeroDivisionError, "division by zero")); + } mp_obj_t args[2]; args[0] = MP_OBJ_NEW_SMALL_INT(i1 / i2); args[1] = MP_OBJ_NEW_SMALL_INT(i1 % i2); return mp_obj_new_tuple(2, args); +#if MICROPY_PY_BUILTINS_FLOAT + } else if (MP_OBJ_IS_TYPE(o1_in, &mp_type_float) || MP_OBJ_IS_TYPE(o2_in, &mp_type_float)) { + mp_float_t f1 = mp_obj_get_float(o1_in); + mp_float_t f2 = mp_obj_get_float(o2_in); + if (f2 == 0.0) { + goto zero_division_error; + } + mp_obj_float_divmod(&f1, &f2); + mp_obj_t tuple[2] = { + mp_obj_new_float(f1), + mp_obj_new_float(f2), + }; + return mp_obj_new_tuple(2, tuple); +#endif } else { nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "unsupported operand type(s) for divmod(): '%s' and '%s'", mp_obj_get_type_str(o1_in), mp_obj_get_type_str(o2_in))); } diff --git a/py/obj.h b/py/obj.h index eff4a4eafc..c7745dc9eb 100644 --- a/py/obj.h +++ b/py/obj.h @@ -487,6 +487,7 @@ typedef struct _mp_obj_float_t { } mp_obj_float_t; mp_float_t mp_obj_float_get(mp_obj_t self_in); mp_obj_t mp_obj_float_binary_op(mp_uint_t op, mp_float_t lhs_val, mp_obj_t rhs); // can return MP_OBJ_NULL if op not supported +void mp_obj_float_divmod(mp_float_t *x, mp_float_t *y); // complex void mp_obj_complex_get(mp_obj_t self_in, mp_float_t *real, mp_float_t *imag); diff --git a/py/objfloat.c b/py/objfloat.c index c6734ee780..b75d0e4ba9 100644 --- a/py/objfloat.c +++ b/py/objfloat.c @@ -143,14 +143,16 @@ mp_obj_t mp_obj_float_binary_op(mp_uint_t op, mp_float_t lhs_val, mp_obj_t rhs_i case MP_BINARY_OP_INPLACE_SUBTRACT: lhs_val -= rhs_val; break; case MP_BINARY_OP_MULTIPLY: case MP_BINARY_OP_INPLACE_MULTIPLY: lhs_val *= rhs_val; break; - // TODO: verify that C floor matches Python semantics case MP_BINARY_OP_FLOOR_DIVIDE: case MP_BINARY_OP_INPLACE_FLOOR_DIVIDE: if (rhs_val == 0) { zero_division_error: - nlr_raise(mp_obj_new_exception_msg(&mp_type_ZeroDivisionError, "float division by zero")); + nlr_raise(mp_obj_new_exception_msg(&mp_type_ZeroDivisionError, "division by zero")); } - lhs_val = MICROPY_FLOAT_C_FUN(floor)(lhs_val / rhs_val); + // Python specs require that x == (x//y)*y + (x%y) so we must + // call divmod to compute the correct floor division, which + // returns the floor divide in lhs_val. + mp_obj_float_divmod(&lhs_val, &rhs_val); break; case MP_BINARY_OP_TRUE_DIVIDE: case MP_BINARY_OP_INPLACE_TRUE_DIVIDE: @@ -159,6 +161,21 @@ mp_obj_t mp_obj_float_binary_op(mp_uint_t op, mp_float_t lhs_val, mp_obj_t rhs_i } lhs_val /= rhs_val; break; + case MP_BINARY_OP_MODULO: + case MP_BINARY_OP_INPLACE_MODULO: + if (rhs_val == 0) { + goto zero_division_error; + } + lhs_val = MICROPY_FLOAT_C_FUN(fmod)(lhs_val, rhs_val); + // Python specs require that mod has same sign as second operand + if (lhs_val == 0.0) { + lhs_val = MICROPY_FLOAT_C_FUN(copysign)(0.0, rhs_val); + } else { + if ((lhs_val < 0.0) != (rhs_val < 0.0)) { + lhs_val += rhs_val; + } + } + break; case MP_BINARY_OP_POWER: case MP_BINARY_OP_INPLACE_POWER: lhs_val = MICROPY_FLOAT_C_FUN(pow)(lhs_val, rhs_val); break; case MP_BINARY_OP_LESS: return MP_BOOL(lhs_val < rhs_val); @@ -173,4 +190,39 @@ mp_obj_t mp_obj_float_binary_op(mp_uint_t op, mp_float_t lhs_val, mp_obj_t rhs_i return mp_obj_new_float(lhs_val); } +void mp_obj_float_divmod(mp_float_t *x, mp_float_t *y) { + // logic here follows that of CPython + // https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations + // x == (x//y)*y + (x%y) + // divmod(x, y) == (x//y, x%y) + mp_float_t mod = MICROPY_FLOAT_C_FUN(fmod)(*x, *y); + mp_float_t div = (*x - mod) / *y; + + // Python specs require that mod has same sign as second operand + if (mod == 0.0) { + mod = MICROPY_FLOAT_C_FUN(copysign)(0.0, *y); + } else { + if ((mod < 0.0) != (*y < 0.0)) { + mod += *y; + div -= 1.0; + } + } + + mp_float_t floordiv; + if (div == 0.0) { + // if division is zero, take the correct sign of zero + floordiv = MICROPY_FLOAT_C_FUN(copysign)(0.0, *x / *y); + } else { + // Python specs require that x == (x//y)*y + (x%y) + floordiv = MICROPY_FLOAT_C_FUN(floor)(div); + if (div - floordiv > 0.5) { + floordiv += 1.0; + } + } + + // return results + *x = floordiv; + *y = mod; +} + #endif // MICROPY_PY_BUILTINS_FLOAT diff --git a/stmhal/Makefile b/stmhal/Makefile index 82d229b873..4978185602 100644 --- a/stmhal/Makefile +++ b/stmhal/Makefile @@ -67,6 +67,7 @@ SRC_LIB = $(addprefix lib/,\ libm/asinfacosf.c \ libm/atanf.c \ libm/atan2f.c \ + libm/fmodf.c \ ) SRC_C = \ diff --git a/tests/float/modulo.py b/tests/float/modulo.py deleted file mode 100644 index 911268513a..0000000000 --- a/tests/float/modulo.py +++ /dev/null @@ -1,14 +0,0 @@ -# check modulo matches python definition -# TODO we currenty fail with this -if False: - print(1.23456 % 0.7) - print(-1.23456 % 0.7) - print(1.23456 % -0.7) - print(-1.23456 % -0.7) - - a = 1.23456 - b = 0.7 - print(a % b) - print(a % -b) - print(-a % b) - print(-a % -b) diff --git a/tests/run-tests b/tests/run-tests index 21a0039e43..34f855d08d 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -33,6 +33,10 @@ def run_tests(pyb, tests, args): if os.getenv('TRAVIS') == 'true': skip_tests.add('basics/memoryerror.py') + # Some tests shouldn't be run on pyboard + if pyb is not None: + skip_tests.add('float/float_divmod.py') # tested by float/float_divmod_relaxed.py instead + # Some tests are known to fail with native emitter # Remove them from the below when they work if args.emit == 'native':