Skip to content

Commit bea8fc3

Browse files
authored
Fix JSON dumping of large numbers (#169)
* Append `.0` when dumping doubles, if value is between 2^53 and 2^64. * Add tests.
1 parent 911c5e2 commit bea8fc3

File tree

2 files changed

+126
-10
lines changed

2 files changed

+126
-10
lines changed

src/Dumper.cpp

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,7 @@ void Dumper::dump(Slice slice) {
5454
dumpValue(slice);
5555
}
5656

57-
/*static*/ void Dumper::dump(Slice slice, Sink* sink,
58-
Options const* options) {
57+
/*static*/ void Dumper::dump(Slice slice, Sink* sink, Options const* options) {
5958
Dumper dumper(sink, options);
6059
dumper.dump(slice);
6160
}
@@ -65,8 +64,7 @@ void Dumper::dump(Slice slice) {
6564
dump(*slice, sink, options);
6665
}
6766

68-
/*static*/ std::string Dumper::toString(Slice slice,
69-
Options const* options) {
67+
/*static*/ std::string Dumper::toString(Slice slice, Options const* options) {
7068
std::string buffer;
7169
StringSink sink(&buffer);
7270
dump(slice, &sink, options);
@@ -217,6 +215,32 @@ void Dumper::appendUInt(uint64_t v) {
217215

218216
void Dumper::appendDouble(double v) {
219217
char temp[24];
218+
double a = fabs(v);
219+
if (a >= ldexpl(1.0, 53) && a < ldexpl(1.0, 64)) {
220+
// This is a special case which we want to handle separately, because
221+
// of two reasons:
222+
// (1) The function fpconv_dtoa below only guarantees to write a
223+
// decimal representation which gives the same double value when
224+
// parsed back into a double. It can write a wrong integer.
225+
// Therefore we want to use the integer code in this case.
226+
// (2) The function fpconv_dtoa will write a normal integer
227+
// representation in this case without a decimal point. If we
228+
// parse this back to vpack later, we end up in a different
229+
// representation (uint64_t or int64_t), so we want to append
230+
// ".0" to the string in this case.
231+
// Note that this automatically excludes all infinities and NaNs,
232+
// which will be handled in the function fpconv_dtoa below.
233+
uint64_t u;
234+
if (v < 0) {
235+
u = static_cast<uint64_t>(-v);
236+
_sink->append("-", 1);
237+
} else {
238+
u = static_cast<uint64_t>(v);
239+
}
240+
appendUInt(u);
241+
_sink->append(".0", 2);
242+
return;
243+
}
220244
int len = fpconv_dtoa(v, &temp[0]);
221245
_sink->append(&temp[0], static_cast<ValueLength>(len));
222246
}
@@ -545,8 +569,7 @@ void Dumper::dumpValue(Slice slice, Slice const* base) {
545569
base = &slice;
546570
}
547571

548-
Slice external(
549-
reinterpret_cast<uint8_t const*>(slice.getExternal()));
572+
Slice external(reinterpret_cast<uint8_t const*>(slice.getExternal()));
550573
dumpValue(external, base);
551574
break;
552575
}
@@ -624,8 +647,8 @@ void Dumper::handleUnsupportedType(Slice slice) {
624647
return;
625648
} else if (options->unsupportedTypeBehavior ==
626649
Options::ConvertUnsupportedType) {
627-
_sink->append(std::string("\"(non-representable type ") +
628-
slice.typeName() + ")\"");
650+
_sink->append(std::string("\"(non-representable type ") + slice.typeName() +
651+
")\"");
629652
return;
630653
}
631654

tests/testsDumper.cpp

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,8 +490,9 @@ TEST(StringDumperTest, SuppressControlChars) {
490490

491491
TEST(StringDumperTest, EscapeControlChars) {
492492
Builder b;
493-
b.add(Value(
494-
"Before\nAfter\r\t\v\f\b\x01\x02/\u00B0\uf0f9\u9095\uf0f9\u90b6\v\n\\\""));
493+
b.add(
494+
Value("Before\nAfter\r\t\v\f\b\x01\x02/"
495+
"\u00B0\uf0f9\u9095\uf0f9\u90b6\v\n\\\""));
495496
Options options;
496497
options.escapeControl = true;
497498
ASSERT_EQ(std::string("\"Before\\nAfter\\r\\t\\u000B\\f\\b\\u0001\\u0002/"
@@ -1958,3 +1959,95 @@ int main(int argc, char* argv[]) {
19581959

19591960
return RUN_ALL_TESTS();
19601961
}
1962+
1963+
TEST(DumperLargeDoubleTest, TwoToThePowerOf60Double) {
1964+
Options options;
1965+
std::string buffer;
1966+
StringSink sink(&buffer);
1967+
Dumper dumper(&sink, &options);
1968+
Builder builder;
1969+
builder.add(Value(ldexp(1.0, 60)));
1970+
dumper.dump(builder.slice());
1971+
ASSERT_EQ("1152921504606846976.0", buffer);
1972+
}
1973+
1974+
TEST(DumperLargeDoubleTest, TwoToThePowerOf60Plus1Double) {
1975+
Options options;
1976+
std::string buffer;
1977+
StringSink sink(&buffer);
1978+
Dumper dumper(&sink, &options);
1979+
Builder builder;
1980+
builder.add(Value(ldexp(1.0, 60) + 1.0));
1981+
dumper.dump(builder.slice());
1982+
ASSERT_EQ("1152921504606846976.0", buffer);
1983+
}
1984+
1985+
TEST(DumperLargeDoubleTest, MinusTwoToThePowerOf60Double) {
1986+
Options options;
1987+
std::string buffer;
1988+
StringSink sink(&buffer);
1989+
Dumper dumper(&sink, &options);
1990+
Builder builder;
1991+
builder.add(Value(-ldexp(1.0, 60)));
1992+
dumper.dump(builder.slice());
1993+
ASSERT_EQ("-1152921504606846976.0", buffer);
1994+
}
1995+
1996+
TEST(DumperLargeDoubleTest, MinusTwoToThePowerOf60Plus1Double) {
1997+
Options options;
1998+
std::string buffer;
1999+
StringSink sink(&buffer);
2000+
Dumper dumper(&sink, &options);
2001+
Builder builder;
2002+
builder.add(Value(-ldexp(1.0, 60) + 1.0));
2003+
dumper.dump(builder.slice());
2004+
ASSERT_EQ("-1152921504606846976.0", buffer);
2005+
}
2006+
2007+
TEST(DumperLargeDoubleTest, TwoToThePowerOf52Double) {
2008+
Options options;
2009+
std::string buffer;
2010+
StringSink sink(&buffer);
2011+
Dumper dumper(&sink, &options);
2012+
Builder builder;
2013+
builder.add(Value(ldexp(1.0, 52)));
2014+
dumper.dump(builder.slice());
2015+
ASSERT_EQ("4503599627370496", buffer);
2016+
}
2017+
2018+
TEST(DumperLargeDoubleTest, TwoToThePowerOf53Double) {
2019+
Options options;
2020+
std::string buffer;
2021+
StringSink sink(&buffer);
2022+
Dumper dumper(&sink, &options);
2023+
Builder builder;
2024+
builder.add(Value(ldexp(1.0, 53)));
2025+
dumper.dump(builder.slice());
2026+
ASSERT_EQ("9007199254740992.0", buffer);
2027+
}
2028+
2029+
TEST(DumperLargeDoubleTest, TwoToThePowerOf63Double) {
2030+
Options options;
2031+
std::string buffer;
2032+
StringSink sink(&buffer);
2033+
Dumper dumper(&sink, &options);
2034+
Builder builder;
2035+
builder.add(Value(ldexp(1.0, 63)));
2036+
dumper.dump(builder.slice());
2037+
ASSERT_EQ("9223372036854775808.0", buffer);
2038+
}
2039+
2040+
TEST(DumperLargeDoubleTest, TwoToThePowerOf64Double) {
2041+
Options options;
2042+
std::string buffer;
2043+
StringSink sink(&buffer);
2044+
Dumper dumper(&sink, &options);
2045+
Builder builder;
2046+
builder.add(Value(ldexp(1.0, 64)));
2047+
dumper.dump(builder.slice());
2048+
// Note that this is, strictly speaking, not correct. But since this is
2049+
// at least 2^64, it will be parsed back to double anywa and then we
2050+
// get back the actual result. This is a sacrifice we make for performance.
2051+
ASSERT_EQ("18446744073709552000", buffer);
2052+
}
2053+

0 commit comments

Comments
 (0)