Fix for printing long doubles bug in dump_float (#3929)

This commit is contained in:
Kirill Lokotkov
2026-05-15 20:25:16 +03:00
committed by GitHub
parent bb5404bb86
commit b1bb9fce0c
3 changed files with 73 additions and 2 deletions
@@ -836,7 +836,7 @@ class serializer
static int snprintf_float(char* buf, std::size_t size, int d, long double x)
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
return (std::snprintf)(buf, size, "%.*lg", d, x);
return (std::snprintf)(buf, size, "%.*Lg", d, x);
}
void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
+1 -1
View File
@@ -19828,7 +19828,7 @@ class serializer
static int snprintf_float(char* buf, std::size_t size, int d, long double x)
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
return (std::snprintf)(buf, size, "%.*lg", d, x);
return (std::snprintf)(buf, size, "%.*Lg", d, x);
}
void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
+71
View File
@@ -11,6 +11,7 @@
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <array>
#include <sstream>
#include <iomanip>
@@ -295,3 +296,73 @@ TEST_CASE("dump with binary values")
"]");
}
}
TEST_CASE("dump for basic_json with long double number_float_t")
{
// Custom basic_json instantiation with long double as NumberFloatType.
// On platforms where long double is wider than double (e.g. GCC/Clang on
// Linux/macOS x86_64), dump() goes through the snprintf path in
// serializer::dump_float(x, std::false_type). That branch must use the
// "%.*Lg" format specifier; using "%.*g" with a long double argument is
// undefined behavior and corrupts the output.
using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string,
bool, std::int64_t, std::uint64_t, long double>;
SECTION("round-trip dump/parse")
{
constexpr std::array<long double, 13> values =
{{
0.0L, -0.0L, 1.0L, -1.0L,
0.5L, -0.5L, 1.5L, -2.25L,
1.23e45L, 1.23e-45L,
(std::numeric_limits<long double>::min)(),
std::numeric_limits<long double>::lowest(),
(std::numeric_limits<long double>::max)()
}};
for (long double v : values)
{
const long_double_json j = v;
const auto s = j.dump();
const auto j2 = long_double_json::parse(s);
CHECK(j2.template get<long double>() == v);
}
}
SECTION("exact dump string for simple values")
{
CHECK(long_double_json(0.5L).dump() == "0.5");
CHECK(long_double_json(-0.5L).dump() == "-0.5");
CHECK(long_double_json(1.5L).dump() == "1.5");
CHECK(long_double_json(-2.25L).dump() == "-2.25");
CHECK(long_double_json(0.0L).dump() == "0.0");
CHECK(long_double_json(1.0L).dump() == "1.0");
CHECK(long_double_json(-1.0L).dump() == "-1.0");
CHECK(long_double_json(100.0L).dump() == "100.0");
}
SECTION("NaN and infinity dump as null")
{
CHECK(long_double_json(std::numeric_limits<long double>::quiet_NaN()).dump() == "null");
CHECK(long_double_json(std::numeric_limits<long double>::infinity()).dump() == "null");
CHECK(long_double_json(-std::numeric_limits<long double>::infinity()).dump() == "null");
}
SECTION("dump output matches double for exactly-representable values")
{
auto check_same = [](long double v_ld, double v_d)
{
const long_double_json j_ld = v_ld;
const json j_d = v_d;
CHECK(j_ld.dump() == j_d.dump());
};
check_same(0.0L, 0.0);
check_same(0.5L, 0.5);
check_same(-0.5L, -0.5);
check_same(1.5L, 1.5);
check_same(-2.25L, -2.25);
check_same(1.0L, 1.0);
check_same(100.0L, 100.0);
}
}