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
pull/447/head
JCrawfy 2020-03-01 22:01:14 +13:00
parent fbbf7ae767
commit ee593c2673
3 changed files with 60 additions and 47 deletions

View File

@ -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));

View File

@ -34,21 +34,6 @@
namespace xlnt {
namespace detail {
/// <summary>
/// Takes in any number and outputs a string form of that number which will
/// serialise and deserialise without loss of precision
/// </summary>
template <typename Number>
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();
}
/// <summary>
/// constexpr abs
/// </summary>
@ -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::numpunct<char>>(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::numpunct<char>>(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<size_t>(copy_end - buf));
return strtod(buf, nullptr);
}
private:
bool should_convert_to_comma = false;
};
} // namespace detail

View File

@ -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()