From 1bf4b80f1ace8384eb9dd6f7f8b67256b3944a7a Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 2 Aug 2024 15:09:30 -0300 Subject: Floats formatted with "correct" precision Conversion float->string ensures that, for any float f, tonumber(tostring(f)) == f, but still avoiding noise like 1.1 converting to "1.1000000000000001". --- lobject.c | 52 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 13 deletions(-) (limited to 'lobject.c') diff --git a/lobject.c b/lobject.c index 1c4ea1af..f7159547 100644 --- a/lobject.c +++ b/lobject.c @@ -10,6 +10,7 @@ #include "lprefix.h" +#include #include #include #include @@ -401,29 +402,54 @@ int luaO_utf8esc (char *buff, unsigned long x) { /* ** Maximum length of the conversion of a number to a string. Must be ** enough to accommodate both LUA_INTEGER_FMT and LUA_NUMBER_FMT. -** (For a long long int, this is 19 digits plus a sign and a final '\0', -** adding to 21. For a long double, it can go to a sign, 33 digits, -** the dot, an exponent letter, an exponent sign, 5 exponent digits, -** and a final '\0', adding to 43.) +** For a long long int, this is 19 digits plus a sign and a final '\0', +** adding to 21. For a long double, it can go to a sign, the dot, an +** exponent letter, an exponent sign, 4 exponent digits, the final +** '\0', plus the significant digits, which are approximately the *_DIG +** attribute. */ -#define MAXNUMBER2STR 44 +#define MAXNUMBER2STR (20 + l_floatatt(DIG)) /* -** Convert a number object to a string, adding it to a buffer +** Convert a float to a string, adding it to a buffer. First try with +** a not too large number of digits, to avoid noise (for instance, +** 1.1 going to "1.1000000000000001"). If that lose precision, so +** that reading the result back gives a different number, then do the +** conversion again with extra precision. Moreover, if the numeral looks +** like an integer (without a decimal point or an exponent), add ".0" to +** its end. +*/ +static int tostringbuffFloat (lua_Number n, char *buff) { + /* first conversion */ + int len = l_sprintf(buff, MAXNUMBER2STR, LUA_NUMBER_FMT, + (LUAI_UACNUMBER)n); + lua_Number check = lua_str2number(buff, NULL); /* read it back */ + if (check != n) { /* not enough precision? */ + /* convert again with more precision */ + len = l_sprintf(buff, MAXNUMBER2STR, LUA_NUMBER_FMT_N, + (LUAI_UACNUMBER)n); + } + /* looks like an integer? */ + if (buff[strspn(buff, "-0123456789")] == '\0') { + buff[len++] = lua_getlocaledecpoint(); + buff[len++] = '0'; /* adds '.0' to result */ + } + return len; +} + + +/* +** Convert a number object to a string, adding it to a buffer. */ static unsigned tostringbuff (TValue *obj, char *buff) { int len; lua_assert(ttisnumber(obj)); if (ttisinteger(obj)) len = lua_integer2str(buff, MAXNUMBER2STR, ivalue(obj)); - else { - len = lua_number2str(buff, MAXNUMBER2STR, fltvalue(obj)); - if (buff[strspn(buff, "-0123456789")] == '\0') { /* looks like an int? */ - buff[len++] = lua_getlocaledecpoint(); - buff[len++] = '0'; /* adds '.0' to result */ - } - } + else + len = tostringbuffFloat(fltvalue(obj), buff); + lua_assert(len < MAXNUMBER2STR); return cast_uint(len); } -- cgit v1.2.3-55-g6feb