E564 py/modmath: Implement math.isclose() for non-complex numbers. · micropython/micropython@af5c998 · GitHub
[go: up one dir, main page]

Skip to content

Commit af5c998

Browse files
stinosdpgeorge
authored andcommitted
py/modmath: Implement math.isclose() for non-complex numbers.
As per PEP 485, this function appeared in for Python 3.5. Configured via MICROPY_PY_MATH_ISCLOSE which is disabled by default, but enabled for the ports which already have MICROPY_PY_MATH_SPECIAL_FUNCTIONS enabled.
1 parent 3eff812 commit af5c998

File tree

9 files changed

+123
-0
lines changed

9 files changed

+123
-0
lines changed

ports/esp32/mpconfigport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
#define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1)
9999
#define MICROPY_PY_MATH (1)
100100
#define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1)
101+
#define MICROPY_PY_MATH_ISCLOSE (1)
101102
#define MICROPY_PY_CMATH (1)
102103
#define MICROPY_PY_GC (1)
103104
#define MICROPY_PY_IO (1)

ports/javascript/mpconfigport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
#define MICROPY_PY_COLLECTIONS (1)
7474
#define MICROPY_PY_MATH (1)
7575
#define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1)
76+
#define MICROPY_PY_MATH_ISCLOSE (1)
7677
#define MICROPY_PY_CMATH (1)
7778
#define MICROPY_PY_IO (1)
7879
#define MICROPY_PY_STRUCT (1)

ports/stm32/mpconfigport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
#define MICROPY_PY_COLLECTIONS_DEQUE (1)
115115
#define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1)
116116
#define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1)
117+
#define MICROPY_PY_MATH_ISCLOSE (1)
117118
#define MICROPY_PY_MATH_FACTORIAL (1)
118119
#define MICROPY_PY_CMATH (1)
119120
#define MICROPY_PY_IO (1)

ports/unix/mpconfigport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
#ifndef MICROPY_PY_MATH_SPECIAL_FUNCTIONS
105105
#define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1)
106106
#endif
107+
#define MICROPY_PY_MATH_ISCLOSE (MICROPY_PY_MATH_SPECIAL_FUNCTIONS)
107108
#define MICROPY_PY_CMATH (1)
108109
#define MICROPY_PY_IO_IOBASE (1)
109110
#define MICROPY_PY_IO_FILEIO (1)

ports/windows/mpconfigport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
#define MICROPY_PY_COLLECTIONS_DEQUE (1)
8787
#define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1)
8888
#define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1)
89+
#define MICROPY_PY_MATH_ISCLOSE (1)
8990
#define MICROPY_PY_CMATH (1)
9091
#define MICROPY_PY_IO_FILEIO (1)
9192
#define MICROPY_PY_GC_COLLECT_RETVAL (1)

py/modmath.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,42 @@ MATH_FUN_1(lgamma, lgamma)
171171
#endif
172172
//TODO: fsum
173173

174+
#if MICROPY_PY_MATH_ISCLOSE
175+
STATIC mp_obj_t mp_math_isclose(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
176+
enum { ARG_a, ARG_b, ARG_rel_tol, ARG_abs_tol };
177+
static const mp_arg_t allowed_args[] = {
178+
{MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ},
179+
{MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ},
180+
{MP_QSTR_rel_tol, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
181+
{MP_QSTR_abs_tol, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_SMALL_INT(0)}},
182+
};
183+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
184+
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
185+
const mp_float_t a = mp_obj_get_float(args[ARG_a].u_obj);
186+
const mp_float_t b = mp_obj_get_float(args[ARG_b].u_obj);
187+
const mp_float_t rel_tol = args[ARG_rel_tol].u_obj == MP_OBJ_NULL
188+
? (mp_float_t)1e-9 : mp_obj_get_float(args[ARG_rel_tol].u_obj);
189+
const mp_float_t abs_tol = mp_obj_get_float(args[ARG_abs_tol].u_obj);
190+
if (rel_tol < (mp_float_t)0.0 || abs_tol < (mp_float_t)0.0) {
191+
math_error();
192+
}
193+
if (a == b) {
194+
return mp_const_true;
195+
}
196+
const mp_float_t difference = MICROPY_FLOAT_C_FUN(fabs)(a - b);
197+
if (isinf(difference)) { // Either a or b is inf
198+
return mp_const_false;
199+
}
200+
if ((difference <= abs_tol) ||
201+
(difference <= MICROPY_FLOAT_C_FUN(fabs)(rel_tol * a)) ||
202+
(difference <= MICROPY_FLOAT_C_FUN(fabs)(rel_tol * b))) {
203+
return mp_const_true;
204+
}
205+
return mp_const_false;
206+
}
207+
MP_DEFINE_CONST_FUN_OBJ_KW(mp_math_isclose_obj, 2, mp_math_isclose);
208+
#endif
209+
174210
// Function that takes a variable number of arguments
175211

176212
// log(x[, base])
@@ -335,6 +371,9 @@ STATIC const mp_rom_map_elem_t mp_module_math_globals_table[] = {
335371
{ MP_ROM_QSTR(MP_QSTR_isfinite), MP_ROM_PTR(&mp_math_isfinite_obj) },
336372
{ MP_ROM_QSTR(MP_QSTR_isinf), MP_ROM_PTR(&mp_math_isinf_obj) },
337373
{ MP_ROM_QSTR(MP_QSTR_isnan), MP_ROM_PTR(&mp_math_isnan_obj) },
374+
#if MICROPY_PY_MATH_ISCLOSE
375+
{ MP_ROM_QSTR(MP_QSTR_isclose), MP_ROM_PTR(&mp_math_isclose_obj) },
376+
#endif
338377
{ MP_ROM_QSTR(MP_QSTR_trunc), MP_ROM_PTR(&mp_math_trunc_obj) },
339378
{ MP_ROM_QSTR(MP_QSTR_radians), MP_ROM_PTR(&mp_math_radians_obj) },
340379
{ MP_ROM_QSTR(MP_QSTR_degrees), MP_ROM_PTR(&mp_math_degrees_obj) },

py/mpconfig.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,6 +1079,11 @@ typedef double mp_float_t;
10791079
#define MICROPY_PY_MATH_FACTORIAL (0)
10801080
#endif
10811081

1082+
// Whether to provide math.isclose function
1083+
#ifndef MICROPY_PY_MATH_ISCLOSE
1084+
#define MICROPY_PY_MATH_ISCLOSE (0)
1085+
#endif
1086+
10821087
// Whether to provide "cmath" module
10831088
#ifndef MICROPY_PY_CMATH
10841089
#define MICROPY_PY_CMATH (0)

tests/float/math_isclose.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# test math.isclose (appeared in Python 3.5)
2+
3+
try:
4+
from math import isclose
5+
except ImportError:
6+
print("SKIP")
7+
raise SystemExit
8+
9+
def test(a, b, **kwargs):
10+
print(isclose(a, b, **kwargs))
11+
12+
def test_combinations(a, b, **kwargs):
13+
test(a, a, **kwargs)
14+
test(a, b, **kwargs)
15+
test(b, a, **kwargs)
16+
test(b, b, **kwargs)
17+
18+
# Special numbers
19+
test_combinations(float('nan'), 1)
20+
test_combinations(float('inf'), 1)
21+
test_combinations(float('-inf'), 1)
22+
23+
# Equality
24+
test(1.0, 1.0, rel_tol=0.0, abs_tol=0.0)
25+
test(2.35e-100, 2.35e-100, rel_tol=0.0, abs_tol=0.0)
26+
test(2.1234e100, 2.1234e100, rel_tol=0.0, abs_tol=0.0)
27+
28+
# Relative tolerance
29+
test(1000.0, 1001.0, rel_tol=1e-3)
30+
test(1000.0, 1001.0, rel_tol=1e-4)
31+
test(1000, 1001, rel_tol=1e-3)
32+
test(1000, 1001, rel_tol=1e-4)
33+
test_combinations(0, 1, rel_tol=1.0)
34+
35+
# Absolute tolerance
36+
test(0.0, 1e-10, abs_tol=1e-10, rel_tol=0.1)
37+
test(0.0, 1e-10, abs_tol=0.0, rel_tol=0.1)
38+
39+
# Bad parameters
40+
try:
41+
isclose(0, 0, abs_tol=-1)
42+
except ValueError:
43+
print('ValueError')
44+
try:
45+
isclose(0, 0, rel_tol=-1)
46+
except ValueError:
47+
print('ValueError')

tests/float/math_isclose.py.exp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
False
2+
False
3+
False
4+
True
5+
True
6+
False
7+
False
8+
True
9+
True
10+
False
11+
False
12+
True
13+
True
14+
True
15+
True
16+
True
17+
False
18+
True
19+
False
20+
True
21+
True
22+
True
23+
True
24+
True
25+
False
26+
ValueError
27+
ValueError

0 commit comments

Comments
 (0)
0