diff --git a/include/nlohmann/detail/conversions/to_chars.hpp b/include/nlohmann/detail/conversions/to_chars.hpp index d97473d78..486e28d48 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 c2823487e..54098a0aa 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 7a857e655..869270b74 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1299,10 +1299,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) { @@ -4010,8 +4011,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; } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 07dea3c4a..6ca3f341b 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -18583,10 +18583,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; @@ -18616,7 +18617,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) @@ -18628,7 +18629,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) @@ -18645,7 +18646,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'; @@ -18667,7 +18668,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)); @@ -18716,7 +18717,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 @@ -18770,13 +18771,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_) {} @@ -19526,7 +19528,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)); } @@ -19538,6 +19540,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 @@ -19683,6 +19687,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; @@ -21298,10 +21304,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) { @@ -24002,15 +24009,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 new file mode 100644 index 000000000..b412eebde --- /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"); +}