From bd668e4c9dc2244aa201addf95acbb8663596423 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Fri, 20 Sep 2024 23:46:31 +0900 Subject: [PATCH] readfloat.c: new implementation of mrb_read_float(); ref #6187 The new version gives more accurate values for decimal number representation that are not divisible in binary representations, for example `0.3`. The function uses `long double` for precision. Please report if `long double` causes problems on any platform (especially microcontrollers). Ref #6182 --- LEGAL | 9 --- src/readfloat.c | 149 ++++++++++++++++++++---------------------------- 2 files changed, 63 insertions(+), 95 deletions(-) diff --git a/LEGAL b/LEGAL index c1384c0ae..9697d5747 100644 --- a/LEGAL +++ b/LEGAL @@ -5,7 +5,6 @@ All the files in this distribution are covered under the MIT license (see the file LICENSE) except some files mentioned below: - src/string.c (memsearch_swar): 2 clause BSD license code by Wojciech Muła (@WojciechMula) -- src/readfloat.c: public domain by Yasuhiro Matsumoto (@mattn) - src/fmt_fp.c: public domain by Dave Hylands (@dhylands) - mrbgems/mruby-dir/src/Win/dirent.c: MIT-like license by Kevlin Henney @@ -39,14 +38,6 @@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -[src/readfloat.c] - -strtod implementation. -author: Yasuhiro Matsumoto (@mattn) -license: public domain - -The original code can be found in https://github.com/mattn/strtod - [src/fmt_fp.c] The code in this function was inspired from Fred Bayer's pdouble.c. diff --git a/src/readfloat.c b/src/readfloat.c index 1c4cf7bc2..7c42efbcd 100644 --- a/src/readfloat.c +++ b/src/readfloat.c @@ -1,25 +1,6 @@ #include #ifndef MRB_NO_FLOAT -/* - * strtod implementation. - * author: Yasuhiro Matsumoto (@mattn) - * license: public domain - */ - -/* -The original code can be found in https://github.com/mattn/strtod - -I modified the routine for mruby: - - * renamed the function `vim_strtod` -> `mrb_read_float` - * simplified the code - * changed the API - -My modifications in this file are also placed in the public domain. - -Matz (Yukihiro Matsumoto) -*/ #include #include @@ -27,91 +8,87 @@ Matz (Yukihiro Matsumoto) MRB_API mrb_bool mrb_read_float(const char *str, char **endp, double *fp) { - double d = 0.0; - int sign; - const char *p, *a; + const char *p = str; + const char *a = p; + long double res = 0.0, frac = 0.0, div = 1.0; + int sign = 1; + int digits = 0; - a = p = str; - while (ISSPACE(*p)) - p++; + // Skip whitespace + while (ISSPACE((unsigned char)*p)) p++; - /* decimal part */ - sign = 1; - if (*p == '-') { - sign = -1; - p++; + // Handle sign + if (*p == '-') { sign = -1.0; p++; } + else if (*p == '+') p++; + + // Parse integer part + while (ISDIGIT(*p)) { + res = res * 10.0 + (*p - '0'); + digits++; + a = ++p; } - else if (*p == '+') - p++; - if (ISDIGIT(*p)) { - d = (double)(*p++ - '0'); - while (*p && ISDIGIT(*p)) { - d = d * 10.0 + (double)(*p - '0'); - p++; - } - a = p; - } - else if (*p != '.') - goto done; - d *= sign; - /* fraction part */ + // Parse fractional part if (*p == '.') { - double f = 0.0; - double base = 0.1; p++; - - if (ISDIGIT(*p)) { - while (*p && ISDIGIT(*p)) { - f += base * (*p - '0'); - base /= 10.0; - p++; - } + while (ISDIGIT(*p)) { + frac = frac * 10.0 + (*p++ - '0'); + div *= 10.0; + digits++; } - d += f * sign; a = p; } - /* exponential part */ - if ((*p == 'E') || (*p == 'e')) { + // If no digits were found, return 0 + if (digits == 0) { + if (endp) *endp = (char*)str; + *fp = 0.0; + return FALSE; + } + + // Combine integer and fractional parts + res += frac / div; + res *= sign; + + // Handle exponent + if ((*p | 32) == 'e') { int e = 0; - - p++; sign = 1; - if (*p == '-') { - sign = -1; - p++; - } - else if (*p == '+') - p++; + p++; + if (*p == '-') { sign = -1; p++; } + else if (*p == '+') p++; - if (ISDIGIT(*p)) { - while (*p == '0') - p++; - if (*p == '\0') --p; - e = (int)(*p++ - '0'); - for (; *p && ISDIGIT(*p); p++) { - if (e < 10000) - e = e * 10 + (*p - '0'); - } - e *= sign; + // If no digits follow 'e', ignore the exponent part + if (!ISDIGIT(*p)) goto done; + + while (ISDIGIT(*p)) { + if (e < 10000) // 10000 is big enough to get Infinity + e = e * 10 + (*p - '0'); + p++; } - else if (!ISDIGIT(*(a-1))) { - return FALSE; - } - else if (*p == 0) - goto done; - d *= pow(10.0, (double)e); + res *= powl(10.0, sign * e); a = p; } - else if (p > str && !ISDIGIT(*(p-1))) { - goto done; + + // Set endp + done: + if (endp) *endp = (char*)a; + *fp = res; + + // strtod(3) stores ERANGE to errno for overflow/underflow + // mruby does not require those checks +#if 0 + // Check for underflow after applying the exponent + if (res != 0.0 && fabsl(res) < (long double)DBL_MIN) { + return FALSE; } -done: - *fp = d; - if (endp) *endp = (char*)a; - if (str == a) return FALSE; + // Check if the result is infinity or NaN + if (isinf(res) || isnan(res)) { + return FALSE; + } +#endif return TRUE; } + #endif