Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions py/parsenum.c
Original file line number Diff line number Diff line change
Expand Up @@ -179,39 +179,40 @@ typedef enum {
} parse_dec_in_t;

#if MICROPY_PY_BUILTINS_FLOAT
// DEC_VAL_MAX only needs to be rough and is used to retain precision while not overflowing
// MANTISSA_MAX is used to retain precision while not overflowing mantissa
// SMALL_NORMAL_VAL is the smallest power of 10 that is still a normal float
// EXACT_POWER_OF_10 is the largest value of x so that 10^x can be stored exactly in a float
// Note: EXACT_POWER_OF_10 is at least floor(log_5(2^mantissa_length)). Indeed, 10^n = 2^n * 5^n
// so we only have to store the 5^n part in the mantissa (the 2^n part will go into the float's
// exponent).
#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
#define DEC_VAL_MAX 1e20F
#define MANTISSA_MAX 0x19999998U
#define SMALL_NORMAL_VAL (1e-37F)
#define SMALL_NORMAL_EXP (-37)
#define EXACT_POWER_OF_10 (9)
#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE
#define DEC_VAL_MAX 1e200
#define MANTISSA_MAX 0x1999999999999998ULL
#define SMALL_NORMAL_VAL (1e-307)
#define SMALL_NORMAL_EXP (-307)
#define EXACT_POWER_OF_10 (22)
#endif

// Break out inner digit accumulation routine to ease trailing zero deferral.
static void accept_digit(mp_float_t *p_dec_val, int dig, int *p_exp_extra, int in) {
static mp_float_uint_t accept_digit(mp_float_uint_t p_mantissa, unsigned int dig, int *p_exp_extra, int in) {
// Core routine to ingest an additional digit.
if (*p_dec_val < DEC_VAL_MAX) {
if (p_mantissa < MANTISSA_MAX) {
// dec_val won't overflow so keep accumulating
*p_dec_val = 10 * *p_dec_val + dig;
if (in == PARSE_DEC_IN_FRAC) {
--(*p_exp_extra);
}
return 10u * p_mantissa + dig;
} else {
// dec_val might overflow and we anyway can't represent more digits
// of precision, so ignore the digit and just adjust the exponent
if (in == PARSE_DEC_IN_INTG) {
++(*p_exp_extra);
}
return p_mantissa;
}
}
#endif // MICROPY_PY_BUILTINS_FLOAT
Expand Down Expand Up @@ -273,6 +274,7 @@ mp_obj_t mp_parse_num_float(const char *str, size_t len, bool allow_imag, mp_lex
// string should be a decimal number
parse_dec_in_t in = PARSE_DEC_IN_INTG;
bool exp_neg = false;
mp_float_uint_t mantissa = 0;
int exp_val = 0;
int exp_extra = 0;
int trailing_zeros_intg = 0, trailing_zeros_frac = 0;
Expand All @@ -288,9 +290,9 @@ mp_obj_t mp_parse_num_float(const char *str, size_t len, bool allow_imag, mp_lex
exp_val = 10 * exp_val + dig;
}
} else {
if (dig == 0 || dec_val >= DEC_VAL_MAX) {
if (dig == 0 || mantissa >= MANTISSA_MAX) {
// Defer treatment of zeros in fractional part. If nothing comes afterwards, ignore them.
// Also, once we reach DEC_VAL_MAX, treat every additional digit as a trailing zero.
// Also, once we reach MANTISSA_MAX, treat every additional digit as a trailing zero.
if (in == PARSE_DEC_IN_INTG) {
++trailing_zeros_intg;
} else {
Expand All @@ -299,14 +301,14 @@ mp_obj_t mp_parse_num_float(const char *str, size_t len, bool allow_imag, mp_lex
} else {
// Time to un-defer any trailing zeros. Intg zeros first.
while (trailing_zeros_intg) {
accept_digit(&dec_val, 0, &exp_extra, PARSE_DEC_IN_INTG);
mantissa = accept_digit(mantissa, 0, &exp_extra, PARSE_DEC_IN_INTG);
--trailing_zeros_intg;
}
while (trailing_zeros_frac) {
accept_digit(&dec_val, 0, &exp_extra, PARSE_DEC_IN_FRAC);
mantissa = accept_digit(mantissa, 0, &exp_extra, PARSE_DEC_IN_FRAC);
--trailing_zeros_frac;
}
accept_digit(&dec_val, dig, &exp_extra, in);
mantissa = accept_digit(mantissa, dig, &exp_extra, in);
}
}
} else if (in == PARSE_DEC_IN_INTG && dig == '.') {
Expand Down Expand Up @@ -340,6 +342,7 @@ mp_obj_t mp_parse_num_float(const char *str, size_t len, bool allow_imag, mp_lex

// apply the exponent, making sure it's not a subnormal value
exp_val += exp_extra + trailing_zeros_intg;
dec_val = (mp_float_t)mantissa;
if (exp_val < SMALL_NORMAL_EXP) {
exp_val -= SMALL_NORMAL_EXP;
dec_val *= SMALL_NORMAL_VAL;
Expand Down
3 changes: 3 additions & 0 deletions tests/float/float_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
print(float("1e18446744073709551621"))
print(float("1e-18446744073709551621"))

# mantissa overflow while processing deferred trailing zeros
print(float("10000000000000000000001"))

# check small decimals are as close to their true value as possible
for n in range(1, 10):
print(float("0.%u" % n) == n / 10)
Loading