Skip to content

Commit

Permalink
Stringify Num using Grisu3 algo
Browse files Browse the repository at this point in the history
- Makes Num stringification 2x faster (tested with rand.Str)
- Fixes RT#127184 https://rt.perl.org/Ticket/Display.html?id=127184
- Fixes RT#132330 https://rt.perl.org/Ticket/Display.html?id=132330
  (fixes Num.WHICH and problems with set()s mentioned in that ticket)

Grisu3[^1] is a 2010 algorithm that stringifies doubles to shortest
possible representation that would still result in the same value if
it's parsed and stringified again. This sort of strigification is
what fixes the reported bugs. As a bonus, the algo is faster than
snprintf('%.15g') we used to use for stringification.

Grisu3 handles ~99.5% of cases. In the remaining 0.5%, it knows it won't
produce the shortest possible result and so a fallback to slower
algorithm is made, which in our current case is snprintf('%.17g'),
which is a suboptimal fallback, and in the future Dragon4 algo[^2]
could be used instead.

The change from .15g to .17g in the fallback is intentional, as 15 is
the number of guaranteed significant digits, but 17 is the number of
maximum available significant digits when the IEEE doubles differ by
at most one unit[^7].

Based on my research, an improved fallback would be Dragon4 algo[^2],
and Grisu3+Dragon4 is the current state of the art. (some references
may state that Errol[^8] algo superseded Grisu3+Dragon4 combo, but
based on authors' corrections[^9], it appears there was a
benchmarking error and Errol is not in fact faster).

The Grisu3+Dragon4 is used[^5] by Rust (see also some trial impls
in [^6]) and Grisu3's author's C++ version of the code[^3] indicates
it's used by V8 JavaScript engine as well. There exist a C# impl[^4]
of the algo as well, used in an efficient JSON encoder.

[1] "Printing Floating-Point Numbers Quickly and
    Accurately with Integers" by Florian Loitsch https://goo.gl/cbvogg
[2] "How to Print Floating-Point Numbers Accurately" by Steel & White:
    https://lists.nongnu.org/archive/html/gcl-devel/2012-10/pdfkieTlklRzN.pdf
[3] https://github.com/google/double-conversion
[4] https://github.com/kring/grisu.net
[5] rust-lang/rust#24612
[6] https://github.com/lifthrasiir/rust-strconv
[7] http://www2.open-std.org/JTC1/SC22/WG21/docs/papers/2005/n1822.pdf
[8] https://github.com/marcandrysco/Errol
[9] https://github.com/marcandrysco/Errol#performance-evaluation-correction
  • Loading branch information
zoffixznet committed Mar 24, 2018
1 parent b5b6968 commit 067c059
Show file tree
Hide file tree
Showing 4 changed files with 398 additions and 9 deletions.
2 changes: 2 additions & 0 deletions build/Makefile.in
Expand Up @@ -94,6 +94,7 @@ OBJECTS = src/core/callsite@obj@ \
src/core/hll@obj@ \
src/core/loadbytecode@obj@ \
src/math/num@obj@ \
src/math/grisu@obj@ \
src/core/coerce@obj@ \
src/core/dll@obj@ \
src/core/ext@obj@ \
Expand Down Expand Up @@ -252,6 +253,7 @@ HEADERS = src/moar.h \
src/core/loadbytecode.h \
src/core/bitmap.h \
src/math/num.h \
src/math/grisu.h \
src/core/coerce.h \
src/core/dll.h \
src/core/ext.h \
Expand Down
10 changes: 1 addition & 9 deletions src/core/coerce.c
Expand Up @@ -183,16 +183,8 @@ MVMString * MVM_coerce_n_s(MVMThreadContext *tc, MVMnum64 n) {
else {
char buf[64];
int i;
if (snprintf(buf, 64, "%.15g", n) < 0)
if (dtoa_grisu3(n, buf, 64) < 0)
MVM_exception_throw_adhoc(tc, "Could not stringify number");
if (strstr(buf, ".")) {
MVMint64 is_not_scientific = !strstr(buf, "e");
i = strlen(buf);
while (i > 1 && ((buf[--i] == '0' && is_not_scientific) || buf[i] == ' '))
buf[i] = '\0';
if (buf[i] == '.')
buf[i] = '\0';
}
return MVM_string_ascii_decode(tc, tc->instance->VMString, buf, strlen(buf));
}
}
Expand Down

0 comments on commit 067c059

Please sign in to comment.