summaryrefslogtreecommitdiffstatshomepage
path: root/py
diff options
context:
space:
mode:
authorDan Ellis <dan.ellis@gmail.com>2022-07-28 23:21:00 -0400
committerDamien George <damien@micropython.org>2022-08-12 23:44:11 +1000
commit6cd2e4191803e95580bdfc57c06ea818454a25d1 (patch)
tree20101bd9787df048d364b51a39e48baba99ea684 /py
parent69719927f1752b2d6333708c5b60067fe3b7965d (diff)
downloadmicropython-6cd2e4191803e95580bdfc57c06ea818454a25d1.tar.gz
micropython-6cd2e4191803e95580bdfc57c06ea818454a25d1.zip
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.
Diffstat (limited to 'py')
-rw-r--r--py/parsenum.c70
1 files changed, 49 insertions, 21 deletions
diff --git a/py/parsenum.c b/py/parsenum.c
index 5e8e5dfe8c..19cc719201 100644
--- a/py/parsenum.c
+++ b/py/parsenum.c
@@ -178,31 +178,51 @@ typedef enum {
PARSE_DEC_IN_EXP,
} parse_dec_in_t;
-#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)
-#else
-mp_obj_t mp_parse_num_float(const char *str, size_t len, bool allow_imag, mp_lexer_t *lex)
-#endif
-{
- #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
+#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
+#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
+#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
+mp_obj_t mp_parse_num_decimal(const char *str, size_t len, bool allow_imag, bool force_complex, mp_lexer_t *lex)
+#else
+mp_obj_t mp_parse_num_float(const char *str, size_t len, bool allow_imag, mp_lexer_t *lex)
+#endif
+{
+ #if MICROPY_PY_BUILTINS_FLOAT
const char *top = str + len;
mp_float_t dec_val = 0;
@@ -255,6 +275,7 @@ parse_start:
bool exp_neg = false;
int exp_val = 0;
int exp_extra = 0;
+ int trailing_zeros_intg = 0, trailing_zeros_frac = 0;
while (str < top) {
unsigned int dig = *str++;
if ('0' <= dig && dig <= '9') {
@@ -267,18 +288,25 @@ parse_start:
exp_val = 10 * exp_val + dig;
}
} else {
- if (dec_val < DEC_VAL_MAX) {
- // dec_val won't overflow so keep accumulating
- dec_val = 10 * dec_val + dig;
- if (in == PARSE_DEC_IN_FRAC) {
- --exp_extra;
+ if (dig == 0 || dec_val >= DEC_VAL_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.
+ if (in == PARSE_DEC_IN_INTG) {
+ ++trailing_zeros_intg;
+ } else {
+ ++trailing_zeros_frac;
}
} 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) {
- ++exp_extra;
+ // 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);
+ --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 == '.') {
@@ -311,7 +339,7 @@ parse_start:
}
// 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) {
exp_val -= SMALL_NORMAL_EXP;
dec_val *= SMALL_NORMAL_VAL;