mirror of
https://github.com/nlohmann/json.git
synced 2026-10-03 13:10:33 +00:00
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>
771 lines
42 KiB
C++
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);
|
|
}
|
|
}
|
|
}
|
|
}
|