Skip to content

Commit b75cb83

Browse files
committed
py/parsenum: Fix parsing of floats that are close to subnormal.
Prior to this patch, a float literal that was close to subnormal would have a loss of precision when parsed. The worst case was something like float('10000000000000000000e-326') which returned 0.0.
1 parent 0c650d4 commit b75cb83

File tree

3 files changed

+22
-2
lines changed

3 files changed

+22
-2
lines changed

py/parsenum.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,15 @@ mp_obj_t mp_parse_num_decimal(const char *str, size_t len, bool allow_imag, bool
172172
#if MICROPY_PY_BUILTINS_FLOAT
173173

174174
// DEC_VAL_MAX only needs to be rough and is used to retain precision while not overflowing
175+
// SMALL_NORMAL_VAL is the smallest power of 10 that is still a normal float
175176
#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
176177
#define DEC_VAL_MAX 1e20F
178+
#define SMALL_NORMAL_VAL (1e-37F)
179+
#define SMALL_NORMAL_EXP (-37)
177180
#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE
178181
#define DEC_VAL_MAX 1e200
182+
#define SMALL_NORMAL_VAL (1e-307)
183+
#define SMALL_NORMAL_EXP (-307)
179184
#endif
180185

181186
const char *top = str + len;
@@ -275,8 +280,13 @@ mp_obj_t mp_parse_num_decimal(const char *str, size_t len, bool allow_imag, bool
275280
exp_val = -exp_val;
276281
}
277282

278-
// apply the exponent
279-
dec_val *= MICROPY_FLOAT_C_FUN(pow)(10, exp_val + exp_extra);
283+
// apply the exponent, making sure it's not a subnormal value
284+
exp_val += exp_extra;
285+
if (exp_val < SMALL_NORMAL_EXP) {
286+
exp_val -= SMALL_NORMAL_EXP;
287+
dec_val *= SMALL_NORMAL_VAL;
288+
}
289+
dec_val *= MICROPY_FLOAT_C_FUN(pow)(10, exp_val);
280290
}
281291

282292
// negate value if needed

tests/float/float_parse.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,8 @@
2020
print(float('.' + '0' * 60 + '1e10') == float('1e-51'))
2121
print(float('.' + '0' * 60 + '9e25'))
2222
print(float('.' + '0' * 60 + '9e40'))
23+
24+
# ensure that accuracy is retained when value is close to a subnormal
25+
print(float('1.00000000000000000000e-37'))
26+
print(float('10.0000000000000000000e-38'))
27+
print(float('100.000000000000000000e-39'))

tests/float/float_parse_doubleprec.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,8 @@
1414
print(float('.' + '0' * 400 + '9e100'))
1515
print(float('.' + '0' * 400 + '9e200'))
1616
print(float('.' + '0' * 400 + '9e400'))
17+
18+
# ensure that accuracy is retained when value is close to a subnormal
19+
print(float('1.00000000000000000000e-307'))
20+
print(float('10.0000000000000000000e-308'))
21+
print(float('100.000000000000000000e-309'))

0 commit comments

Comments
 (0)