Skip to content

Commit 8594ce2

Browse files
committed
py: Implement divmod, % and proper // for floating point.
Tested and working on unix and pyboard.
1 parent 5c67834 commit 8594ce2

File tree

8 files changed

+150
-18
lines changed

8 files changed

+150
-18
lines changed

lib/libm/fmodf.c

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*****************************************************************************/
2+
/*****************************************************************************/
3+
// fmodf from musl-0.9.15
4+
/*****************************************************************************/
5+
/*****************************************************************************/
6+
7+
#include "libm.h"
8+
9+
float fmodf(float x, float y)
10+
{
11+
union {float f; uint32_t i;} ux = {x}, uy = {y};
12+
int ex = ux.i>>23 & 0xff;
13+
int ey = uy.i>>23 & 0xff;
14+
uint32_t sx = ux.i & 0x80000000;
15+
uint32_t i;
16+
uint32_t uxi = ux.i;
17+
18+
if (uy.i<<1 == 0 || isnan(y) || ex == 0xff)
19+
return (x*y)/(x*y);
20+
if (uxi<<1 <= uy.i<<1) {
21+
if (uxi<<1 == uy.i<<1)
22+
return 0*x;
23+
return x;
24+
}
25+
26+
/* normalize x and y */
27+
if (!ex) {
28+
for (i = uxi<<9; i>>31 == 0; ex--, i <<= 1);
29+
uxi <<= -ex + 1;
30+
} else {
31+
uxi &= -1U >> 9;
32+
uxi |= 1U << 23;
33+
}
34+
if (!ey) {
35+
for (i = uy.i<<9; i>>31 == 0; ey--, i <<= 1);
36+
uy.i <<= -ey + 1;
37+
} else {
38+
uy.i &= -1U >> 9;
39+
uy.i |= 1U << 23;
40+
}
41+
42+
/* x mod y */
43+
for (; ex > ey; ex--) {
44+
i = uxi - uy.i;
45+
if (i >> 31 == 0) {
46+
if (i == 0)
47+
return 0*x;
48+
uxi = i;
49+
}
50+
uxi <<= 1;
51+
}
52+
i = uxi - uy.i;
53+
if (i >> 31 == 0) {
54+
if (i == 0)
55+
return 0*x;
56+
uxi = i;
57+
}
58+
for (; uxi>>23 == 0; uxi <<= 1, ex--);
59+
60+
/* scale result up */
61+
if (ex > 0) {
62+
uxi -= 1U << 23;
63+
uxi |= (uint32_t)ex << 23;
64+
} else {
65+
uxi >>= -ex + 1;
66+
}
67+
uxi |= sx;
68+
ux.i = uxi;
69+
return ux.f;
70+
}

lib/libm/math.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ float acoshf(float x) { return 0.0; }
117117
float asinhf(float x) { return 0.0; }
118118
float atanhf(float x) { return 0.0; }
119119
float tanf(float x) { return 0.0; }
120-
float fmodf(float x, float y) { return 0.0; }
121120
float tgammaf(float x) { return 0.0; }
122121
float lgammaf(float x) { return 0.0; }
123122
float erff(float x) { return 0.0; }

py/builtin.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,13 +242,32 @@ STATIC mp_obj_t mp_builtin_dir(mp_uint_t n_args, const mp_obj_t *args) {
242242
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_dir_obj, 0, 1, mp_builtin_dir);
243243

244244
STATIC mp_obj_t mp_builtin_divmod(mp_obj_t o1_in, mp_obj_t o2_in) {
245+
// TODO handle big int
245246
if (MP_OBJ_IS_SMALL_INT(o1_in) && MP_OBJ_IS_SMALL_INT(o2_in)) {
246247
mp_int_t i1 = MP_OBJ_SMALL_INT_VALUE(o1_in);
247248
mp_int_t i2 = MP_OBJ_SMALL_INT_VALUE(o2_in);
249+
if (i2 == 0) {
250+
zero_division_error:
251+
nlr_raise(mp_obj_new_exception_msg(&mp_type_ZeroDivisionError, "division by zero"));
252+
}
248253
mp_obj_t args[2];
249254
args[0] = MP_OBJ_NEW_SMALL_INT(i1 / i2);
250255
args[1] = MP_OBJ_NEW_SMALL_INT(i1 % i2);
251256
return mp_obj_new_tuple(2, args);
257+
#if MICROPY_PY_BUILTINS_FLOAT
258+
} else if (MP_OBJ_IS_TYPE(o1_in, &mp_type_float) || MP_OBJ_IS_TYPE(o2_in, &mp_type_float)) {
259+
mp_float_t f1 = mp_obj_get_float(o1_in);
260+
mp_float_t f2 = mp_obj_get_float(o2_in);
261+
if (f2 == 0.0) {
262+
goto zero_division_error;
263+
}
264+
mp_obj_float_divmod(&f1, &f2);
265+
mp_obj_t tuple[2] = {
266+
mp_obj_new_float(f1),
267+
mp_obj_new_float(f2),
268+
};
269+
return mp_obj_new_tuple(2, tuple);
270+
#endif
252271
} else {
253272
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)));
254273
}

py/obj.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,7 @@ typedef struct _mp_obj_float_t {
487487
} mp_obj_float_t;
488488
mp_float_t mp_obj_float_get(mp_obj_t self_in);
489489
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
490+
void mp_obj_float_divmod(mp_float_t *x, mp_float_t *y);
490491

491492
// complex
492493
void mp_obj_complex_get(mp_obj_t self_in, mp_float_t *real, mp_float_t *imag);

py/objfloat.c

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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
143143
case MP_BINARY_OP_INPLACE_SUBTRACT: lhs_val -= rhs_val; break;
144144
case MP_BINARY_OP_MULTIPLY:
145145
case MP_BINARY_OP_INPLACE_MULTIPLY: lhs_val *= rhs_val; break;
146-
// TODO: verify that C floor matches Python semantics
147146
case MP_BINARY_OP_FLOOR_DIVIDE:
148147
case MP_BINARY_OP_INPLACE_FLOOR_DIVIDE:
149148
if (rhs_val == 0) {
150149
zero_division_error:
151-
nlr_raise(mp_obj_new_exception_msg(&mp_type_ZeroDivisionError, "float division by zero"));
150+
nlr_raise(mp_obj_new_exception_msg(&mp_type_ZeroDivisionError, "division by zero"));
152151
}
153-
lhs_val = MICROPY_FLOAT_C_FUN(floor)(lhs_val / rhs_val);
152+
// Python specs require that x == (x//y)*y + (x%y) so we must
153+
// call divmod to compute the correct floor division, which
154+
// returns the floor divide in lhs_val.
155+
mp_obj_float_divmod(&lhs_val, &rhs_val);
154156
break;
155157
case MP_BINARY_OP_TRUE_DIVIDE:
156158
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
159161
}
160162
lhs_val /= rhs_val;
161163
break;
164+
case MP_BINARY_OP_MODULO:
165+
case MP_BINARY_OP_INPLACE_MODULO:
166+
if (rhs_val == 0) {
167+
goto zero_division_error;
168+
}
169+
lhs_val = MICROPY_FLOAT_C_FUN(fmod)(lhs_val, rhs_val);
170+
// Python specs require that mod has same sign as second operand
171+
if (lhs_val == 0.0) {
172+
lhs_val = MICROPY_FLOAT_C_FUN(copysign)(0.0, rhs_val);
173+
} else {
174+
if ((lhs_val < 0.0) != (rhs_val < 0.0)) {
175+
lhs_val += rhs_val;
176+
}
177+
}
178+
break;
162179
case MP_BINARY_OP_POWER:
163180
case MP_BINARY_OP_INPLACE_POWER: lhs_val = MICROPY_FLOAT_C_FUN(pow)(lhs_val, rhs_val); break;
164181
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
173190
return mp_obj_new_float(lhs_val);
174191
}
175192

193+
void mp_obj_float_divmod(mp_float_t *x, mp_float_t *y) {
194+
// logic here follows that of CPython
195+
// https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations
196+
// x == (x//y)*y + (x%y)
197+
// divmod(x, y) == (x//y, x%y)
198+
mp_float_t mod = MICROPY_FLOAT_C_FUN(fmod)(*x, *y);
199+
mp_float_t div = (*x - mod) / *y;
200+
201+
// Python specs require that mod has same sign as second operand
202+
if (mod == 0.0) {
203+
mod = MICROPY_FLOAT_C_FUN(copysign)(0.0, *y);
204+
} else {
205+
if ((mod < 0.0) != (*y < 0.0)) {
206+
mod += *y;
207+
div -= 1.0;
208+
}
209+
}
210+
211+
mp_float_t floordiv;
212+
if (div == 0.0) {
213+
// if division is zero, take the correct sign of zero
214+
floordiv = MICROPY_FLOAT_C_FUN(copysign)(0.0, *x / *y);
215+
} else {
216+
// Python specs require that x == (x//y)*y + (x%y)
217+
floordiv = MICROPY_FLOAT_C_FUN(floor)(div);
218+
if (div - floordiv > 0.5) {
219+
floordiv += 1.0;
220+
}
221+
}
222+
223+
// return results
224+
*x = floordiv;
225+
*y = mod;
226+
}
227+
176228
#endif // MICROPY_PY_BUILTINS_FLOAT

stmhal/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ SRC_LIB = $(addprefix lib/,\
6767
libm/asinfacosf.c \
6868
libm/atanf.c \
6969
libm/atan2f.c \
70+
libm/fmodf.c \
7071
)
7172

7273
SRC_C = \

tests/float/modulo.py

Lines changed: 0 additions & 14 deletions
This file was deleted.

tests/run-tests

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ def run_tests(pyb, tests, args):
3333
if os.getenv('TRAVIS') == 'true':
3434
skip_tests.add('basics/memoryerror.py')
3535

36+
# Some tests shouldn't be run on pyboard
37+
if pyb is not None:
38+
skip_tests.add('float/float_divmod.py') # tested by float/float_divmod_relaxed.py instead
39+
3640
# Some tests are known to fail with native emitter
3741
# Remove them from the below when they work
3842
if args.emit == 'native':

0 commit comments

Comments
 (0)