double_to_string.cpp 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. // A core part of the xlsx serialisation routine is taking doubles from memory and stringifying them
  2. // this has a few requirements
  3. // - expect strings in the form 1234.56 (i.e. no thousands seperator, '.' used for the decimal seperator)
  4. // - outputs up to 15 significant figures (excel only serialises numbers up to 15sf)
  5. #include "benchmark/benchmark.h"
  6. #include <locale>
  7. #include <random>
  8. #include <sstream>
  9. namespace {
  10. // setup a large quantity of random doubles as strings
  11. template <bool Decimal_Locale = true>
  12. class RandomFloats : public benchmark::Fixture
  13. {
  14. static constexpr size_t Number_of_Elements = 1 << 20;
  15. static_assert(Number_of_Elements > 1'000'000, "ensure a decent set of random values is generated");
  16. std::vector<double> inputs;
  17. size_t index = 0;
  18. const char *locale_str = nullptr;
  19. public:
  20. void SetUp(const ::benchmark::State &state)
  21. {
  22. if (Decimal_Locale)
  23. {
  24. locale_str = setlocale(LC_ALL, "C");
  25. }
  26. else
  27. {
  28. locale_str = setlocale(LC_ALL, "de-DE");
  29. }
  30. std::random_device rd; // obtain a seed for the random number engine
  31. std::mt19937 gen(rd());
  32. // doing full range is stupid (<double>::min/max()...), it just ends up generating very large numbers
  33. // uniform is probably not the best distribution to use here, but it will do for now
  34. std::uniform_real_distribution<double> dis(-1'000, 1'000);
  35. // generate a large quantity of doubles to deserialise
  36. inputs.reserve(Number_of_Elements);
  37. for (int i = 0; i < Number_of_Elements; ++i)
  38. {
  39. double d = dis(gen);
  40. inputs.push_back(d);
  41. }
  42. }
  43. void TearDown(const ::benchmark::State &state)
  44. {
  45. // restore locale
  46. setlocale(LC_ALL, locale_str);
  47. // gbench is keeping the fixtures alive somewhere, need to clear the data after use
  48. inputs = std::vector<double>{};
  49. }
  50. double &get_rand()
  51. {
  52. return inputs[++index & (Number_of_Elements - 1)];
  53. }
  54. };
  55. /// Takes in a double and outputs a string form of that number which will
  56. /// serialise and deserialise without loss of precision
  57. std::string serialize_number_to_string(double num)
  58. {
  59. // more digits and excel won't match
  60. constexpr int Excel_Digit_Precision = 15; //sf
  61. std::stringstream ss;
  62. ss.precision(Excel_Digit_Precision);
  63. ss << num;
  64. return ss.str();
  65. }
  66. class number_serialiser
  67. {
  68. static constexpr int Excel_Digit_Precision = 15; //sf
  69. std::ostringstream ss;
  70. public:
  71. explicit number_serialiser()
  72. {
  73. ss.precision(Excel_Digit_Precision);
  74. ss.imbue(std::locale("C"));
  75. }
  76. std::string serialise(double d)
  77. {
  78. ss.str(""); // reset string buffer
  79. ss.clear(); // reset any error flags
  80. ss << d;
  81. return ss.str();
  82. }
  83. };
  84. class number_serialiser_mk2
  85. {
  86. static constexpr int Excel_Digit_Precision = 15; //sf
  87. bool should_convert_comma;
  88. void convert_comma(char *buf, int len)
  89. {
  90. char *buf_end = buf + len;
  91. char *decimal = std::find(buf, buf_end, ',');
  92. if (decimal != buf_end)
  93. {
  94. *decimal = '.';
  95. }
  96. }
  97. public:
  98. explicit number_serialiser_mk2()
  99. : should_convert_comma(std::use_facet<std::numpunct<char>>(std::locale{}).decimal_point() == ',')
  100. {
  101. }
  102. std::string serialise(double d)
  103. {
  104. char buf[Excel_Digit_Precision + 1]; // need space for trailing '\0'
  105. int len = snprintf(buf, sizeof(buf), "%.15g", d);
  106. if (should_convert_comma)
  107. {
  108. convert_comma(buf, len);
  109. }
  110. return std::string(buf, len);
  111. }
  112. };
  113. using RandFloats = RandomFloats<true>;
  114. using RandFloatsComma = RandomFloats<false>;
  115. } // namespace
  116. BENCHMARK_F(RandFloats, string_from_double_sstream)
  117. (benchmark::State &state)
  118. {
  119. while (state.KeepRunning())
  120. {
  121. benchmark::DoNotOptimize(
  122. serialize_number_to_string(get_rand()));
  123. }
  124. }
  125. BENCHMARK_F(RandFloats, string_from_double_sstream_cached)
  126. (benchmark::State &state)
  127. {
  128. number_serialiser ser;
  129. while (state.KeepRunning())
  130. {
  131. benchmark::DoNotOptimize(
  132. ser.serialise(get_rand()));
  133. }
  134. }
  135. BENCHMARK_F(RandFloats, string_from_double_snprintf)
  136. (benchmark::State &state)
  137. {
  138. while (state.KeepRunning())
  139. {
  140. char buf[16];
  141. int len = snprintf(buf, sizeof(buf), "%.15g", get_rand());
  142. benchmark::DoNotOptimize(
  143. std::string(buf, len));
  144. }
  145. }
  146. BENCHMARK_F(RandFloats, string_from_double_snprintf_fixed)
  147. (benchmark::State &state)
  148. {
  149. number_serialiser_mk2 ser;
  150. while (state.KeepRunning())
  151. {
  152. benchmark::DoNotOptimize(
  153. ser.serialise(get_rand()));
  154. }
  155. }
  156. // locale names are different between OS's, and std::from_chars is only complete in MSVC
  157. #ifdef _MSC_VER
  158. #include <charconv>
  159. BENCHMARK_F(RandFloats, string_from_double_std_to_chars)
  160. (benchmark::State &state)
  161. {
  162. while (state.KeepRunning())
  163. {
  164. char buf[16];
  165. std::to_chars_result result = std::to_chars(buf, buf + std::size(buf), get_rand());
  166. benchmark::DoNotOptimize(
  167. std::string(buf, result.ptr));
  168. }
  169. }
  170. BENCHMARK_F(RandFloatsComma, string_from_double_snprintf_fixed_comma)
  171. (benchmark::State &state)
  172. {
  173. number_serialiser_mk2 ser;
  174. while (state.KeepRunning())
  175. {
  176. benchmark::DoNotOptimize(
  177. ser.serialise(get_rand()));
  178. }
  179. }
  180. #endif