4747template <arithmetic_float T>
4848struct BenchArgs {
4949 using Type = T;
50- using BenchFn = std::function<int (T, std::span<char >&, size_t fixed_size )>;
50+ using BenchFn = std::function<int (T, std::span<char >&)>;
5151
52- BenchArgs (const std::string& name = {}, BenchFn func = {}, bool used = true ,
53- size_t testRepeat = 100 , size_t fixedSize = 9 )
54- : name(name), func(func), used(used), testRepeat(testRepeat), fixedSize(fixedSize) {}
52+ BenchArgs (const std::string& name = {}, BenchFn func = {}, bool used = true , size_t testRepeat = 100 )
53+ : name(name), func(func), used(used), testRepeat(testRepeat) {}
5554
5655 std::string name{};
5756 BenchFn func{};
5857 bool used{};
5958 size_t testRepeat{100 };
60- size_t fixedSize{9 };
59+
60+ static void initFixedSize (size_t size) {
61+ fixedSize = size;
62+ snprintf (formatStr, sizeof (formatStr), " %%.%zug" , fixedSize);
63+ formatStrStr = fmt::format (" {{:.{}g}}" , fixedSize);
64+ }
65+ static inline size_t fixedSize;
66+ static inline char formatStr[10 ];
67+ static inline std::string formatStrStr;
6168};
6269
6370namespace BenchmarkShortest {
@@ -72,32 +79,6 @@ int dragon4(T d, std::span<char>& buffer) {
7279 PrintFloatFormat_Positional, -1 );
7380}
7481
75- // No errol3 implementation optimized for float instead of double ?
76- template <arithmetic_float T>
77- int errol3 (T d, std::span<char >& buffer) {
78- #if ERROL_SUPPORTED
79- errol3_dtoa (d, buffer.data ()); // returns the exponent
80- return std::strlen (buffer.data ());
81- #else
82- std::cerr << " errol3 not supported" << std::endl;
83- std::abort ();
84- #endif
85- }
86-
87- template <arithmetic_float T>
88- int to_string (T d, std::span<char >& buffer) {
89- const std::string s = std::to_string (d);
90- std::copy (s.begin (), s.end (), buffer.begin ());
91- return s.size ();
92- }
93-
94- template <arithmetic_float T>
95- int fmt_format (T d, std::span<char >& buffer) {
96- const std::string s = fmt::format (" {}" , d);
97- std::copy (s.begin (), s.end (), buffer.begin ());
98- return s.size ();
99- }
100-
10182// There's no "ftoa", only "dtoa", so not optimized for float.
10283template <arithmetic_float T>
10384int netlib (T d, std::span<char >& buffer) {
@@ -137,7 +118,7 @@ int netlib(T d, std::span<char>& buffer) {
137118 } else {
138119 buffer[i++] = ' 0' + value;
139120 }
140- };
121+ };
141122 // Fractional part (if any remaining digits)
142123 const int remaining_digits = rve - (result + std::max (0 , decpt));
143124 if (remaining_digits > 0 ) {
@@ -167,6 +148,31 @@ int netlib(T d, std::span<char>& buffer) {
167148#endif
168149}
169150
151+ // No errol3 implementation optimized for float instead of double ?
152+ template <arithmetic_float T>
153+ int errol3 (T d, std::span<char >& buffer) {
154+ #if ERROL_SUPPORTED
155+ errol3_dtoa (d, buffer.data ()); // returns the exponent
156+ return std::strlen (buffer.data ());
157+ #else
158+ std::cerr << " errol3 not supported" << std::endl;
159+ std::abort ();
160+ #endif
161+ }
162+
163+ template <arithmetic_float T>
164+ int to_string (T d, std::span<char >& buffer) {
165+ const std::string s = std::to_string (d);
166+ std::copy (s.begin (), s.end (), buffer.begin ());
167+ return s.size ();
168+ }
169+
170+ template <arithmetic_float T>
171+ int fmt_format (T d, std::span<char >& buffer) {
172+ const auto it = fmt::format_to (buffer.data (), " {}" , d);
173+ return std::distance (buffer.data (), it);
174+ }
175+
170176// grisu2 is hardcoded for double.
171177template <arithmetic_float T>
172178int grisu2 (T d, std::span<char >& buffer) {
@@ -218,13 +224,15 @@ int teju_jagua(T d, std::span<char>& buffer) {
218224
219225template <arithmetic_float T>
220226int double_conversion (T d, std::span<char >& buffer) {
221- const static double_conversion::DoubleToStringConverter converter (
222- double_conversion::DoubleToStringConverter::NO_FLAGS, " inf" , " nan" , ' e' ,
223- -4 , 6 , 0 , 0 );
227+ using namespace double_conversion ;
228+ const static DoubleToStringConverter conv (
229+ DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN | DoubleToStringConverter::UNIQUE_ZERO,
230+ " inf" , " nan" , ' e' , -4 , 6 , 0 , 0 );
231+
224232 double_conversion::StringBuilder builder (buffer.data (), buffer.size ());
225233 const bool valid = std::is_same_v<T, float >
226- ? converter .ToShortestSingle (d, &builder)
227- : converter .ToShortest (d, &builder);
234+ ? conv .ToShortestSingle (d, &builder)
235+ : conv .ToShortest (d, &builder);
228236 if (!valid) {
229237 std::cerr << " problem with " << d << std::endl;
230238 std::abort ();
@@ -277,53 +285,110 @@ int std_to_chars(T d, std::span<char>& buffer) {
277285namespace BenchmarkFixedSize {
278286
279287template <arithmetic_float T>
280- int abseil (T d, std::span<char >& buffer, size_t fixed_size) {
288+ int dragon4 (T d, std::span<char >& buffer) {
289+ if constexpr (std::is_same_v<T, float >)
290+ return PrintFloat32 (buffer.data (), buffer.size (), d,
291+ PrintFloatFormat_Positional, BenchArgs<T>::fixedSize);
292+ else
293+ return PrintFloat64 (buffer.data (), buffer.size (), d,
294+ PrintFloatFormat_Positional, BenchArgs<T>::fixedSize);
295+ }
296+
297+ template <arithmetic_float T>
298+ int netlib (T d, std::span<char >& buffer) {
299+ #if NETLIB_SUPPORTED
300+ char * res;
301+ if constexpr (std::is_same_v<T, float >)
302+ res = g_ffmt (buffer.data (), &d, BenchArgs<T>::fixedSize, buffer.size ());
303+ else
304+ res = g_dfmt (buffer.data (), &d, BenchArgs<T>::fixedSize, buffer.size ());
305+ *res = ' \0 ' ;
306+ return res - buffer.data () + 1 ;
307+ #else
308+ std::cerr << " netlib not supported" << std::endl;
309+ std::abort ();
310+ #endif
311+ }
312+
313+ template <arithmetic_float T>
314+ int abseil (T d, std::span<char >& buffer) {
281315 // StrAppend is faster but only outputs 6 digits after the decimal point
282316 // std::string s;
283317 // absl::StrAppend(&s, d);
284318 // std::copy(s.begin(), s.end(), buffer.begin());
285319 // return size(s);
286- if constexpr (std::is_same_v<T, float >)
287- return absl::SNPrintF (buffer.data (), buffer.size (), " %.9g" , d);
288- else
289- return absl::SNPrintF (buffer.data (), buffer.size (), " %.17g" , d);
320+ return absl::SNPrintF (buffer.data (), buffer.size (),
321+ BenchArgs<T>::formatStr, d);
290322}
291323
292324template <arithmetic_float T>
293- int snprintf (T d, std::span<char >& buffer, size_t fixed_size) {
294- if constexpr (std::is_same_v<T, float >)
295- return std::snprintf (buffer.data (), buffer.size (), " %.9g" , d);
296- else
297- return std::snprintf (buffer.data (), buffer.size (), " %.17g" , d);
325+ int snprintf (T d, std::span<char >& buffer) {
326+ return std::snprintf (buffer.data (), buffer.size (),
327+ BenchArgs<T>::formatStr, d);
298328}
299329
300- } // namespace BenchmarksShortest
330+ template <arithmetic_float T>
331+ int fmt_format (T d, std::span<char >& buffer) {
332+ const auto it = fmt::format_to (buffer.begin (),
333+ fmt::runtime (BenchArgs<T>::formatStrStr), d);
334+ return std::distance (buffer.begin (), it);
335+ }
301336
302- template <typename T>
303- auto make_shortest_adapter (int (*fn)(T, std::span<char >&)) {
304- return [fn](T v, std::span<char >& buf, size_t /* fixed_size*/ ) -> int {
305- return fn (v, buf);
306- };
337+ template <arithmetic_float T>
338+ int ryu (T d, std::span<char >& buffer) {
339+ return d2fixed_buffered_n (d, BenchArgs<T>::fixedSize, buffer.data ());
307340}
308341
342+ template <arithmetic_float T>
343+ int double_conversion (T d, std::span<char >& buffer) {
344+ const static double_conversion::DoubleToStringConverter conv (
345+ double_conversion::DoubleToStringConverter::NO_FLAGS, " inf" , " nan" , ' e' ,
346+ -6 , 21 , BenchArgs<T>::fixedSize, BenchArgs<T>::fixedSize);
347+
348+ double_conversion::StringBuilder builder (buffer.data (), buffer.size ());
349+ if (!conv.ToPrecision (d, BenchArgs<T>::fixedSize, &builder)) {
350+ std::cerr << " problem with " << d << std::endl;
351+ std::abort ();
352+ }
353+ return strlen (builder.Finalize ());
354+ }
355+
356+ template <arithmetic_float T>
357+ int std_to_chars (T d, std::span<char >& buffer) {
358+ #if TO_CHARS_SUPPORTED
359+ const auto [p, ec]
360+ = std::to_chars (buffer.data (), buffer.data () + buffer.size (), d,
361+ std::chars_format::general, BenchArgs<T>::fixedSize);
362+ if (ec != std::errc ()) {
363+ std::cerr << " problem with " << d << std::endl;
364+ std::abort ();
365+ }
366+ return p - buffer.data ();
367+ #else
368+ std::cerr << " std::to_chars not supported" << std::endl;
369+ std::abort ();
370+ #endif
371+ }
372+
373+ } // namespace BenchmarksShortest
374+
309375template <typename T>
310- auto make_fixed_adapter (int (*fn)(T, std::span<char >&, size_t )) {
311- return [fn](T v, std::span<char >& buf, size_t fixed_size ) -> int {
312- return fn (v, buf, fixed_size );
376+ auto wrap (int (*fn)(T, std::span<char >&)) {
377+ return [fn](T v, std::span<char >& buf) -> int {
378+ return fn (v, buf);
313379 };
314380}
315381
316382template <arithmetic_float T>
317383std::vector<BenchArgs<T>> initArgs (bool use_errol = false , size_t repeat = 0 , size_t fixed_size = 0 ) {
318384 std::vector<BenchArgs<T>> args;
319385 if (fixed_size == 0 ) { // shortest-length representation
320- auto && wrap = make_shortest_adapter<T>;
321386 namespace s = BenchmarkShortest;
322387 args.emplace_back (" dragon4" , wrap (s::dragon4<T>) , true , 10 );
323388 args.emplace_back (" netlib" , wrap (s::netlib<T>) , NETLIB_SUPPORTED && std::is_same_v<T, double > , 10 );
324389 args.emplace_back (" errol3" , wrap (s::errol3<T>) , ERROL_SUPPORTED && use_errol);
325390 args.emplace_back (" fmt_format" , wrap (s::fmt_format<T>) , true );
326- args.emplace_back (" grisu2" , wrap (s::grisu2<T>) , std::is_same_v<T, double >);
391+ // args.emplace_back("grisu2" , wrap(s::grisu2<T>) , std::is_same_v<T, double>);
327392 args.emplace_back (" grisu3" , wrap (s::grisu3<T>) , std::is_same_v<T, double >);
328393 args.emplace_back (" grisu_exact" , wrap (s::grisu_exact<T>) , true );
329394 args.emplace_back (" schubfach" , wrap (s::schubfach<T>) , true );
@@ -334,26 +399,27 @@ std::vector<BenchArgs<T>> initArgs(bool use_errol = false, size_t repeat = 0, si
334399 args.emplace_back (" swiftDtoa" , wrap (s::swiftDtoa<T>) , SWIFT_LIB_SUPPORTED);
335400 args.emplace_back (" yy_double" , wrap (s::yy_double<T>) , YY_DOUBLE_SUPPORTED && std::is_same_v<T, double >);
336401 args.emplace_back (" std::to_chars" , wrap (s::std_to_chars<T>) , TO_CHARS_SUPPORTED);
337-
338402 // to_string, snprintf and abseil do not support shortest-length representation
403+ // grisu2 does not round-trip correctly
339404 } else { // fixed-length representation
340- auto && wrap = make_fixed_adapter<T>;
341- namespace f = BenchmarkFixedSize;
342- args.emplace_back (" snprintf" , wrap (f::snprintf<T>) , true );
343- args.emplace_back (" abseil" , wrap (f::abseil<T>) , ABSEIL_SUPPORTED);
344-
345- // to_string is hard-coded for 6 digits after the decimal point
346- // args.emplace_back("to_string", BenchmarkFixedSize::to_string<T>, true);
347-
348405 fmt::println (" # testing fixed-size output to {} digits" , fixed_size);
349- for (auto &arg : args)
350- arg.fixedSize = fixed_size;
406+ BenchArgs<T>::initFixedSize (fixed_size);
407+
408+ namespace f = BenchmarkFixedSize;
409+ args.emplace_back (" dragon4" , wrap (f::dragon4<T>) , true , 10 );
410+ args.emplace_back (" netlib" , wrap (f::netlib<T>) , NETLIB_SUPPORTED , 10 );
411+ args.emplace_back (" abseil" , wrap (f::abseil<T>) , ABSEIL_SUPPORTED);
412+ args.emplace_back (" snprintf" , wrap (f::snprintf<T>) , true );
413+ args.emplace_back (" fmt_format" , wrap (f::fmt_format<T>) , true );
414+ args.emplace_back (" ryu" , wrap (f::ryu<T>) , std::is_same_v<T, double >);
415+ args.emplace_back (" double_conversion" , wrap (f::double_conversion<T>) , true );
416+ args.emplace_back (" std::to_chars" , wrap (f::std_to_chars<T>) , TO_CHARS_SUPPORTED);
351417 }
352418
353419 if (repeat > 0 ) {
354- fmt::println (" # forcing repeat count to {}" , repeat);
355- for (auto &arg : args)
356- arg.testRepeat = repeat;
420+ fmt::println (" # forcing repeat count to {}" , repeat);
421+ for (auto &arg : args)
422+ arg.testRepeat = repeat;
357423 }
358424
359425 return args;
0 commit comments