py/parsenum: Ensure that trailing zeros lead to identical results.
Prior to this commit, parsenum would calculate "1e-20" as 1.0*pow(10, -20), and "1.000e-20" as 1000.0*pow(10, -23); in certain cases, this could make seemingly-identical values compare as not equal. This commit watches for trailing zeros as a special case, and ignores them when appropriate, so "1.000e-20" is also calculated as 1.0*pow(10, -20). Fixes issue #5831.
This commit is contained in:
parent
69719927f1
commit
6cd2e41918
|
@ -178,6 +178,44 @@ typedef enum {
|
||||||
PARSE_DEC_IN_EXP,
|
PARSE_DEC_IN_EXP,
|
||||||
} parse_dec_in_t;
|
} 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
|
||||||
|
// 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 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 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) {
|
||||||
|
// Core routine to ingest an additional digit.
|
||||||
|
if (*p_dec_val < DEC_VAL_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);
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // MICROPY_BUILTINS_FLOAT
|
||||||
|
|
||||||
#if MICROPY_PY_BUILTINS_COMPLEX
|
#if MICROPY_PY_BUILTINS_COMPLEX
|
||||||
mp_obj_t mp_parse_num_decimal(const char *str, size_t len, bool allow_imag, bool force_complex, mp_lexer_t *lex)
|
mp_obj_t mp_parse_num_decimal(const char *str, size_t len, bool allow_imag, bool force_complex, mp_lexer_t *lex)
|
||||||
#else
|
#else
|
||||||
|
@ -186,24 +224,6 @@ mp_obj_t mp_parse_num_float(const char *str, size_t len, bool allow_imag, mp_lex
|
||||||
{
|
{
|
||||||
#if MICROPY_PY_BUILTINS_FLOAT
|
#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
|
|
||||||
// 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 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 SMALL_NORMAL_VAL (1e-307)
|
|
||||||
#define SMALL_NORMAL_EXP (-307)
|
|
||||||
#define EXACT_POWER_OF_10 (22)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const char *top = str + len;
|
const char *top = str + len;
|
||||||
mp_float_t dec_val = 0;
|
mp_float_t dec_val = 0;
|
||||||
bool dec_neg = false;
|
bool dec_neg = false;
|
||||||
|
@ -255,6 +275,7 @@ parse_start:
|
||||||
bool exp_neg = false;
|
bool exp_neg = false;
|
||||||
int exp_val = 0;
|
int exp_val = 0;
|
||||||
int exp_extra = 0;
|
int exp_extra = 0;
|
||||||
|
int trailing_zeros_intg = 0, trailing_zeros_frac = 0;
|
||||||
while (str < top) {
|
while (str < top) {
|
||||||
unsigned int dig = *str++;
|
unsigned int dig = *str++;
|
||||||
if ('0' <= dig && dig <= '9') {
|
if ('0' <= dig && dig <= '9') {
|
||||||
|
@ -267,18 +288,25 @@ parse_start:
|
||||||
exp_val = 10 * exp_val + dig;
|
exp_val = 10 * exp_val + dig;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (dec_val < DEC_VAL_MAX) {
|
if (dig == 0 || dec_val >= DEC_VAL_MAX) {
|
||||||
// dec_val won't overflow so keep accumulating
|
// Defer treatment of zeros in fractional part. If nothing comes afterwards, ignore them.
|
||||||
dec_val = 10 * dec_val + dig;
|
// Also, once we reach DEC_VAL_MAX, treat every additional digit as a trailing zero.
|
||||||
if (in == PARSE_DEC_IN_FRAC) {
|
if (in == PARSE_DEC_IN_INTG) {
|
||||||
--exp_extra;
|
++trailing_zeros_intg;
|
||||||
|
} else {
|
||||||
|
++trailing_zeros_frac;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// dec_val might overflow and we anyway can't represent more digits
|
// Time to un-defer any trailing zeros. Intg zeros first.
|
||||||
// of precision, so ignore the digit and just adjust the exponent
|
while (trailing_zeros_intg) {
|
||||||
if (in == PARSE_DEC_IN_INTG) {
|
accept_digit(&dec_val, 0, &exp_extra, PARSE_DEC_IN_INTG);
|
||||||
++exp_extra;
|
--trailing_zeros_intg;
|
||||||
}
|
}
|
||||||
|
while (trailing_zeros_frac) {
|
||||||
|
accept_digit(&dec_val, 0, &exp_extra, PARSE_DEC_IN_FRAC);
|
||||||
|
--trailing_zeros_frac;
|
||||||
|
}
|
||||||
|
accept_digit(&dec_val, dig, &exp_extra, in);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (in == PARSE_DEC_IN_INTG && dig == '.') {
|
} else if (in == PARSE_DEC_IN_INTG && dig == '.') {
|
||||||
|
@ -311,7 +339,7 @@ parse_start:
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply the exponent, making sure it's not a subnormal value
|
// apply the exponent, making sure it's not a subnormal value
|
||||||
exp_val += exp_extra;
|
exp_val += exp_extra + trailing_zeros_intg;
|
||||||
if (exp_val < SMALL_NORMAL_EXP) {
|
if (exp_val < SMALL_NORMAL_EXP) {
|
||||||
exp_val -= SMALL_NORMAL_EXP;
|
exp_val -= SMALL_NORMAL_EXP;
|
||||||
dec_val *= SMALL_NORMAL_VAL;
|
dec_val *= SMALL_NORMAL_VAL;
|
||||||
|
|
Loading…
Reference in New Issue