From b75cb8392bdf5f8c12072eac3adbdaad53d1e8d2 Mon Sep 17 00:00:00 2001
From: Damien George <damien.p.george@gmail.com>
Date: Thu, 8 Feb 2018 14:02:50 +1100
Subject: [PATCH] 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.
---
 py/parsenum.c                         | 14 ++++++++++++--
 tests/float/float_parse.py            |  5 +++++
 tests/float/float_parse_doubleprec.py |  5 +++++
 3 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/py/parsenum.c b/py/parsenum.c
index 98e773685..124489c66 100644
--- a/py/parsenum.c
+++ b/py/parsenum.c
@@ -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
diff --git a/tests/float/float_parse.py b/tests/float/float_parse.py
index 448eff3bc..de4ea455f 100644
--- a/tests/float/float_parse.py
+++ b/tests/float/float_parse.py
@@ -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'))
diff --git a/tests/float/float_parse_doubleprec.py b/tests/float/float_parse_doubleprec.py
index 356601130..2ea7842f3 100644
--- a/tests/float/float_parse_doubleprec.py
+++ b/tests/float/float_parse_doubleprec.py
@@ -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'))
-- 
GitLab