Skip to content

Commit 89bc58b

Browse files
committed
add fixed-precision wrappers of compat algorithms
1 parent 947dfdd commit 89bc58b

File tree

3 files changed

+140
-74
lines changed

3 files changed

+140
-74
lines changed

benchmarks/algorithms.h

Lines changed: 138 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,24 @@
4747
template<arithmetic_float T>
4848
struct 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

6370
namespace 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.
10283
template<arithmetic_float T>
10384
int 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.
171177
template<arithmetic_float T>
172178
int grisu2(T d, std::span<char>& buffer) {
@@ -218,13 +224,15 @@ int teju_jagua(T d, std::span<char>& buffer) {
218224

219225
template<arithmetic_float T>
220226
int 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) {
277285
namespace BenchmarkFixedSize {
278286

279287
template<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

292324
template<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+
309375
template <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

316382
template <arithmetic_float T>
317383
std::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;

benchmarks/benchmark.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ void process(const std::vector<TestCase<T>> &lines,
9393
char buf[100];
9494
std::span<char> bufspan(buf, sizeof(buf));
9595
for (const auto d : lines)
96-
volume += algo.func(d.value, bufspan, algo.fixedSize);
96+
volume += algo.func(d.value, bufspan);
9797
return volume;
9898
}, algo.testRepeat);
9999
}

benchmarks/benchutil.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ void evaluate_properties_helper(Range&& cases,
101101
// representation using the fewest significant digits.
102102
// So we use dragonbox, which serves as the reference implementation.
103103
const size_t vRef = BenchmarkShortest::dragonbox(d, bufRef);
104-
const size_t vAlgo = algo.func(d, bufAlgo, algo.fixedSize);
104+
const size_t vAlgo = algo.func(d, bufAlgo);
105105

106106
std::string_view svRef{bufRef.data(), vRef},
107107
svAlgo{bufAlgo.data(), vAlgo};

0 commit comments

Comments
 (0)