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.
This commit is contained in:
Jon Siwek 2021-05-17 17:40:24 -07:00
parent f66b4f5340
commit 48ee0f31a1
4 changed files with 81 additions and 0 deletions

View file

@ -23,6 +23,8 @@
*/ */
static const double _pow10[] = {1, 10, 100, 1000, 10000, 100000, 1000000, static const double _pow10[] = {1, 10, 100, 1000, 10000, 100000, 1000000,
10000000, 100000000, 1000000000}; 10000000, 100000000, 1000000000};
static const double _pow10r[] = {1, .1, .01, .001, .0001, .00001, .000001,
.0000001, .00000001, .000000001};
static void strreverse(char* begin, char* end) static void strreverse(char* begin, char* end)
{ {
@ -287,6 +289,14 @@ void modp_dtoa2(double value, char* str, int prec)
prec = 9; 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; int whole = (int) value;
double tmp = (value - whole) * _pow10[prec]; double tmp = (value - whole) * _pow10[prec];
uint32_t frac = (uint32_t)(tmp); uint32_t frac = (uint32_t)(tmp);

View file

@ -91,6 +91,10 @@ void modp_dtoa(double value, char* buf, int precision);
* will be switched exponential format and include as many precision digits * will be switched exponential format and include as many precision digits
* as needed to preserve information. * 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[in] value
* \param[out] buf The allocated output buffer. Should be 32 chars or more. * \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. * \param[in] precision Number of digits to the right of the decimal point.

View file

@ -27,3 +27,35 @@ relational operator (PASS)
relational operator (PASS) relational operator (PASS)
division operator (PASS) division operator (PASS)
max double value = 1.7976931348623157e+308 (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

View file

@ -75,5 +75,40 @@ event zeek_init()
local str1 = fmt("max double value = %.16e", d19); local str1 = fmt("max double value = %.16e", d19);
test_case( str1, str1 == "max double value = 1.7976931348623157e+308" ); 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;
} }