From 48ee0f31a18e94fa9d34ad281b6fb13e5b9ffcfa Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Mon, 17 May 2021 17:40:24 -0700 Subject: [PATCH 1/2] GH-1244: Change modp_dtoa2() to use scientific notation for small values This fixes problems where printing floating point numbers less than 10^-6 output as "0.0". Such numbers now use using scientific notation and preserve the value's actual floating point representation. --- src/modp_numtoa.c | 10 +++++++ src/modp_numtoa.h | 4 +++ testing/btest/Baseline/language.double/out | 32 ++++++++++++++++++++ testing/btest/language/double.zeek | 35 ++++++++++++++++++++++ 4 files changed, 81 insertions(+) diff --git a/src/modp_numtoa.c b/src/modp_numtoa.c index d77b68580a..0725e58159 100644 --- a/src/modp_numtoa.c +++ b/src/modp_numtoa.c @@ -23,6 +23,8 @@ */ static const double _pow10[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000}; +static const double _pow10r[] = {1, .1, .01, .001, .0001, .00001, .000001, + .0000001, .00000001, .000000001}; static void strreverse(char* begin, char* end) { @@ -287,6 +289,14 @@ void modp_dtoa2(double value, char* str, int prec) prec = 9; } + double smallest = _pow10r[prec]; + + if (value != 0.0 && value < smallest) { + sprintf(str, "%.*e", DBL_DECIMAL_DIG - 1, neg ? -value : value); + sn_strip_trailing_zeros(str); + return; + } + int whole = (int) value; double tmp = (value - whole) * _pow10[prec]; uint32_t frac = (uint32_t)(tmp); diff --git a/src/modp_numtoa.h b/src/modp_numtoa.h index 619a4ac767..c8c46827c8 100644 --- a/src/modp_numtoa.h +++ b/src/modp_numtoa.h @@ -91,6 +91,10 @@ void modp_dtoa(double value, char* buf, int precision); * will be switched exponential format and include as many precision digits * as needed to preserve information. * + * If a non-zero input value is less than 10^(-precision), the output format + * will be switched exponential format and include as many precision digits + * as needed to preserve information. + * * \param[in] value * \param[out] buf The allocated output buffer. Should be 32 chars or more. * \param[in] precision Number of digits to the right of the decimal point. diff --git a/testing/btest/Baseline/language.double/out b/testing/btest/Baseline/language.double/out index 8ec12b40e1..453fae924f 100644 --- a/testing/btest/Baseline/language.double/out +++ b/testing/btest/Baseline/language.double/out @@ -27,3 +27,35 @@ relational operator (PASS) relational operator (PASS) division operator (PASS) max double value = 1.7976931348623157e+308 (PASS) + +4.9999999999999999e-13 +4.9999999999999997e-12 +5.0000000000000002e-11 +5.0000000000000003e-10 +5.0000000000000001e-09 +4.9999999999999998e-08 +4.9999999999999998e-07 + +0.000005 +0.00005 +0.0005 +0.005 +0.05 +0.5 +5.0 + +1.e-13 +9.9999999999999998e-13 +9.9999999999999994e-12 +1.e-10 +1.0000000000000001e-09 +1.e-08 +9.9999999999999995e-08 + +0.000001 +0.00001 +0.0001 +0.001 +0.01 +0.1 +1.0 diff --git a/testing/btest/language/double.zeek b/testing/btest/language/double.zeek index 56ce711da2..f98abc839a 100644 --- a/testing/btest/language/double.zeek +++ b/testing/btest/language/double.zeek @@ -75,5 +75,40 @@ event zeek_init() local str1 = fmt("max double value = %.16e", d19); test_case( str1, str1 == "max double value = 1.7976931348623157e+308" ); + # Printing small numbers: default precision is 6 with values smaller than + # 10^-6 rendered in scientific notation, preserving exact floating point + # representation. + print ""; + print 0.0000000000005; + print 0.000000000005; + print 0.00000000005; + print 0.0000000005; + print 0.000000005; + print 0.00000005; + print 0.0000005; + print ""; + print 0.000005; + print 0.00005; + print 0.0005; + print 0.005; + print 0.05; + print 0.5; + print 5.0; + print ""; + print 0.0000000000001; + print 0.000000000001; + print 0.00000000001; + print 0.0000000001; + print 0.000000001; + print 0.00000001; + print 0.0000001; + print ""; + print 0.000001; + print 0.00001; + print 0.0001; + print 0.001; + print 0.01; + print 0.1; + print 1.0; } From 8996dfbfef706f39e9d0b884cf83a06f79fdfdd8 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Mon, 17 May 2021 17:56:19 -0700 Subject: [PATCH 2/2] Omit unneeded decimal points in modp_dtoa2() scientific notation output For example, "1e-13" is now used instead of "1.e-13". --- src/modp_numtoa.c | 4 ++++ testing/btest/Baseline/language.double/out | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/modp_numtoa.c b/src/modp_numtoa.c index 0725e58159..f6b7059d8a 100644 --- a/src/modp_numtoa.c +++ b/src/modp_numtoa.c @@ -55,6 +55,7 @@ static void sn_strip_trailing_zeros(char* str) if ( ! frac ) return; + char* start_dec = frac; char* exp = 0; char* trailing_zeros = 0; @@ -80,6 +81,9 @@ static void sn_strip_trailing_zeros(char* str) ++frac; } + if ( trailing_zeros == start_dec ) + --trailing_zeros; + if ( trailing_zeros && exp ) { for ( ; ; ) diff --git a/testing/btest/Baseline/language.double/out b/testing/btest/Baseline/language.double/out index 453fae924f..143e1a7c81 100644 --- a/testing/btest/Baseline/language.double/out +++ b/testing/btest/Baseline/language.double/out @@ -44,12 +44,12 @@ max double value = 1.7976931348623157e+308 (PASS) 0.5 5.0 -1.e-13 +1e-13 9.9999999999999998e-13 9.9999999999999994e-12 -1.e-10 +1e-10 1.0000000000000001e-09 -1.e-08 +1e-08 9.9999999999999995e-08 0.000001