From c30a7adaff1cd04c1ddede8f80b79669222a3c8c Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Mon, 29 Sep 2025 11:24:41 +0800 Subject: [PATCH] add `cpy11__float_div_mod` --- docs/modules/math.md | 7 ++-- src/bindings/py_number.c | 72 ++++++++++++++++++++++++++++++++++++---- src/modules/math.c | 2 ++ tests/02_float.py | 7 ++++ 4 files changed, 80 insertions(+), 8 deletions(-) diff --git a/docs/modules/math.md b/docs/modules/math.md index 421edbef..f84571db 100644 --- a/docs/modules/math.md +++ b/docs/modules/math.md @@ -123,11 +123,14 @@ Convert angle `x` from radians to degrees. Convert angle `x` from degrees to radians. - ### `math.modf(x)` Return the fractional and integer parts of `x`. Both results carry the sign of `x` and are floats. +### `math.copysign(x, y)` + +Return a float with the magnitude (absolute value) of `x` but the sign of `y`. + ### `math.factorial(x)` -Return `x` factorial as an integer. \ No newline at end of file +Return `x` factorial as an integer. diff --git a/src/bindings/py_number.c b/src/bindings/py_number.c index 1e8db693..55c9d494 100644 --- a/src/bindings/py_number.c +++ b/src/bindings/py_number.c @@ -149,6 +149,44 @@ static py_i64 cpy11__fast_mod(py_i64 a, py_i64 b) { return b < 0 ? -res : res; } +// https://github.com/python/cpython/blob/3.11/Objects/floatobject.c#L677 +static void cpy11__float_div_mod(double vx, double wx, double *floordiv, double *mod) +{ + double div; + *mod = fmod(vx, wx); + /* fmod is typically exact, so vx-mod is *mathematically* an + exact multiple of wx. But this is fp arithmetic, and fp + vx - mod is an approximation; the result is that div may + not be an exact integral value after the division, although + it will always be very close to one. + */ + div = (vx - *mod) / wx; + if (*mod) { + /* ensure the remainder has the same sign as the denominator */ + if ((wx < 0) != (*mod < 0)) { + *mod += wx; + div -= 1.0; + } + } + else { + /* the remainder is zero, and in the presence of signed zeroes + fmod returns different results across platforms; ensure + it has the same sign as the denominator. */ + *mod = copysign(0.0, wx); + } + /* snap quotient to nearest integral value */ + if (div) { + *floordiv = floor(div); + if (div - *floordiv > 0.5) { + *floordiv += 1.0; + } + } + else { + /* div is zero - get the same sign as the true quotient */ + *floordiv = copysign(0.0, vx / wx); /* zero w/ sign of vx/wx */ + } +} + static bool int__floordiv__(int argc, py_Ref argv) { PY_CHECK_ARGC(2); py_i64 lhs = py_toint(&argv[0]); @@ -181,8 +219,24 @@ static bool float__floordiv__(int argc, py_Ref argv) { py_f64 rhs; if(try_castfloat(&argv[1], &rhs)) { if(rhs == 0.0) return ZeroDivisionError("float modulo by zero"); - py_f64 r = fmod(lhs, rhs); - py_newfloat(py_retval(), trunc((lhs - r) / rhs)); + double q, r; + cpy11__float_div_mod(lhs, rhs, &q, &r); + py_newfloat(py_retval(), q); + return true; + } + py_newnotimplemented(py_retval()); + return true; +} + +static bool float__rfloordiv__(int argc, py_Ref argv) { + PY_CHECK_ARGC(2); + py_f64 rhs = py_tofloat(&argv[0]); + py_f64 lhs; + if(try_castfloat(&argv[1], &lhs)) { + if(rhs == 0.0) return ZeroDivisionError("float modulo by zero"); + double q, r; + cpy11__float_div_mod(lhs, rhs, &q, &r); + py_newfloat(py_retval(), q); return true; } py_newnotimplemented(py_retval()); @@ -195,7 +249,9 @@ static bool float__mod__(int argc, py_Ref argv) { py_f64 rhs; if(try_castfloat(&argv[1], &rhs)) { if(rhs == 0.0) return ZeroDivisionError("float modulo by zero"); - py_newfloat(py_retval(), fmod(lhs, rhs)); + double q, r; + cpy11__float_div_mod(lhs, rhs, &q, &r); + py_newfloat(py_retval(), r); return true; } py_newnotimplemented(py_retval()); @@ -208,7 +264,9 @@ static bool float__rmod__(int argc, py_Ref argv) { py_f64 lhs; if(try_castfloat(&argv[1], &lhs)) { if(rhs == 0.0) return ZeroDivisionError("float modulo by zero"); - py_newfloat(py_retval(), fmod(lhs, rhs)); + double q, r; + cpy11__float_div_mod(lhs, rhs, &q, &r); + py_newfloat(py_retval(), r); return true; } py_newnotimplemented(py_retval()); @@ -221,9 +279,10 @@ static bool float__divmod__(int argc, py_Ref argv) { py_f64 rhs; if(try_castfloat(&argv[1], &rhs)) { if(rhs == 0.0) return ZeroDivisionError("float modulo by zero"); - py_f64 r = fmod(lhs, rhs); + double q, r; + cpy11__float_div_mod(lhs, rhs, &q, &r); py_Ref p = py_newtuple(py_retval(), 2); - py_newfloat(&p[0], trunc((lhs - r) / rhs)); + py_newfloat(&p[0], q); py_newfloat(&p[1], r); return true; } @@ -565,6 +624,7 @@ void pk_number__register() { // fmod py_bindmagic(tp_float, __floordiv__, float__floordiv__); + py_bindmagic(tp_float, __rfloordiv__, float__rfloordiv__); py_bindmagic(tp_float, __mod__, float__mod__); py_bindmagic(tp_float, __rmod__, float__rmod__); py_bindmagic(tp_float, __divmod__, float__divmod__); diff --git a/src/modules/math.c b/src/modules/math.c index 4ef3f81e..8c6fb55b 100644 --- a/src/modules/math.c +++ b/src/modules/math.c @@ -133,6 +133,7 @@ static bool math_radians(int argc, py_Ref argv) { } TWO_ARG_FUNC(fmod, fmod) +TWO_ARG_FUNC(copysign, copysign) static bool math_modf(int argc, py_Ref argv) { PY_CHECK_ARGC(1); @@ -200,6 +201,7 @@ void pk__add_module_math() { py_bindfunc(mod, "fmod", math_fmod); py_bindfunc(mod, "modf", math_modf); + py_bindfunc(mod, "copysign", math_copysign); py_bindfunc(mod, "factorial", math_factorial); } diff --git a/tests/02_float.py b/tests/02_float.py index 6d801938..4652133a 100644 --- a/tests/02_float.py +++ b/tests/02_float.py @@ -120,3 +120,10 @@ assert eq(10.5 // 4.5, 2.0) _0, _1 = divmod(10.5, 4) assert eq(_0, 2.0) assert eq(_1, 2.5) + +assert eq(3.4 % -2, -0.6) +assert eq(-2 % 3.4, 1.4) +assert eq(-3.4 % -2, -1.4) +assert eq(-6 // 3.4, -2.0) +assert eq(-6 % 3.4, 0.8) +