Files
json/tests/src/unit-to_chars.cpp
T
Niels Lohmann bee66ec810 Write doubles with the shortest digits (Zmij)
dump() writes doubles with the conversion of Zmij by Victor Zverovich
(https://github.com/vitaut/zmij, MIT), ported to C++11 in
detail/conversions/zmij.hpp: the shortest decimal in the rounding
interval, the closest one if there are several. Grisu2 does not always
find the shortest digits; about 0.14% of random doubles are now written
differently (0.08% with fewer digits, 0.06% with the closest last
digit); short decimals such as 0.1 or 2555.56 are not affected. float
keeps Grisu2.

The layout of doubles is unchanged, but written differently: the digits
are converted eight at a time (the BCD conversion of Xiang JunBo, as in
Zmij) and stored with one byte swap per eight digits; leading and
trailing zeros are counted from those bytes; and the layouts of
format_buffer() are written with fixed-size moves instead of per-digit
loops and moves of the buffer (to_chars() uses a local buffer if the
caller's is shorter than the 41 bytes this may write).

The powers of ten come from the table for number parsing, adjusted
where it holds them rounded up, and from the compressed tables of Zmij
beyond 10^308. json::dump() gets faster on floats: canada -53%,
numbers -46%, mesh -37%, marine_ik -30%.

Tests: the powers of ten recomputed with a small big-integer; for random
doubles, all powers of two and of ten and their neighbors, and boundary
values: the output reads back as the same value, no decimal with one
digit fewer does, the layout equals that of format_buffer() for the same
digits, and (C++17) the digits equal those of std::to_chars.
The size ratios of canada.json in unit-binary_formats.cpp and one
expectation in unit-to_chars.cpp change with the shorter output.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:02:18 +02:00

771 lines
42 KiB
C++

// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// XXX:
// Only compile these tests if 'float' and 'double' are IEEE-754 single- and
// double-precision numbers, resp.
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
using nlohmann::detail::dtoa_impl::reinterpret_bits;
#include <array>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <limits>
#include <random>
#include <string>
#include <utility>
#include <vector>
#if defined(JSON_HAS_CPP_17)
#include <charconv>
#endif
namespace
{
float make_float(uint32_t sign_bit, uint32_t biased_exponent, uint32_t significand)
{
assert(sign_bit == 0 || sign_bit == 1);
assert(biased_exponent <= 0xFF);
assert(significand <= 0x007FFFFF);
uint32_t bits = 0;
bits |= sign_bit << 31;
bits |= biased_exponent << 23;
bits |= significand;
return reinterpret_bits<float>(bits);
}
// ldexp -- convert f * 2^e to IEEE single precision
float make_float(uint64_t f, int e)
{
constexpr uint64_t kHiddenBit = 0x00800000;
constexpr uint64_t kSignificandMask = 0x007FFFFF;
constexpr int kPhysicalSignificandSize = 23; // Excludes the hidden bit.
constexpr int kExponentBias = 0x7F + kPhysicalSignificandSize;
constexpr int kDenormalExponent = 1 - kExponentBias;
constexpr int kMaxExponent = 0xFF - kExponentBias;
while (f > kHiddenBit + kSignificandMask)
{
f >>= 1;
e++;
}
if (e >= kMaxExponent)
{
return std::numeric_limits<float>::infinity();
}
if (e < kDenormalExponent)
{
return 0.0;
}
while (e > kDenormalExponent && (f & kHiddenBit) == 0)
{
f <<= 1;
e--;
}
const uint64_t biased_exponent = (e == kDenormalExponent && (f & kHiddenBit) == 0)
? 0
: static_cast<uint64_t>(e + kExponentBias);
const uint64_t bits = (f & kSignificandMask) | (biased_exponent << kPhysicalSignificandSize);
return reinterpret_bits<float>(static_cast<uint32_t>(bits));
}
double make_double(uint64_t sign_bit, uint64_t biased_exponent, uint64_t significand)
{
assert(sign_bit == 0 || sign_bit == 1);
assert(biased_exponent <= 0x7FF);
assert(significand <= 0x000FFFFFFFFFFFFF);
uint64_t bits = 0;
bits |= sign_bit << 63;
bits |= biased_exponent << 52;
bits |= significand;
return reinterpret_bits<double>(bits);
}
// ldexp -- convert f * 2^e to IEEE double precision
double make_double(uint64_t f, int e)
{
constexpr uint64_t kHiddenBit = 0x0010000000000000;
constexpr uint64_t kSignificandMask = 0x000FFFFFFFFFFFFF;
constexpr int kPhysicalSignificandSize = 52; // Excludes the hidden bit.
constexpr int kExponentBias = 0x3FF + kPhysicalSignificandSize;
constexpr int kDenormalExponent = 1 - kExponentBias;
constexpr int kMaxExponent = 0x7FF - kExponentBias;
while (f > kHiddenBit + kSignificandMask)
{
f >>= 1;
e++;
}
if (e >= kMaxExponent)
{
return std::numeric_limits<double>::infinity();
}
if (e < kDenormalExponent)
{
return 0.0;
}
while (e > kDenormalExponent && (f & kHiddenBit) == 0)
{
f <<= 1;
e--;
}
const uint64_t biased_exponent = (e == kDenormalExponent && (f & kHiddenBit) == 0)
? 0
: static_cast<uint64_t>(e + kExponentBias);
const uint64_t bits = (f & kSignificandMask) | (biased_exponent << kPhysicalSignificandSize);
return reinterpret_bits<double>(bits);
}
} // namespace
TEST_CASE("digit gen")
{
SECTION("single precision")
{
auto check_float = [](float number, const std::string & digits, int expected_exponent)
{
CAPTURE(number)
CAPTURE(digits)
CAPTURE(expected_exponent)
std::array<char, 32> buf{};
int len = 0;
int exponent = 0;
nlohmann::detail::dtoa_impl::grisu2(buf.data(), len, exponent, number);
CHECK(digits == std::string(buf.data(), buf.data() + len));
CHECK(expected_exponent == exponent);
};
check_float(make_float(0, 0, 0x00000001), "1", -45); // min denormal
check_float(make_float(0, 0, 0x007FFFFF), "11754942", -45); // max denormal
check_float(make_float(0, 1, 0x00000000), "11754944", -45); // min normal
check_float(make_float(0, 1, 0x00000001), "11754945", -45);
check_float(make_float(0, 1, 0x007FFFFF), "23509886", -45);
check_float(make_float(0, 2, 0x00000000), "23509887", -45);
check_float(make_float(0, 2, 0x00000001), "2350989", -44);
check_float(make_float(0, 24, 0x00000000), "98607613", -39); // fail if no special case in normalized boundaries
check_float(make_float(0, 30, 0x00000000), "63108872", -37); // fail if no special case in normalized boundaries
check_float(make_float(0, 31, 0x00000000), "12621775", -36); // fail if no special case in normalized boundaries
check_float(make_float(0, 57, 0x00000000), "84703295", -29); // fail if no special case in normalized boundaries
check_float(make_float(0, 254, 0x007FFFFE), "34028233", 31);
check_float(make_float(0, 254, 0x007FFFFF), "34028235", 31); // max normal
// V. Paxson and W. Kahan, "A Program for Testing IEEE Binary-Decimal Conversion", manuscript, May 1991,
// ftp://ftp.ee.lbl.gov/testbase-report.ps.Z (report)
// ftp://ftp.ee.lbl.gov/testbase.tar.Z (program)
// Table 16: Stress Inputs for Converting 24-bit Binary to Decimal, < 1/2 ULP
check_float(make_float(12676506, -102), "25", -25);
check_float(make_float(12676506, -103), "125", -26);
check_float(make_float(15445013, 86), "1195", 30);
check_float(make_float(13734123, -138), "39415", -39);
check_float(make_float(12428269, -130), "913085", -38);
check_float(make_float(15334037, -146), "1719005", -43);
check_float(make_float(11518287, -41), "52379105", -13);
check_float(make_float(12584953, -145), "2821644", -43);
check_float(make_float(15961084, -125), "37524328", -38);
check_float(make_float(14915817, -146), "16721209", -44);
check_float(make_float(10845484, -102), "21388946", -31);
check_float(make_float(16431059, -61), "7125836", -18);
// Table 17: Stress Inputs for Converting 24-bit Binary to Decimal, > 1/2 ULP
check_float(make_float(16093626, 69), "95", 26);
check_float(make_float( 9983778, 25), "335", 12);
check_float(make_float(12745034, 104), "2585", 35);
check_float(make_float(12706553, 72), "60005", 24);
check_float(make_float(11005028, 45), "387205", 15);
check_float(make_float(15059547, 71), "3555835", 22);
check_float(make_float(16015691, -99), "25268305", -30);
check_float(make_float( 8667859, 56), "6245851", 17);
check_float(make_float(14855922, -82), "30721327", -25);
check_float(make_float(14855922, -83), "15360663", -25);
check_float(make_float(10144164, -110), "781478", -32);
check_float(make_float(13248074, 95), "52481028", 28);
}
SECTION("double precision")
{
auto check_double = [](double number, const std::string & digits, int expected_exponent)
{
CAPTURE(number)
CAPTURE(digits)
CAPTURE(expected_exponent)
std::array<char, 32> buf{};
int len = 0;
int exponent = 0;
nlohmann::detail::dtoa_impl::grisu2(buf.data(), len, exponent, number);
CHECK(digits == std::string(buf.data(), buf.data() + len));
CHECK(expected_exponent == exponent);
};
check_double(make_double(0, 0, 0x0000000000000001), "5", -324); // min denormal
check_double(make_double(0, 0, 0x000FFFFFFFFFFFFF), "2225073858507201", -323); // max denormal
check_double(make_double(0, 1, 0x0000000000000000), "22250738585072014", -324); // min normal
check_double(make_double(0, 1, 0x0000000000000001), "2225073858507202", -323);
check_double(make_double(0, 1, 0x000FFFFFFFFFFFFF), "44501477170144023", -324);
check_double(make_double(0, 2, 0x0000000000000000), "4450147717014403", -323);
check_double(make_double(0, 2, 0x0000000000000001), "4450147717014404", -323);
check_double(make_double(0, 4, 0x0000000000000000), "17800590868057611", -323); // fail if no special case in normalized boundaries
check_double(make_double(0, 5, 0x0000000000000000), "35601181736115222", -323); // fail if no special case in normalized boundaries
check_double(make_double(0, 6, 0x0000000000000000), "7120236347223045", -322); // fail if no special case in normalized boundaries
check_double(make_double(0, 10, 0x0000000000000000), "11392378155556871", -321); // fail if no special case in normalized boundaries
check_double(make_double(0, 2046, 0x000FFFFFFFFFFFFE), "17976931348623155", 292);
check_double(make_double(0, 2046, 0x000FFFFFFFFFFFFF), "17976931348623157", 292); // max normal
// Test different paths in DigitGen
check_double( 10000, "1", 4);
check_double( 1200000, "12", 5);
check_double(4.9406564584124654e-324, "5", -324); // exit integral loop
check_double(2.2250738585072009e-308, "2225073858507201", -323); // exit fractional loop
check_double( 1.82877982605164e-99, "182877982605164", -113);
check_double( 1.1505466208671903e-09, "11505466208671903", -25);
check_double( 5.5645893133766722e+20, "5564589313376672", 5);
check_double( 53.034830388866226, "53034830388866226", -15);
check_double( 0.0021066531670178605, "21066531670178605", -19);
// V. Paxson and W. Kahan, "A Program for Testing IEEE Binary-Decimal Conversion", manuscript, May 1991,
// ftp://ftp.ee.lbl.gov/testbase-report.ps.Z (report)
// ftp://ftp.ee.lbl.gov/testbase.tar.Z (program)
// Table 3: Stress Inputs for Converting 53-bit Binary to Decimal, < 1/2 ULP
check_double(make_double(8511030020275656, -342) /* 9.5e-088 */, "95", -89);
check_double(make_double(5201988407066741, -824) /* 4.65e-233 */, "465", -235);
check_double(make_double(6406892948269899, +237) /* 1.415e+087 */, "1415", 84);
check_double(make_double(8431154198732492, +72) /* 3.9815e+037 */, "39815", 33);
check_double(make_double(6475049196144587, +99) /* 4.10405e+045 */, "410405", 40);
check_double(make_double(8274307542972842, +726) /* 2.920845e+234 */, "2920845", 228);
check_double(make_double(5381065484265332, -456) /* 2.8919465e-122 */, "28919465", -129);
check_double(make_double(6761728585499734, -1057) /* 4.37877185e-303 */, "437877185", -311);
check_double(make_double(7976538478610756, +376) /* 1.227701635e+129 */, "1227701635", 120);
check_double(make_double(5982403858958067, +377) /* 1.8415524525e+129 */, "18415524525", 119);
check_double(make_double(5536995190630837, +93) /* 5.48357443505e+043 */, "548357443505", 32);
check_double(make_double(7225450889282194, +710) /* 3.891901811465e+229 */, "3891901811465", 217);
check_double(make_double(7225450889282194, +709) /* 1.9459509057325e+229 */, "19459509057325", 216);
check_double(make_double(8703372741147379, +117) /* 1.44609583816055e+051 */, "144609583816055", 37);
check_double(make_double(8944262675275217, -1001) /* 4.173677474585315e-286 */, "4173677474585315", -301);
check_double(make_double(7459803696087692, -707) /* 1.1079507728788885e-197 */, "11079507728788885", -213);
check_double(make_double(6080469016670379, -381) /* 1.234550136632744e-099 */, "1234550136632744", -114);
check_double(make_double(8385515147034757, +721) /* 9.2503171196036502e+232 */, "925031711960365", 218);
check_double(make_double(7514216811389786, -828) /* 4.1980471502848898e-234 */, "419804715028489", -248);
check_double(make_double(8397297803260511, -345) /* 1.1716315319786511e-088 */, "11716315319786511", -104);
check_double(make_double(6733459239310543, +202) /* 4.3281007284461249e+076 */, "4328100728446125", 61);
check_double(make_double(8091450587292794, -473) /* 3.3177101181600311e-127 */, "3317710118160031", -142);
// Table 4: Stress Inputs for Converting 53-bit Binary to Decimal, > 1/2 ULP
check_double(make_double(6567258882077402, +952) /* 2.5e+302 */, "25", 301);
check_double(make_double(6712731423444934, +535) /* 7.55e+176 */, "755", 174);
check_double(make_double(6712731423444934, +534) /* 3.775e+176 */, "3775", 173);
check_double(make_double(5298405411573037, -957) /* 4.3495e-273 */, "43495", -277);
check_double(make_double(5137311167659507, -144) /* 2.30365e-028 */, "230365", -33);
check_double(make_double(6722280709661868, +363) /* 1.263005e+125 */, "1263005", 119);
check_double(make_double(5344436398034927, -169) /* 7.1422105e-036 */, "71422105", -43);
check_double(make_double(8369123604277281, -853) /* 1.39345735e-241 */, "139345735", -249);
check_double(make_double(8995822108487663, -780) /* 1.414634485e-219 */, "1414634485", -228);
check_double(make_double(8942832835564782, -383) /* 4.5392779195e-100 */, "45392779195", -110);
check_double(make_double(8942832835564782, -384) /* 2.26963895975e-100 */, "226963895975", -111);
check_double(make_double(8942832835564782, -385) /* 1.134819479875e-100 */, "1134819479875", -112);
check_double(make_double(6965949469487146, -249) /* 7.7003665618895e-060 */, "77003665618895", -73);
check_double(make_double(6965949469487146, -250) /* 3.85018328094475e-060 */, "385018328094475", -74);
check_double(make_double(6965949469487146, -251) /* 1.925091640472375e-060 */, "1925091640472375", -75);
check_double(make_double(7487252720986826, +548) /* 6.8985865317742005e+180 */, "68985865317742005", 164);
check_double(make_double(5592117679628511, +164) /* 1.3076622631878654e+065 */, "13076622631878654", 49);
check_double(make_double(8887055249355788, +665) /* 1.3605202075612124e+216 */, "13605202075612124", 200);
check_double(make_double(6994187472632449, +690) /* 3.5928102174759597e+223 */, "35928102174759597", 207);
check_double(make_double(8797576579012143, +588) /* 8.9125197712484552e+192 */, "8912519771248455", 177);
check_double(make_double(7363326733505337, +272) /* 5.5876975736230114e+097 */, "55876975736230114", 81);
check_double(make_double(8549497411294502, -448) /* 1.1762578307285404e-119 */, "11762578307285404", -135);
// Table 20: Stress Inputs for Converting 56-bit Binary to Decimal, < 1/2 ULP
check_double(make_double(50883641005312716, -172) /* 8.4999999999999993e-036 */, "8499999999999999", -51);
check_double(make_double(38162730753984537, -170) /* 2.5499999999999999e-035 */, "255", -37);
check_double(make_double(50832789069151999, -101) /* 2.0049999999999997e-014 */, "20049999999999997", -30);
check_double(make_double(51822367833714164, -109) /* 7.9844999999999994e-017 */, "7984499999999999", -32);
check_double(make_double(66840152193508133, -172) /* 1.1165499999999999e-035 */, "11165499999999999", -51);
check_double(make_double(55111239245584393, -138) /* 1.581615e-025 */, "1581615", -31);
check_double(make_double(71704866733321482, -112) /* 1.3809855e-017 */, "13809855", -24);
check_double(make_double(67160949328233173, -142) /* 1.2046404499999999e-026 */, "12046404499999999", -42);
check_double(make_double(53237141308040189, -152) /* 9.3251405449999991e-030 */, "9325140544999999", -45);
check_double(make_double(62785329394975786, -112) /* 1.2092014595e-017 */, "12092014595", -27);
check_double(make_double(48367680154689523, -77) /* 3.2007045838499998e-007 */, "320070458385", -18);
check_double(make_double(42552223180606797, -102) /* 8.391946324354999e-015 */, "8391946324354999", -30);
check_double(make_double(63626356173011241, -112) /* 1.2253990460585e-017 */, "12253990460585", -30);
check_double(make_double(43566388595783643, -99) /* 6.8735641489760495e-014 */, "687356414897605", -28);
check_double(make_double(54512669636675272, -159) /* 7.459816430480385e-032 */, "7459816430480385", -47);
check_double(make_double(52306490527514614, -167) /* 2.7960588398142552e-034 */, "2796058839814255", -49);
check_double(make_double(52306490527514614, -168) /* 1.3980294199071276e-034 */, "13980294199071276", -50);
check_double(make_double(41024721590449423, -89) /* 6.6279012373057359e-011 */, "6627901237305736", -26);
check_double(make_double(37664020415894738, -132) /* 6.9177880043968072e-024 */, "6917788004396807", -39);
check_double(make_double(37549883692866294, -93) /* 3.7915693108349708e-012 */, "3791569310834971", -27);
check_double(make_double(69124110374399839, -104) /* 3.4080817676591365e-015 */, "34080817676591365", -31);
check_double(make_double(69124110374399839, -105) /* 1.7040408838295683e-015 */, "17040408838295683", -31);
// Table 21: Stress Inputs for Converting 56-bit Binary to Decimal, > 1/2 ULP
check_double(make_double(49517601571415211, -94) /* 2.4999999999999998e-012 */, "25", -13);
check_double(make_double(49517601571415211, -95) /* 1.2499999999999999e-012 */, "125", -14);
check_double(make_double(54390733528642804, -133) /* 4.9949999999999996e-024 */, "49949999999999996", -40); // shortest: 4995e-27
check_double(make_double(71805402319113924, -157) /* 3.9304999999999998e-031 */, "39304999999999998", -47); // shortest: 39305e-35
check_double(make_double(40435277969631694, -179) /* 5.2770499999999992e-038 */, "5277049999999999", -53);
check_double(make_double(57241991568619049, -165) /* 1.223955e-033 */, "1223955", -39);
check_double(make_double(65224162876242886, +58) /* 1.8799584999999998e+034 */, "18799584999999998", 18);
check_double(make_double(70173376848895368, -138) /* 2.01387715e-025 */, "201387715", -33);
check_double(make_double(37072848117383207, -99) /* 5.8490641049999989e-014 */, "5849064104999999", -29);
check_double(make_double(56845051585389697, -176) /* 5.9349003054999999e-037 */, "59349003055", -47);
check_double(make_double(54791673366936431, -145) /* 1.2284718039499998e-027 */, "12284718039499998", -43);
check_double(make_double(66800318669106231, -169) /* 8.9270767180849991e-035 */, "8927076718084999", -50);
check_double(make_double(66800318669106231, -170) /* 4.4635383590424995e-035 */, "44635383590424995", -51);
check_double(make_double(66574323440112438, -119) /* 1.0016990862549499e-019 */, "10016990862549499", -35);
check_double(make_double(65645179969330963, -173) /* 5.4829412628024647e-036 */, "5482941262802465", -51);
check_double(make_double(61847254334681076, -109) /* 9.5290783281036439e-017 */, "9529078328103644", -32);
check_double(make_double(39990712921393606, -145) /* 8.9662279366405553e-028 */, "8966227936640555", -43);
check_double(make_double(59292318184400283, -149) /* 8.3086234418058538e-029 */, "8308623441805854", -44);
check_double(make_double(69116558615326153, -143) /* 6.1985873566126555e-027 */, "61985873566126555", -43);
check_double(make_double(69116558615326153, -144) /* 3.0992936783063277e-027 */, "30992936783063277", -43);
check_double(make_double(39462549494468513, -152) /* 6.9123512506176015e-030 */, "6912351250617602", -45);
check_double(make_double(39462549494468513, -153) /* 3.4561756253088008e-030 */, "3456175625308801", -45);
}
}
TEST_CASE("formatting")
{
SECTION("single precision")
{
auto check_float = [](float number, const std::string & expected)
{
std::array<char, 33> buf{};
char* end = nlohmann::detail::to_chars(buf.data(), buf.data() + 32, number); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
const std::string actual(buf.data(), end);
CHECK(actual == expected);
};
// %.9g
check_float( -1.2345e-22f, "-1.2345e-22" ); // -1.23450004e-22
check_float( -1.2345e-21f, "-1.2345e-21" ); // -1.23450002e-21
check_float( -1.2345e-20f, "-1.2345e-20" ); // -1.23450002e-20
check_float( -1.2345e-19f, "-1.2345e-19" ); // -1.23449999e-19
check_float( -1.2345e-18f, "-1.2345e-18" ); // -1.23449996e-18
check_float( -1.2345e-17f, "-1.2345e-17" ); // -1.23449998e-17
check_float( -1.2345e-16f, "-1.2345e-16" ); // -1.23449996e-16
check_float( -1.2345e-15f, "-1.2345e-15" ); // -1.23450002e-15
check_float( -1.2345e-14f, "-1.2345e-14" ); // -1.23450004e-14
check_float( -1.2345e-13f, "-1.2345e-13" ); // -1.23449997e-13
check_float( -1.2345e-12f, "-1.2345e-12" ); // -1.23450002e-12
check_float( -1.2345e-11f, "-1.2345e-11" ); // -1.2345e-11
check_float( -1.2345e-10f, "-1.2345e-10" ); // -1.2345e-10
check_float( -1.2345e-9f, "-1.2345e-09" ); // -1.23449995e-09
check_float( -1.2345e-8f, "-1.2345e-08" ); // -1.23449997e-08
check_float( -1.2345e-7f, "-1.2345e-07" ); // -1.23449993e-07
check_float( -1.2345e-6f, "-1.2345e-06" ); // -1.23450002e-06
check_float( -1.2345e-5f, "-1.2345e-05" ); // -1.2345e-05
check_float( -1.2345e-4f, "-0.00012345" ); // -0.000123449994
check_float( -1.2345e-3f, "-0.0012345" ); // -0.00123449997
check_float( -1.2345e-2f, "-0.012345" ); // -0.0123450002
check_float( -1.2345e-1f, "-0.12345" ); // -0.123450004
check_float( -0.0f, "-0.0" ); // -0
check_float( 0.0f, "0.0" ); // 0
check_float( 1.2345e+0f, "1.2345" ); // 1.23450005
check_float( 1.2345e+1f, "12.345" ); // 12.3450003
check_float( 1.2345e+2f, "123.45" ); // 123.449997
check_float( 1.2345e+3f, "1234.5" ); // 1234.5
check_float( 1.2345e+4f, "12345.0" ); // 12345
check_float( 1.2345e+5f, "123450.0" ); // 123450
check_float( 1.2345e+6f, "1.2345e+06" ); // 1234500
check_float( 1.2345e+7f, "1.2345e+07" ); // 12345000
check_float( 1.2345e+8f, "1.2345e+08" ); // 123450000
check_float( 1.2345e+9f, "1.2345e+09" ); // 1.23449997e+09
check_float( 1.2345e+10f, "1.2345e+10" ); // 1.23449999e+10
check_float( 1.2345e+11f, "1.2345e+11" ); // 1.23449999e+11
check_float( 1.2345e+12f, "1.2345e+12" ); // 1.23450006e+12
check_float( 1.2345e+13f, "1.2345e+13" ); // 1.23449995e+13
check_float( 1.2345e+14f, "1.2345e+14" ); // 1.23450002e+14
check_float( 1.2345e+15f, "1.2345e+15" ); // 1.23450003e+15
check_float( 1.2345e+16f, "1.2345e+16" ); // 1.23449998e+16
check_float( 1.2345e+17f, "1.2345e+17" ); // 1.23449996e+17
check_float( 1.2345e+18f, "1.2345e+18" ); // 1.23450004e+18
check_float( 1.2345e+19f, "1.2345e+19" ); // 1.23449999e+19
check_float( 1.2345e+20f, "1.2345e+20" ); // 1.23449999e+20
check_float( 1.2345e+21f, "1.2345e+21" ); // 1.23449999e+21
check_float( 1.2345e+22f, "1.2345e+22" ); // 1.23450005e+22
}
SECTION("double precision")
{
auto check_double = [](double number, const std::string & expected)
{
std::array<char, 33> buf{};
char* end = nlohmann::detail::to_chars(buf.data(), buf.data() + 32, number); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
const std::string actual(buf.data(), end);
CHECK(actual == expected);
};
// dtoa %.15g %.17g shortest
check_double( -1.2345e-22, "-1.2345e-22" ); // -1.2345e-22 -1.2345000000000001e-22 -1.2345e-22
check_double( -1.2345e-21, "-1.2345e-21" ); // -1.2345e-21 -1.2345000000000001e-21 -1.2345e-21
check_double( -1.2345e-20, "-1.2345e-20" ); // -1.2345e-20 -1.2345e-20 -1.2345e-20
check_double( -1.2345e-19, "-1.2345e-19" ); // -1.2345e-19 -1.2345000000000001e-19 -1.2345e-19
check_double( -1.2345e-18, "-1.2345e-18" ); // -1.2345e-18 -1.2345000000000001e-18 -1.2345e-18
check_double( -1.2345e-17, "-1.2345e-17" ); // -1.2345e-17 -1.2345e-17 -1.2345e-17
check_double( -1.2345e-16, "-1.2345e-16" ); // -1.2345e-16 -1.2344999999999999e-16 -1.2345e-16
check_double( -1.2345e-15, "-1.2345e-15" ); // -1.2345e-15 -1.2345e-15 -1.2345e-15
check_double( -1.2345e-14, "-1.2345e-14" ); // -1.2345e-14 -1.2345e-14 -1.2345e-14
check_double( -1.2345e-13, "-1.2345e-13" ); // -1.2345e-13 -1.2344999999999999e-13 -1.2345e-13
check_double( -1.2345e-12, "-1.2345e-12" ); // -1.2345e-12 -1.2345e-12 -1.2345e-12
check_double( -1.2345e-11, "-1.2345e-11" ); // -1.2345e-11 -1.2345e-11 -1.2345e-11
check_double( -1.2345e-10, "-1.2345e-10" ); // -1.2345e-10 -1.2345e-10 -1.2345e-10
check_double( -1.2345e-9, "-1.2345e-09" ); // -1.2345e-09 -1.2345e-09 -1.2345e-9
check_double( -1.2345e-8, "-1.2345e-08" ); // -1.2345e-08 -1.2345000000000001e-08 -1.2345e-8
check_double( -1.2345e-7, "-1.2345e-07" ); // -1.2345e-07 -1.2345000000000001e-07 -1.2345e-7
check_double( -1.2345e-6, "-1.2345e-06" ); // -1.2345e-06 -1.2345e-06 -1.2345e-6
check_double( -1.2345e-5, "-1.2345e-05" ); // -1.2345e-05 -1.2345e-05 -1.2345e-5
check_double( -1.2345e-4, "-0.00012345" ); // -0.00012345 -0.00012344999999999999 -0.00012345
check_double( -1.2345e-3, "-0.0012345" ); // -0.0012345 -0.0012344999999999999 -0.0012345
check_double( -1.2345e-2, "-0.012345" ); // -0.012345 -0.012345 -0.012345
check_double( -1.2345e-1, "-0.12345" ); // -0.12345 -0.12345 -0.12345
check_double( -0.0, "-0.0" ); // -0 -0 -0
check_double( 0.0, "0.0" ); // 0 0 0
check_double( 1.2345e+0, "1.2345" ); // 1.2345 1.2344999999999999 1.2345
check_double( 1.2345e+1, "12.345" ); // 12.345 12.345000000000001 12.345
check_double( 1.2345e+2, "123.45" ); // 123.45 123.45 123.45
check_double( 1.2345e+3, "1234.5" ); // 1234.5 1234.5 1234.5
check_double( 1.2345e+4, "12345.0" ); // 12345 12345 12345
check_double( 1.2345e+5, "123450.0" ); // 123450 123450 123450
check_double( 1.2345e+6, "1234500.0" ); // 1234500 1234500 1234500
check_double( 1.2345e+7, "12345000.0" ); // 12345000 12345000 12345000
check_double( 1.2345e+8, "123450000.0" ); // 123450000 123450000 123450000
check_double( 1.2345e+9, "1234500000.0" ); // 1234500000 1234500000 1234500000
check_double( 1.2345e+10, "12345000000.0" ); // 12345000000 12345000000 12345000000
check_double( 1.2345e+11, "123450000000.0" ); // 123450000000 123450000000 123450000000
check_double( 1.2345e+12, "1234500000000.0" ); // 1234500000000 1234500000000 1234500000000
check_double( 1.2345e+13, "12345000000000.0" ); // 12345000000000 12345000000000 12345000000000
check_double( 1.2345e+14, "123450000000000.0" ); // 123450000000000 123450000000000 123450000000000
check_double( 1.2345e+15, "1.2345e+15" ); // 1.2345e+15 1234500000000000 1.2345e15
check_double( 1.2345e+16, "1.2345e+16" ); // 1.2345e+16 12345000000000000 1.2345e16
check_double( 1.2345e+17, "1.2345e+17" ); // 1.2345e+17 1.2345e+17 1.2345e17
check_double( 1.2345e+18, "1.2345e+18" ); // 1.2345e+18 1.2345e+18 1.2345e18
check_double( 1.2345e+19, "1.2345e+19" ); // 1.2345e+19 1.2345e+19 1.2345e19
check_double( 1.2345e+20, "1.2345e+20" ); // 1.2345e+20 1.2345e+20 1.2345e20
check_double( 1.2345e+21, "1.2345e+21" ); // 1.2345e+21 1.2344999999999999e+21 1.2345e21
check_double( 1.2345e+22, "1.2345e+22" ); // 1.2345e+22 1.2345e+22 1.2345e22
}
SECTION("integer")
{
auto check_integer = [](std::int64_t number, const std::string & expected)
{
const nlohmann::json j = number;
CHECK(j.dump() == expected);
};
// edge cases
check_integer(INT64_MIN, "-9223372036854775808");
check_integer(INT64_MAX, "9223372036854775807");
// few random big integers
check_integer(-3456789012345678901LL, "-3456789012345678901");
check_integer(3456789012345678901LL, "3456789012345678901");
check_integer(-5678901234567890123LL, "-5678901234567890123");
check_integer(5678901234567890123LL, "5678901234567890123");
// integers with various digit counts
check_integer(-1000000000000000000LL, "-1000000000000000000");
check_integer(-100000000000000000LL, "-100000000000000000");
check_integer(-10000000000000000LL, "-10000000000000000");
check_integer(-1000000000000000LL, "-1000000000000000");
check_integer(-100000000000000LL, "-100000000000000");
check_integer(-10000000000000LL, "-10000000000000");
check_integer(-1000000000000LL, "-1000000000000");
check_integer(-100000000000LL, "-100000000000");
check_integer(-10000000000LL, "-10000000000");
check_integer(-1000000000LL, "-1000000000");
check_integer(-100000000LL, "-100000000");
check_integer(-10000000LL, "-10000000");
check_integer(-1000000LL, "-1000000");
check_integer(-100000LL, "-100000");
check_integer(-10000LL, "-10000");
check_integer(-1000LL, "-1000");
check_integer(-100LL, "-100");
check_integer(-10LL, "-10");
check_integer(-1LL, "-1");
check_integer(0, "0");
check_integer(1LL, "1");
check_integer(10LL, "10");
check_integer(100LL, "100");
check_integer(1000LL, "1000");
check_integer(10000LL, "10000");
check_integer(100000LL, "100000");
check_integer(1000000LL, "1000000");
check_integer(10000000LL, "10000000");
check_integer(100000000LL, "100000000");
check_integer(1000000000LL, "1000000000");
check_integer(10000000000LL, "10000000000");
check_integer(100000000000LL, "100000000000");
check_integer(1000000000000LL, "1000000000000");
check_integer(10000000000000LL, "10000000000000");
check_integer(100000000000000LL, "100000000000000");
check_integer(1000000000000000LL, "1000000000000000");
check_integer(10000000000000000LL, "10000000000000000");
check_integer(100000000000000000LL, "100000000000000000");
check_integer(1000000000000000000LL, "1000000000000000000");
}
}
namespace
{
// a small unsigned big integer (32-bit limbs, least significant first), to
// recompute the powers of ten of the shortest double conversion
using big = std::vector<std::uint32_t>;
void big_mul_small(big& x, std::uint32_t m)
{
std::uint64_t carry = 0;
for (auto& limb : x)
{
const std::uint64_t v = (static_cast<std::uint64_t>(limb) * m) + carry;
limb = static_cast<std::uint32_t>(v);
carry = v >> 32u;
}
if (carry != 0)
{
x.push_back(static_cast<std::uint32_t>(carry));
}
}
void big_div_small(big& x, std::uint32_t d)
{
std::uint64_t rest = 0;
for (std::size_t i = x.size(); i-- > 0;)
{
const std::uint64_t v = (rest << 32u) | x[i];
x[i] = static_cast<std::uint32_t>(v / d);
rest = v % d;
}
while (!x.empty() && x.back() == 0)
{
x.pop_back();
}
}
std::size_t big_bit_length(const big& x)
{
std::size_t n = 32 * x.size();
for (std::uint32_t top = x.back(); (top & 0x80000000u) == 0; top <<= 1u)
{
--n;
}
return n;
}
bool big_bit(const big& x, std::size_t i)
{
return ((x[i / 32] >> (i % 32)) & 1u) != 0;
}
/// the 128 most significant bits of x (floor), shifted left if x has fewer bits
std::pair<std::uint64_t, std::uint64_t> big_top128(const big& x)
{
const std::size_t n = big_bit_length(x);
std::uint64_t high = 0;
std::uint64_t low = 0;
for (std::size_t k = 0; k < 128; ++k)
{
const bool bit = k < n && big_bit(x, n - 1 - k);
if (k < 64)
{
high = (high << 1u) | (bit ? 1u : 0u);
}
else
{
low = (low << 1u) | (bit ? 1u : 0u);
}
}
return {high, low};
}
/// the digits (without trailing zeros) and the decimal exponent of a
/// representation "[-]d[.ddd][e[+-]x]"
std::pair<std::string, int> digits_and_exponent(const std::string& s)
{
std::string digits;
int point = -1;
int exponent = 0;
for (std::size_t i = 0; i < s.size(); ++i)
{
const char c = s[i];
if (c >= '0' && c <= '9')
{
digits += c;
}
else if (c == '.')
{
point = static_cast<int>(digits.size());
}
else if (c == 'e' || c == 'E')
{
exponent = std::stoi(s.substr(i + 1));
break;
}
}
int e = exponent + (point < 0 ? static_cast<int>(digits.size()) : point) - static_cast<int>(digits.size());
const std::size_t first = digits.find_first_not_of('0');
digits = first == std::string::npos ? "0" : digits.substr(first);
while (digits.size() > 1 && digits.back() == '0')
{
digits.pop_back();
++e;
}
return {digits, e};
}
/// whether the decimal digits * 10^e reads back as v
bool reads_back(const std::string& digits, int e, double v)
{
const std::string text = digits + "e" + std::to_string(e);
return std::strtod(text.c_str(), nullptr) == v;
}
/// Check the representation of a positive finite double: it reads back as
/// the same value, and no representation with fewer digits does.
void check_shortest(double v)
{
std::array<char, 33> buf{};
char* end = nlohmann::detail::to_chars(buf.data(), buf.data() + 32, v);
const std::string text(buf.data(), end);
CAPTURE(text);
CHECK(std::strtod(text.c_str(), nullptr) == v);
// the layout is that of format_buffer() for the same digits
std::array<char, 64> reference{};
int len = 0;
int exponent = 0;
nlohmann::detail::dtoa_impl::shortest_digits(reference.data(), len, exponent, v);
const char* const reference_end = nlohmann::detail::dtoa_impl::format_buffer(reference.data(), len, exponent, -4, 15);
CHECK(text == std::string(reference.data(), static_cast<std::size_t>(reference_end - reference.data())));
const auto de = digits_and_exponent(text);
const std::string& digits = de.first;
if (digits.size() > 1)
{
// the decimals of one digit fewer next to the value
std::array<char, 64> shorter{};
const int n = std::snprintf(shorter.data(), shorter.size(), "%.*e", static_cast<int>(digits.size()) - 2, v); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
const auto near = digits_and_exponent(std::string(shorter.data(), static_cast<std::size_t>(n)));
// as an integer with digits.size() - 1 digits
std::string m = near.first;
int e = near.second;
while (m.size() < digits.size() - 1)
{
m += '0';
--e;
}
const std::uint64_t mid = std::stoull(m);
for (const std::uint64_t candidate :
{
mid - 1, mid, mid + 1
})
{
CAPTURE(candidate);
CHECK(!reads_back(std::to_string(candidate), e, v));
}
}
#if defined(JSON_HAS_CPP_17) && defined(__cpp_lib_to_chars)
// the closest of the shortest representations, as std::to_chars finds it
std::array<char, 64> std_text{};
const auto r = std::to_chars(std_text.data(), std_text.data() + std_text.size(), v, std::chars_format::scientific);
CHECK(digits_and_exponent(std::string(std_text.data(), r.ptr)) == de);
#endif
}
} // namespace
TEST_CASE("shortest digits of doubles")
{
SECTION("powers of ten")
{
// the 128-bit significands of 10^k, rounded down, recomputed
for (int k = -342; k <= 341; ++k)
{
CAPTURE(k);
big x{1};
if (k >= 0)
{
for (int i = 0; i < k; ++i)
{
big_mul_small(x, 10);
}
}
else
{
// floor(2^b / 10^-k) for a b that leaves more than 128 bits
const int b = 128 + 64 + (4 * -k);
x.assign(static_cast<std::size_t>(b / 32) + 1, 0);
x.back() = 1u << (b % 32);
for (int i = 0; i < -k; ++i)
{
big_div_small(x, 10);
}
}
const auto expected = big_top128(x);
const auto actual = nlohmann::detail::zmij::pow10(k);
CHECK(actual.high == expected.first);
CHECK(actual.low == expected.second);
}
}
SECTION("boundary values")
{
for (const double v :
{
std::numeric_limits<double>::min(), std::numeric_limits<double>::max(), std::numeric_limits<double>::denorm_min(),
std::nextafter(std::numeric_limits<double>::min(), 0.0), 1.0, 2.0, 0.1, 0.3, 1e21, 1e22, 1e23, 5e-324, 9007199254740993.0,
1.2345e+21, 2.2250738585072014e-308, 1.7976931348623157e308, 4.9406564584124654e-324, 123456789012345680.0
})
{
check_shortest(v);
}
// all powers of two (their rounding interval is narrower below)
for (int e = -1074; e <= 1023; ++e)
{
check_shortest(std::ldexp(1.0, e));
}
// powers of ten and their neighbors
for (int e = -323; e <= 308; ++e)
{
const double p = std::strtod(("1e" + std::to_string(e)).c_str(), nullptr);
check_shortest(p);
check_shortest(std::nextafter(p, 0.0));
check_shortest(std::nextafter(p, std::numeric_limits<double>::infinity()));
}
}
SECTION("random doubles")
{
std::mt19937_64 rng(5295); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible
for (int i = 0; i < 100000; ++i)
{
const std::uint64_t bits = rng() & 0x7FFFFFFFFFFFFFFFu;
const auto v = reinterpret_bits<double>(bits);
if (std::isfinite(v) && v != 0)
{
check_shortest(v);
}
}
}
}