diff --git a/CHANGELOG.md b/CHANGELOG.md index 819cd3d..125ccfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ Highlights are indicated with ❤️. - added `toml::format_flags::allow_unicode_strings` - added `toml::format_flags::indent_array_elements` (#120) (@W4RH4WK) - added `toml::format_flags::indent_sub_tables` (#120) (@W4RH4WK) +- added `toml::format_flags::relaxed_float_precision` (#89) (@vaartis) - added `toml::format_flags::quote_infinities_and_nans` - added `toml::is_key<>` and toml::is_key_or_convertible<>` metafunctions - added `toml::node_view::operator==` diff --git a/CMakeLists.txt b/CMakeLists.txt index 437015b..6916710 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.14) project( tomlplusplus VERSION 3.0.0 - DESCRIPTION "Header-only TOML config file parser and serializer for C++17 (and later!)" + DESCRIPTION "Header-only TOML config file parser and serializer for C++17" HOMEPAGE_URL "https://marzer.github.io/tomlplusplus/" LANGUAGES CXX ) diff --git a/include/toml++/impl/at_path.h b/include/toml++/impl/at_path.h index 866b7be..468a7f1 100644 --- a/include/toml++/impl/at_path.h +++ b/include/toml++/impl/at_path.h @@ -30,7 +30,7 @@ TOML_NAMESPACE_START /// \eout /// /// - /// \warning Keys in paths are interpreted literally, so whitespace (or lack thereof) matters: + /// \note Keys in paths are interpreted literally, so whitespace (or lack thereof) matters: /// \cpp /// toml::at_path(config, "foo.bar") // same as config["foo"]["bar"] /// toml::at_path(config, "foo. bar") // same as config["foo"][" bar"] diff --git a/include/toml++/impl/formatter.inl b/include/toml++/impl/formatter.inl index bb25a93..3603685 100644 --- a/include/toml++/impl/formatter.inl +++ b/include/toml++/impl/formatter.inl @@ -410,7 +410,12 @@ TOML_IMPL_NAMESPACE_START case fp_class::neg_inf: inf_nan = &constants_->float_neg_inf; break; case fp_class::pos_inf: inf_nan = &constants_->float_pos_inf; break; case fp_class::nan: inf_nan = &constants_->float_nan; break; - case fp_class::ok: print_to_stream(*stream_, *val); break; + case fp_class::ok: + print_to_stream(*stream_, + *val, + value_flags::none, + !!(config_.flags & format_flags::relaxed_float_precision)); + break; default: TOML_UNREACHABLE; } diff --git a/include/toml++/impl/forward_declarations.h b/include/toml++/impl/forward_declarations.h index 155969c..915b7bd 100644 --- a/include/toml++/impl/forward_declarations.h +++ b/include/toml++/impl/forward_declarations.h @@ -329,6 +329,13 @@ TOML_NAMESPACE_START // abi namespace /// \brief Combination mask of all indentation-enabling flags. indentation = indent_sub_tables | indent_array_elements, + + /// \brief Emit floating-point values with relaxed precision. + /// + /// \warning Setting this flag may cause serialized documents to no longer round-trip correctly + /// since floats might have a less precise value upon being written out than they did when being + /// read in. Use this flag at your own risk. + relaxed_float_precision = (1ull << 11), }; TOML_MAKE_FLAGS(format_flags); diff --git a/include/toml++/impl/preprocessor.h b/include/toml++/impl/preprocessor.h index b74c9ec..61b979c 100644 --- a/include/toml++/impl/preprocessor.h +++ b/include/toml++/impl/preprocessor.h @@ -516,12 +516,12 @@ //# ATTRIBUTES, UTILITY MACROS ETC //#==================================================================================================================== -#if TOML_GCC || TOML_CLANG || (TOML_ICC && !TOML_ICC_CL) +#if !defined(TOML_FLOAT_CHARCONV) && (TOML_GCC || TOML_CLANG || (TOML_ICC && !TOML_ICC_CL)) // not supported by any version of GCC or Clang as of 26/11/2020 // not supported by any version of ICC on Linux as of 11/01/2021 #define TOML_FLOAT_CHARCONV 0 #endif -#if defined(__EMSCRIPTEN__) || defined(__APPLE__) +#if !defined(TOML_INT_CHARCONV) && (defined(__EMSCRIPTEN__) || defined(__APPLE__)) // causes link errors on emscripten // causes Mac OS SDK version errors on some versions of Apple Clang #define TOML_INT_CHARCONV 0 @@ -690,9 +690,7 @@ #endif #define TOML_MAKE_FLAGS_(name, op) \ - TOML_NODISCARD \ - TOML_ALWAYS_INLINE \ - TOML_ATTR(const) \ + TOML_CONST_INLINE_GETTER \ constexpr name operator op(name lhs, name rhs) noexcept \ { \ using under = std::underlying_type_t; \ @@ -708,17 +706,13 @@ TOML_MAKE_FLAGS_(name, &); \ TOML_MAKE_FLAGS_(name, |); \ TOML_MAKE_FLAGS_(name, ^); \ - TOML_NODISCARD \ - TOML_ALWAYS_INLINE \ - TOML_ATTR(const) \ + TOML_CONST_INLINE_GETTER \ constexpr name operator~(name val) noexcept \ { \ using under = std::underlying_type_t; \ return static_cast(~static_cast(val)); \ } \ - TOML_NODISCARD \ - TOML_ALWAYS_INLINE \ - TOML_ATTR(const) \ + TOML_CONST_INLINE_GETTER \ constexpr bool operator!(name val) noexcept \ { \ using under = std::underlying_type_t; \ diff --git a/include/toml++/impl/print_to_stream.h b/include/toml++/impl/print_to_stream.h index b1a0bad..584b38e 100644 --- a/include/toml++/impl/print_to_stream.h +++ b/include/toml++/impl/print_to_stream.h @@ -54,10 +54,10 @@ TOML_IMPL_NAMESPACE_START void print_to_stream(std::ostream&, uint64_t, value_flags = {}, size_t min_digits = 0); TOML_API - void print_to_stream(std::ostream&, float, value_flags = {}); + void print_to_stream(std::ostream&, float, value_flags = {}, bool relaxed_precision = false); TOML_API - void print_to_stream(std::ostream&, double, value_flags = {}); + void print_to_stream(std::ostream&, double, value_flags = {}, bool relaxed_precision = false); TOML_API void print_to_stream(std::ostream&, bool); diff --git a/include/toml++/impl/print_to_stream.inl b/include/toml++/impl/print_to_stream.inl index 195d40d..d11ed4f 100644 --- a/include/toml++/impl/print_to_stream.inl +++ b/include/toml++/impl/print_to_stream.inl @@ -62,10 +62,10 @@ TOML_ANON_NAMESPACE_START inline constexpr size_t charconv_buffer_length = 20; // strlen("18446744073709551615") template <> - inline constexpr size_t charconv_buffer_length = 40; + inline constexpr size_t charconv_buffer_length = 64; template <> - inline constexpr size_t charconv_buffer_length = 60; + inline constexpr size_t charconv_buffer_length = 64; template TOML_INTERNAL_LINKAGE @@ -156,7 +156,10 @@ TOML_ANON_NAMESPACE_START template TOML_INTERNAL_LINKAGE - void print_floating_point_to_stream(std::ostream & stream, T val, value_flags format = {}) + void print_floating_point_to_stream(std::ostream & stream, + T val, + value_flags format, + [[maybe_unused]] bool relaxed_precision) { switch (impl::fpclassify(val)) { @@ -178,20 +181,31 @@ TOML_ANON_NAMESPACE_START #if TOML_FLOAT_CHARCONV + const auto hex = !!(format & value_flags::format_as_hexadecimal); char buf[charconv_buffer_length]; - const auto res = !!(format & value_flags::format_as_hexadecimal) - ? std::to_chars(buf, buf + sizeof(buf), val, std::chars_format::hex) - : std::to_chars(buf, buf + sizeof(buf), val); - const auto str = std::string_view{ buf, static_cast(res.ptr - buf) }; + auto res = hex ? std::to_chars(buf, buf + sizeof(buf), val, std::chars_format::hex) + : std::to_chars(buf, buf + sizeof(buf), val); + auto str = std::string_view{ buf, static_cast(res.ptr - buf) }; + + char buf2[charconv_buffer_length]; + if (!hex && relaxed_precision) + { + res = std::to_chars(buf2, buf2 + sizeof(buf2), val, std::chars_format::general, 6); + const auto str2 = std::string_view{ buf2, static_cast(res.ptr - buf2) }; + if (str2.length() < str.length()) + str = str2; + } + impl::print_to_stream(stream, str); - if (!(format & value_flags::format_as_hexadecimal) && needs_decimal_point(str)) + if (!hex && needs_decimal_point(str)) toml::impl::print_to_stream(stream, ".0"sv); #else std::ostringstream ss; ss.imbue(std::locale::classic()); - ss.precision(std::numeric_limits::max_digits10); + if (!relaxed_precision) + ss.precision(std::numeric_limits::max_digits10); if (!!(format & value_flags::format_as_hexadecimal)) ss << std::hexfloat; ss << val; @@ -286,15 +300,15 @@ TOML_IMPL_NAMESPACE_START } TOML_EXTERNAL_LINKAGE - void print_to_stream(std::ostream & stream, float val, value_flags format) + void print_to_stream(std::ostream & stream, float val, value_flags format, bool relaxed_precision) { - TOML_ANON_NAMESPACE::print_floating_point_to_stream(stream, val, format); + TOML_ANON_NAMESPACE::print_floating_point_to_stream(stream, val, format, relaxed_precision); } TOML_EXTERNAL_LINKAGE - void print_to_stream(std::ostream & stream, double val, value_flags format) + void print_to_stream(std::ostream & stream, double val, value_flags format, bool relaxed_precision) { - TOML_ANON_NAMESPACE::print_floating_point_to_stream(stream, val, format); + TOML_ANON_NAMESPACE::print_floating_point_to_stream(stream, val, format, relaxed_precision); } TOML_EXTERNAL_LINKAGE diff --git a/tests/parsing_floats.cpp b/tests/parsing_floats.cpp index 0e79127..11e1f18 100644 --- a/tests/parsing_floats.cpp +++ b/tests/parsing_floats.cpp @@ -133,6 +133,12 @@ TEST_CASE("parsing - floats") parse_expected_value(FILE_LINE_ARGS, "6.02e23"sv, 6.02e23); parse_expected_value(FILE_LINE_ARGS, "6.02e+23"sv, 6.02e+23); parse_expected_value(FILE_LINE_ARGS, "1.112_650_06e-17"sv, 1.11265006e-17); + parse_expected_value(FILE_LINE_ARGS, "0.010284358729827818"sv, 0.010284358729827818); + parse_expected_value(FILE_LINE_ARGS, "0.010284358729827818"sv, 0.010284358729827818); + parse_expected_value(FILE_LINE_ARGS, "0.0102"sv, 0.0102); + parse_expected_value(FILE_LINE_ARGS, "10.0102"sv, 10.0102); + parse_expected_value(FILE_LINE_ARGS, "10.010284358729828"sv, 10.010284358729828); + parse_expected_value(FILE_LINE_ARGS, "10.0"sv, 10.0); // toml/issues/562 (hexfloats) #if TOML_LANG_UNRELEASED diff --git a/toml.hpp b/toml.hpp index d942739..2bbdafc 100644 --- a/toml.hpp +++ b/toml.hpp @@ -526,12 +526,12 @@ #endif #endif -#if TOML_GCC || TOML_CLANG || (TOML_ICC && !TOML_ICC_CL) +#if !defined(TOML_FLOAT_CHARCONV) && (TOML_GCC || TOML_CLANG || (TOML_ICC && !TOML_ICC_CL)) // not supported by any version of GCC or Clang as of 26/11/2020 // not supported by any version of ICC on Linux as of 11/01/2021 #define TOML_FLOAT_CHARCONV 0 #endif -#if defined(__EMSCRIPTEN__) || defined(__APPLE__) +#if !defined(TOML_INT_CHARCONV) && (defined(__EMSCRIPTEN__) || defined(__APPLE__)) // causes link errors on emscripten // causes Mac OS SDK version errors on some versions of Apple Clang #define TOML_INT_CHARCONV 0 @@ -700,9 +700,7 @@ #endif #define TOML_MAKE_FLAGS_(name, op) \ - TOML_NODISCARD \ - TOML_ALWAYS_INLINE \ - TOML_ATTR(const) \ + TOML_CONST_INLINE_GETTER \ constexpr name operator op(name lhs, name rhs) noexcept \ { \ using under = std::underlying_type_t; \ @@ -718,17 +716,13 @@ TOML_MAKE_FLAGS_(name, &); \ TOML_MAKE_FLAGS_(name, |); \ TOML_MAKE_FLAGS_(name, ^); \ - TOML_NODISCARD \ - TOML_ALWAYS_INLINE \ - TOML_ATTR(const) \ + TOML_CONST_INLINE_GETTER \ constexpr name operator~(name val) noexcept \ { \ using under = std::underlying_type_t; \ return static_cast(~static_cast(val)); \ } \ - TOML_NODISCARD \ - TOML_ALWAYS_INLINE \ - TOML_ATTR(const) \ + TOML_CONST_INLINE_GETTER \ constexpr bool operator!(name val) noexcept \ { \ using under = std::underlying_type_t; \ @@ -1295,6 +1289,7 @@ TOML_NAMESPACE_START // abi namespace indent_sub_tables = (1ull << 9), indent_array_elements = (1ull << 10), indentation = indent_sub_tables | indent_array_elements, + relaxed_float_precision = (1ull << 11), }; TOML_MAKE_FLAGS(format_flags); @@ -2045,10 +2040,10 @@ TOML_IMPL_NAMESPACE_START void print_to_stream(std::ostream&, uint64_t, value_flags = {}, size_t min_digits = 0); TOML_API - void print_to_stream(std::ostream&, float, value_flags = {}); + void print_to_stream(std::ostream&, float, value_flags = {}, bool relaxed_precision = false); TOML_API - void print_to_stream(std::ostream&, double, value_flags = {}); + void print_to_stream(std::ostream&, double, value_flags = {}, bool relaxed_precision = false); TOML_API void print_to_stream(std::ostream&, bool); @@ -9376,10 +9371,10 @@ TOML_ANON_NAMESPACE_START inline constexpr size_t charconv_buffer_length = 20; // strlen("18446744073709551615") template <> - inline constexpr size_t charconv_buffer_length = 40; + inline constexpr size_t charconv_buffer_length = 64; template <> - inline constexpr size_t charconv_buffer_length = 60; + inline constexpr size_t charconv_buffer_length = 64; template TOML_INTERNAL_LINKAGE @@ -9470,7 +9465,10 @@ TOML_ANON_NAMESPACE_START template TOML_INTERNAL_LINKAGE - void print_floating_point_to_stream(std::ostream & stream, T val, value_flags format = {}) + void print_floating_point_to_stream(std::ostream & stream, + T val, + value_flags format, + [[maybe_unused]] bool relaxed_precision) { switch (impl::fpclassify(val)) { @@ -9492,20 +9490,31 @@ TOML_ANON_NAMESPACE_START #if TOML_FLOAT_CHARCONV + const auto hex = !!(format & value_flags::format_as_hexadecimal); char buf[charconv_buffer_length]; - const auto res = !!(format & value_flags::format_as_hexadecimal) - ? std::to_chars(buf, buf + sizeof(buf), val, std::chars_format::hex) - : std::to_chars(buf, buf + sizeof(buf), val); - const auto str = std::string_view{ buf, static_cast(res.ptr - buf) }; + auto res = hex ? std::to_chars(buf, buf + sizeof(buf), val, std::chars_format::hex) + : std::to_chars(buf, buf + sizeof(buf), val); + auto str = std::string_view{ buf, static_cast(res.ptr - buf) }; + + char buf2[charconv_buffer_length]; + if (!hex && relaxed_precision) + { + res = std::to_chars(buf2, buf2 + sizeof(buf2), val, std::chars_format::general, 6); + const auto str2 = std::string_view{ buf2, static_cast(res.ptr - buf2) }; + if (str2.length() < str.length()) + str = str2; + } + impl::print_to_stream(stream, str); - if (!(format & value_flags::format_as_hexadecimal) && needs_decimal_point(str)) + if (!hex && needs_decimal_point(str)) toml::impl::print_to_stream(stream, ".0"sv); #else std::ostringstream ss; ss.imbue(std::locale::classic()); - ss.precision(std::numeric_limits::max_digits10); + if (!relaxed_precision) + ss.precision(std::numeric_limits::max_digits10); if (!!(format & value_flags::format_as_hexadecimal)) ss << std::hexfloat; ss << val; @@ -9600,15 +9609,15 @@ TOML_IMPL_NAMESPACE_START } TOML_EXTERNAL_LINKAGE - void print_to_stream(std::ostream & stream, float val, value_flags format) + void print_to_stream(std::ostream & stream, float val, value_flags format, bool relaxed_precision) { - TOML_ANON_NAMESPACE::print_floating_point_to_stream(stream, val, format); + TOML_ANON_NAMESPACE::print_floating_point_to_stream(stream, val, format, relaxed_precision); } TOML_EXTERNAL_LINKAGE - void print_to_stream(std::ostream & stream, double val, value_flags format) + void print_to_stream(std::ostream & stream, double val, value_flags format, bool relaxed_precision) { - TOML_ANON_NAMESPACE::print_floating_point_to_stream(stream, val, format); + TOML_ANON_NAMESPACE::print_floating_point_to_stream(stream, val, format, relaxed_precision); } TOML_EXTERNAL_LINKAGE @@ -15244,7 +15253,12 @@ TOML_IMPL_NAMESPACE_START case fp_class::neg_inf: inf_nan = &constants_->float_neg_inf; break; case fp_class::pos_inf: inf_nan = &constants_->float_pos_inf; break; case fp_class::nan: inf_nan = &constants_->float_nan; break; - case fp_class::ok: print_to_stream(*stream_, *val); break; + case fp_class::ok: + print_to_stream(*stream_, + *val, + value_flags::none, + !!(config_.flags & format_flags::relaxed_float_precision)); + break; default: TOML_UNREACHABLE; }