From a509d42c06ced43d5d46f8ad2c307d2d4b6ca447 Mon Sep 17 00:00:00 2001 From: Daniel Oberhoff Date: Mon, 1 Jul 2024 21:53:03 +0200 Subject: [PATCH 1/3] implement settable floating point precision output Signed-off-by: Daniel Oberhoff --- include/nlohmann/detail/conversions/to_chars.hpp | 15 ++++++++++----- include/nlohmann/detail/output/serializer.hpp | 11 +++++++---- include/nlohmann/json.hpp | 8 +++++--- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/include/nlohmann/detail/conversions/to_chars.hpp b/include/nlohmann/detail/conversions/to_chars.hpp index e10741c923..7f6c4cc4bd 100644 --- a/include/nlohmann/detail/conversions/to_chars.hpp +++ b/include/nlohmann/detail/conversions/to_chars.hpp @@ -978,10 +978,11 @@ notation. Otherwise it will be printed in exponential notation. JSON_HEDLEY_NON_NULL(1) JSON_HEDLEY_RETURNS_NON_NULL inline char* format_buffer(char* buf, int len, int decimal_exponent, - int min_exp, int max_exp) + int min_exp, int max_exp, size_t precision) { JSON_ASSERT(min_exp < 0); JSON_ASSERT(max_exp > 0); + precision = (std::min)(precision, std::numeric_limits::max_digits10); const int k = len; const int n = len + decimal_exponent; @@ -1009,6 +1010,9 @@ inline char* format_buffer(char* buf, int len, int decimal_exponent, JSON_ASSERT(k > n); + // truncate the digits by the precision + k = (std::min)(static_cast(n) + precision, static_cast(k)); + std::memmove(buf + (static_cast(n) + 1), buf + n, static_cast(k) - static_cast(n)); buf[n] = '.'; return buf + (static_cast(k) + 1U); @@ -1023,7 +1027,8 @@ inline char* format_buffer(char* buf, int len, int decimal_exponent, buf[0] = '0'; buf[1] = '.'; std::memset(buf + 2, '0', static_cast(-n)); - return buf + (2U + static_cast(-n) + static_cast(k)); + // truncate the reported buffer end by the precision + return buf + (std::min)(precision + 2, (2U + static_cast(-n) + static_cast(k))); } if (k == 1) @@ -1040,7 +1045,7 @@ inline char* format_buffer(char* buf, int len, int decimal_exponent, std::memmove(buf + 2, buf + 1, static_cast(k) - 1); buf[1] = '.'; - buf += 1 + static_cast(k); + buf += 1 + (std::min)(precision, static_cast(k)); } *buf++ = 'e'; @@ -1062,7 +1067,7 @@ format. Returns an iterator pointing past-the-end of the decimal representation. template JSON_HEDLEY_NON_NULL(1, 2) JSON_HEDLEY_RETURNS_NON_NULL -char* to_chars(char* first, const char* last, FloatType value) +char* to_chars(char* first, const char* last, FloatType value, size_t precision = std::numeric_limits::max_digits10) { static_cast(last); // maybe unused - fix warning JSON_ASSERT(std::isfinite(value)); @@ -1111,7 +1116,7 @@ char* to_chars(char* first, const char* last, FloatType value) JSON_ASSERT(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits::max_digits10); JSON_ASSERT(last - first >= std::numeric_limits::max_digits10 + 6); - return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); + return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp, precision); } } // namespace detail diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp index ed20b0d9e1..36d4dca87b 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -64,13 +64,14 @@ class serializer @param[in] ichar indentation character to use @param[in] error_handler_ how to react on decoding errors */ - serializer(output_adapter_t s, const char ichar, + serializer(output_adapter_t s, const char ichar, std::size_t prec = 1000, error_handler_t error_handler_ = error_handler_t::strict) : o(std::move(s)) , loc(std::localeconv()) , thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->thousands_sep))) , decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->decimal_point))) , indent_char(ichar) + , precision(prec) , indent_string(512, indent_char) , error_handler(error_handler_) {} @@ -820,7 +821,7 @@ class serializer void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) { auto* begin = number_buffer.data(); - auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); + auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x, precision); o->write_characters(begin, static_cast(end - begin)); } @@ -828,10 +829,10 @@ class serializer void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) { // get number of digits for a float -> text -> float round-trip - static constexpr auto d = std::numeric_limits::max_digits10; + static constexpr int d_max = std::numeric_limits::max_digits10; + int d = static_cast((std::min)(precision, static_cast(d_max))); // the actual conversion - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg) std::ptrdiff_t len = (std::snprintf)(number_buffer.data(), number_buffer.size(), "%.*g", d, x); // negative value indicates an error @@ -977,6 +978,8 @@ class serializer /// the indentation character const char indent_char; + /// precision for floating point output + std::size_t precision; /// the indentation string string_t indent_string; diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 31ca64539b..47f271ba4d 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1272,10 +1272,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec string_t dump(const int indent = -1, const char indent_char = ' ', const bool ensure_ascii = false, - const error_handler_t error_handler = error_handler_t::strict) const + const error_handler_t error_handler = error_handler_t::strict, + const size_t precision = std::numeric_limits::max()) const { string_t result; - serializer s(detail::output_adapter(result), indent_char, error_handler); + serializer s(detail::output_adapter(result), indent_char, precision, error_handler); if (indent >= 0) { @@ -3982,8 +3983,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec o.width(0); // do the actual serialization - serializer s(detail::output_adapter(o), o.fill()); + serializer s(detail::output_adapter(o), o.fill(), static_cast(o.precision())).; s.dump(j, pretty_print, false, static_cast(indentation)); + return o; } From 9d7aa816c9f5fe900e039b0e17eb6296bd1cfde4 Mon Sep 17 00:00:00 2001 From: Daniel Oberhoff Date: Thu, 9 Jan 2025 14:56:34 +0100 Subject: [PATCH 2/3] add test for precision setting Signed-off-by: Daniel Oberhoff --- tests/src/unit-precision.cpp | 57 ++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/src/unit-precision.cpp diff --git a/tests/src/unit-precision.cpp b/tests/src/unit-precision.cpp new file mode 100644 index 0000000000..695049c9a8 --- /dev/null +++ b/tests/src/unit-precision.cpp @@ -0,0 +1,57 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ (test suite) +| | |__ | | | | | | version 3.10.5 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2022 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "doctest_compatibility.h" + +#include +#include +#include +#include +#include +using json = nlohmann::json; + +#include + +TEST_CASE("precision") +{ + json j; + j = M_PI; + auto to_string = [](const nlohmann::json& j, size_t precision) + { + std::stringstream ss; + ss << std::setprecision(precision) << j; + return ss.str(); + }; + auto s1 = to_string(j, 1); + auto s2 = to_string(j, 2); + auto s3 = to_string(j, 3); + CHECK(s1 == "3.1"); + CHECK(s2 == "3.14"); + CHECK(s3 == "3.141"); +} From 0132248b6146aa8c2a23ccd2bcc7c551155a004c Mon Sep 17 00:00:00 2001 From: Daniel Oberhoff Date: Thu, 9 Jan 2025 15:10:55 +0100 Subject: [PATCH 3/3] amalgamate Signed-off-by: Daniel Oberhoff --- single_include/nlohmann/json.hpp | 35 ++++++++++++++++++++------------ tests/src/unit-precision.cpp | 2 +- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index a858728c4c..e6911834b8 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -17886,10 +17886,11 @@ notation. Otherwise it will be printed in exponential notation. JSON_HEDLEY_NON_NULL(1) JSON_HEDLEY_RETURNS_NON_NULL inline char* format_buffer(char* buf, int len, int decimal_exponent, - int min_exp, int max_exp) + int min_exp, int max_exp, size_t precision) { JSON_ASSERT(min_exp < 0); JSON_ASSERT(max_exp > 0); + precision = std::min(precision, 1000); const int k = len; const int n = len + decimal_exponent; @@ -17919,7 +17920,7 @@ inline char* format_buffer(char* buf, int len, int decimal_exponent, std::memmove(buf + (static_cast(n) + 1), buf + n, static_cast(k) - static_cast(n)); buf[n] = '.'; - return buf + (static_cast(k) + 1U); + return buf + (std::min(n + precision, static_cast(k)) + 1U); } if (min_exp < n && n <= 0) @@ -17931,7 +17932,7 @@ inline char* format_buffer(char* buf, int len, int decimal_exponent, buf[0] = '0'; buf[1] = '.'; std::memset(buf + 2, '0', static_cast(-n)); - return buf + (2U + static_cast(-n) + static_cast(k)); + return buf + std::min(precision + 2, (2U + static_cast(-n) + static_cast(k))); } if (k == 1) @@ -17948,7 +17949,7 @@ inline char* format_buffer(char* buf, int len, int decimal_exponent, std::memmove(buf + 2, buf + 1, static_cast(k) - 1); buf[1] = '.'; - buf += 1 + static_cast(k); + buf += 1 + std::min(precision, static_cast(k)); } *buf++ = 'e'; @@ -17970,7 +17971,7 @@ format. Returns an iterator pointing past-the-end of the decimal representation. template JSON_HEDLEY_NON_NULL(1, 2) JSON_HEDLEY_RETURNS_NON_NULL -char* to_chars(char* first, const char* last, FloatType value) +char* to_chars(char* first, const char* last, FloatType value, size_t precision = std::numeric_limits::max_digits10) { static_cast(last); // maybe unused - fix warning JSON_ASSERT(std::isfinite(value)); @@ -18019,7 +18020,7 @@ char* to_chars(char* first, const char* last, FloatType value) JSON_ASSERT(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits::max_digits10); JSON_ASSERT(last - first >= std::numeric_limits::max_digits10 + 6); - return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); + return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp, precision); } } // namespace detail @@ -18073,13 +18074,14 @@ class serializer @param[in] ichar indentation character to use @param[in] error_handler_ how to react on decoding errors */ - serializer(output_adapter_t s, const char ichar, + serializer(output_adapter_t s, const char ichar, size_t precision = 1000, error_handler_t error_handler_ = error_handler_t::strict) : o(std::move(s)) , loc(std::localeconv()) , thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->thousands_sep))) , decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->decimal_point))) , indent_char(ichar) + , precision(precision) , indent_string(512, indent_char) , error_handler(error_handler_) {} @@ -18829,7 +18831,7 @@ class serializer void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) { auto* begin = number_buffer.data(); - auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); + auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x, precision); o->write_characters(begin, static_cast(end - begin)); } @@ -18841,6 +18843,8 @@ class serializer // the actual conversion // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + char format[100]; + snprintf(format, 100, "%%.%dg", precision); std::ptrdiff_t len = (std::snprintf)(number_buffer.data(), number_buffer.size(), "%.*g", d, x); // negative value indicates an error @@ -18986,6 +18990,8 @@ class serializer /// the indentation character const char indent_char; + /// precision for floating point output + size_t precision; /// the indentation string string_t indent_string; @@ -20575,10 +20581,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec string_t dump(const int indent = -1, const char indent_char = ' ', const bool ensure_ascii = false, - const error_handler_t error_handler = error_handler_t::strict) const + const error_handler_t error_handler = error_handler_t::strict, + const size_t precision = std::numeric_limits::max()) const { string_t result; - serializer s(detail::output_adapter(result), indent_char, error_handler); + serializer s(detail::output_adapter(result), indent_char, precision, error_handler); if (indent >= 0) { @@ -23278,15 +23285,17 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec friend std::ostream& operator<<(std::ostream& o, const basic_json& j) { // read width member and use it as indentation parameter if nonzero - const bool pretty_print = o.width() > 0; - const auto indentation = pretty_print ? o.width() : 0; + const auto width = o.width(); + const bool pretty_print = width > 0; + const auto indentation = pretty_print ? width : 0; // reset width to 0 for subsequent calls to this stream o.width(0); // do the actual serialization - serializer s(detail::output_adapter(o), o.fill()); + serializer s(detail::output_adapter(o), o.fill(), o.precision()); s.dump(j, pretty_print, false, static_cast(indentation)); + return o; } diff --git a/tests/src/unit-precision.cpp b/tests/src/unit-precision.cpp index 695049c9a8..b412eebdeb 100644 --- a/tests/src/unit-precision.cpp +++ b/tests/src/unit-precision.cpp @@ -42,7 +42,7 @@ TEST_CASE("precision") { json j; j = M_PI; - auto to_string = [](const nlohmann::json& j, size_t precision) + auto to_string = [](const nlohmann::json & j, size_t precision) { std::stringstream ss; ss << std::setprecision(precision) << j;