From ee593c267358ea662bf21b2b51ebbf14f79930f3 Mon Sep 17 00:00:00 2001 From: JCrawfy Date: Sun, 1 Mar 2020 22:01:14 +1300 Subject: [PATCH] bug fixes, move the faster serialisation into the numeric header serialisation ends up roughly 2x improvement going from sstream to snprintf Run on (4 X 3500 MHz CPU s) CPU Caches: L1 Data 32K (x4) L1 Instruction 32K (x4) L2 Unified 262K (x4) L3 Unified 6291K (x1) ------------------------------------------------------------------------------------------------------------- Benchmark Time CPU Iterations ------------------------------------------------------------------------------------------------------------- RandFloatStrs/double_from_string_sstream 968 ns 977 ns 640000 RandFloatStrs/double_from_string_strtod 272 ns 270 ns 2488889 RandFloatStrs/double_from_string_strtod_fixed 272 ns 270 ns 2488889 RandFloatStrs/double_from_string_strtod_fixed_const_ref 273 ns 270 ns 2488889 RandFloatStrs/double_from_string_std_from_chars 193 ns 195 ns 3446154 RandFloatCommaStrs/double_from_string_strtod_fixed_comma_ref 272 ns 273 ns 2635294 RandFloatCommaStrs/double_from_string_strtod_fixed_comma_const_ref 276 ns 273 ns 2635294 RandFloats/string_from_double_sstream 1311 ns 1318 ns 497778 RandFloats/string_from_double_sstream_cached 1076 ns 1050 ns 640000 RandFloats/string_from_double_snprintf 601 ns 600 ns 1120000 RandFloats/string_from_double_snprintf_fixed 600 ns 600 ns 1120000 RandFloats/string_from_double_std_to_chars 117 ns 117 ns 5600000 RandFloatsComma/string_from_double_snprintf_fixed_comma 600 ns 600 ns 1120000 --- .../microbenchmarks/double_to_string.cpp | 4 +- include/xlnt/utils/numeric.hpp | 86 +++++++++++-------- tests/detail/numeric_util_test_suite.cpp | 17 ++-- 3 files changed, 60 insertions(+), 47 deletions(-) diff --git a/benchmarks/microbenchmarks/double_to_string.cpp b/benchmarks/microbenchmarks/double_to_string.cpp index 621c1165..0b82803f 100644 --- a/benchmarks/microbenchmarks/double_to_string.cpp +++ b/benchmarks/microbenchmarks/double_to_string.cpp @@ -118,7 +118,7 @@ public: std::string serialise(double d) { char buf[Excel_Digit_Precision + 1]; // need space for trailing '\0' - int len = snprintf(buf, sizeof(buf), "%16f", d); + int len = snprintf(buf, sizeof(buf), "%.15g", d); if (should_convert_comma) { convert_comma(buf, len); @@ -158,7 +158,7 @@ BENCHMARK_F(RandFloats, string_from_double_snprintf) while (state.KeepRunning()) { char buf[16]; - int len = snprintf(buf, sizeof(buf), "%16f", get_rand()); + int len = snprintf(buf, sizeof(buf), "%.15g", get_rand()); benchmark::DoNotOptimize( std::string(buf, len)); diff --git a/include/xlnt/utils/numeric.hpp b/include/xlnt/utils/numeric.hpp index 28f37a90..6171ec13 100644 --- a/include/xlnt/utils/numeric.hpp +++ b/include/xlnt/utils/numeric.hpp @@ -34,21 +34,6 @@ namespace xlnt { namespace detail { -/// -/// Takes in any number and outputs a string form of that number which will -/// serialise and deserialise without loss of precision -/// -template -std::string serialize_number_to_string(Number num) -{ - // more digits and excel won't match - constexpr int Excel_Digit_Precision = 15; //sf - std::stringstream ss; - ss.precision(Excel_Digit_Precision); - ss << num; - return ss.str(); -} - /// /// constexpr abs /// @@ -117,45 +102,72 @@ bool float_equals(const LNumber &lhs, const RNumber &rhs, return ((lhs + scaled_fuzz) >= rhs) && ((rhs + scaled_fuzz) >= lhs); } -struct number_converter +class number_serialiser { - explicit number_converter() - : should_convert_to_comma(std::use_facet>(std::locale{}).decimal_point() == ',') + static constexpr int Excel_Digit_Precision = 15; //sf + bool should_convert_comma; + + static void convert_comma_to_pt(char *buf, int len) + { + char *buf_end = buf + len; + char *decimal = std::find(buf, buf_end, ','); + if (decimal != buf_end) + { + *decimal = '.'; + } + } + + static void convert_pt_to_comma(char *buf, size_t len) + { + char *buf_end = buf + len; + char *decimal = std::find(buf, buf_end, '.'); + if (decimal != buf_end) + { + *decimal = ','; + } + } + +public: + explicit number_serialiser() + : should_convert_comma(std::use_facet>(std::locale{}).decimal_point() == ',') { } - double stold(std::string &s) const noexcept + std::string serialise(double d) const + { + char buf[30]; + int len = snprintf(buf, sizeof(buf), "%.15g", d); + if (should_convert_comma) + { + convert_comma_to_pt(buf, len); + } + return std::string(buf, len); + } + + double deserialise(std::string &s) const noexcept { assert(!s.empty()); - if (should_convert_to_comma) + if (should_convert_comma) { - auto decimal_pt = std::find(s.begin(), s.end(), '.'); - if (decimal_pt != s.end()) - { - *decimal_pt = ','; - } + // s.data() doesn't have a non-const overload until c++17, hence this little dance + convert_pt_to_comma(&s[0], s.size()); } return strtod(s.c_str(), nullptr); } - double stold(const std::string &s) const + double deserialise(const std::string &s) const { assert(!s.empty()); - if (!should_convert_to_comma) + if (!should_convert_comma) { return strtod(s.c_str(), nullptr); } - std::string copy(s); - auto decimal_pt = std::find(copy.begin(), copy.end(), '.'); - if (decimal_pt != copy.end()) - { - *decimal_pt = ','; - } - return strtod(copy.c_str(), nullptr); + char buf[30]; + assert(s.size() < std::size(buf)); + auto copy_end = std::copy(s.begin(), s.end(), buf); + convert_pt_to_comma(buf, static_cast(copy_end - buf)); + return strtod(buf, nullptr); } - -private: - bool should_convert_to_comma = false; }; } // namespace detail diff --git a/tests/detail/numeric_util_test_suite.cpp b/tests/detail/numeric_util_test_suite.cpp index 1d8026ba..c2536f84 100644 --- a/tests/detail/numeric_util_test_suite.cpp +++ b/tests/detail/numeric_util_test_suite.cpp @@ -40,17 +40,18 @@ public: void test_serialise_number() { + xlnt::detail::number_serialiser serialiser; // excel serialises numbers as floating point values with <= 15 digits of precision - xlnt_assert(xlnt::detail::serialize_number_to_string(1) == "1"); + xlnt_assert(serialiser.serialise(1) == "1"); // trailing zeroes are ignored - xlnt_assert(xlnt::detail::serialize_number_to_string(1.0) == "1"); - xlnt_assert(xlnt::detail::serialize_number_to_string(1.0f) == "1"); + xlnt_assert(serialiser.serialise(1.0) == "1"); + xlnt_assert(serialiser.serialise(1.0f) == "1"); // one to 1 relation - xlnt_assert(xlnt::detail::serialize_number_to_string(1.23456) == "1.23456"); - xlnt_assert(xlnt::detail::serialize_number_to_string(1.23456789012345) == "1.23456789012345"); - xlnt_assert(xlnt::detail::serialize_number_to_string(123456.789012345) == "123456.789012345"); - xlnt_assert(xlnt::detail::serialize_number_to_string(1.23456789012345e+67) == "1.23456789012345e+67"); - xlnt_assert(xlnt::detail::serialize_number_to_string(1.23456789012345e-67) == "1.23456789012345e-67"); + xlnt_assert(serialiser.serialise(1.23456) == "1.23456"); + xlnt_assert(serialiser.serialise(1.23456789012345) == "1.23456789012345"); + xlnt_assert(serialiser.serialise(123456.789012345) == "123456.789012345"); + xlnt_assert(serialiser.serialise(1.23456789012345e+67) == "1.23456789012345e+67"); + xlnt_assert(serialiser.serialise(1.23456789012345e-67) == "1.23456789012345e-67"); } void test_float_equals_zero()