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.
pull/3608/head
Damien George 2018-02-08 14:02:50 +11:00
rodzic 0c650d4276
commit b75cb8392b
3 zmienionych plików z 22 dodań i 2 usunięć

Wyświetl plik

@ -172,10 +172,15 @@ mp_obj_t mp_parse_num_decimal(const char *str, size_t len, bool allow_imag, bool
#if MICROPY_PY_BUILTINS_FLOAT
// DEC_VAL_MAX only needs to be rough and is used to retain precision while not overflowing
// SMALL_NORMAL_VAL is the smallest power of 10 that is still a normal float
#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
#define DEC_VAL_MAX 1e20F
#define SMALL_NORMAL_VAL (1e-37F)
#define SMALL_NORMAL_EXP (-37)
#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE
#define DEC_VAL_MAX 1e200
#define SMALL_NORMAL_VAL (1e-307)
#define SMALL_NORMAL_EXP (-307)
#endif
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
exp_val = -exp_val;
}
// apply the exponent
dec_val *= MICROPY_FLOAT_C_FUN(pow)(10, exp_val + exp_extra);
// apply the exponent, making sure it's not a subnormal value
exp_val += exp_extra;
if (exp_val < SMALL_NORMAL_EXP) {
exp_val -= SMALL_NORMAL_EXP;
dec_val *= SMALL_NORMAL_VAL;
}
dec_val *= MICROPY_FLOAT_C_FUN(pow)(10, exp_val);
}
// negate value if needed

Wyświetl plik

@ -20,3 +20,8 @@ print(float('.' + '9' * 70 + 'e-50') == float('1e-50'))
print(float('.' + '0' * 60 + '1e10') == float('1e-51'))
print(float('.' + '0' * 60 + '9e25'))
print(float('.' + '0' * 60 + '9e40'))
# ensure that accuracy is retained when value is close to a subnormal
print(float('1.00000000000000000000e-37'))
print(float('10.0000000000000000000e-38'))
print(float('100.000000000000000000e-39'))

Wyświetl plik

@ -14,3 +14,8 @@ print(float('.' + '9' * 400 + 'e-100'))
print(float('.' + '0' * 400 + '9e100'))
print(float('.' + '0' * 400 + '9e200'))
print(float('.' + '0' * 400 + '9e400'))
# ensure that accuracy is retained when value is close to a subnormal
print(float('1.00000000000000000000e-307'))
print(float('10.0000000000000000000e-308'))
print(float('100.000000000000000000e-309'))