Skip to content

Commit 20f9eb7

Browse files
committed
Unify exhaustive32 and thoroughfloat64 evaluation
1 parent d832f8e commit 20f9eb7

File tree

3 files changed

+131
-196
lines changed

3 files changed

+131
-196
lines changed

benchmarks/benchutil.h

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
#ifndef BENCHUTIL_H
22
#define BENCHUTIL_H
33

4+
#include <fmt/format.h>
5+
46
#include <atomic>
57
#include <cfloat>
68
#include <cstdio>
79

10+
#include "algorithms.h"
811
#include "counters/event_counter.h"
912

1013
event_collector collector;
1114

15+
template <arithmetic_float T>
16+
struct TestCase {
17+
T value;
18+
std::optional<std::string> str_value;
19+
};
20+
1221
bool algo_filtered_out(const std::string &algo_name,
1322
const std::vector<std::string> &algo_filter) {
1423
if (algo_filter.empty())
@@ -21,6 +30,108 @@ bool algo_filtered_out(const std::string &algo_name,
2130
return true;
2231
}
2332

33+
template<arithmetic_float T, std::ranges::input_range Range>
34+
void evaluate_properties_helper(bool errol,
35+
const std::vector<std::string> &algo_filter,
36+
Range&& cases) {
37+
fmt::println("{:20} {:20}", "Algorithm", "Valid shortest serialization");
38+
const auto args = Benchmarks::initArgs<T>(errol);
39+
40+
// Get number of cases for progress display
41+
uint64_t total = 0;
42+
if constexpr (std::ranges::sized_range<Range>)
43+
total = static_cast<uint64_t>(std::ranges::size(cases));
44+
else if constexpr (std::is_same_v<T, float>)
45+
total = (1ULL << 32);
46+
const uint64_t progress_interval = (total > 0 ? total / 100 : 0);
47+
48+
for (const auto &algo : args) {
49+
if (!algo.used) {
50+
fmt::println("# skipping {}", algo.name);
51+
continue;
52+
}
53+
if (algo.func == Benchmarks::dragonbox<T>) {
54+
fmt::println("# skipping {} because it is the reference.", algo.name);
55+
continue;
56+
}
57+
if (algo_filtered_out(algo.name, algo_filter)) {
58+
fmt::println("# filtered out {}", algo.name);
59+
continue;
60+
}
61+
62+
fmt::print("# processing {}", algo.name);
63+
fflush(stdout);
64+
65+
bool incorrect = false;
66+
char buf1[100], buf2[100];
67+
std::span<char> bufRef(buf1, sizeof buf1), bufAlgo(buf2, sizeof buf2);
68+
69+
uint64_t count = 0;
70+
for (const auto &tc : cases) {
71+
if (progress_interval > 0 && (count++ % progress_interval) == 0) {
72+
std::printf(".");
73+
std::fflush(stdout);
74+
}
75+
76+
const T d = tc.value;
77+
const std::string sv = tc.str_value ? std::format("case: {};", *tc.str_value) : "";
78+
79+
if (std::isnan(d) || std::isinf(d))
80+
continue;
81+
82+
// Reference output, we cannot use std::to_chars here, because it produces
83+
// the shortest representation, which is not necessarily the same as the
84+
// representation using the fewest significant digits.
85+
// So we use dragonbox, which serves as the reference implementation.
86+
const size_t vRef = Benchmarks::dragonbox(d, bufRef);
87+
const size_t vAlgo = algo.func(d, bufAlgo);
88+
89+
std::string_view svRef{bufRef.data(), vRef},
90+
svAlgo{bufAlgo.data(), vAlgo};
91+
92+
auto countRef = count_significant_digits(svRef);
93+
auto countAlgo = count_significant_digits(svAlgo);
94+
auto backRef = parse_float<T>(svRef);
95+
auto backAlgo = parse_float<T>(svAlgo);
96+
97+
if(!backRef || !backAlgo) {
98+
incorrect = true;
99+
fmt::print(" parse error: {} d = {}, ref={}, algo={}",
100+
sv, float_to_hex<T>(d), svRef, svAlgo);
101+
fflush(stdout);
102+
break;
103+
}
104+
if(*backRef != d || *backAlgo != d)
105+
fmt::println("\n# Error: parsing the output with std::from_chars does not bring back the input.");
106+
if(*backRef != d) {
107+
incorrect = true;
108+
fmt::print(" ref mismatch: {} d = {}, backRef = {}; svRef = {}, svAlgo = {}",
109+
sv, float_to_hex<T>(d), *backRef, svRef, svAlgo);
110+
fflush(stdout);
111+
break;
112+
}
113+
if(*backAlgo != d) {
114+
incorrect = true;
115+
fmt::print(" algo mismatch: {} d = {}, backAlgo = {}; svRef = {}, svAlgo = {}, "
116+
"parsing the output with std::from_chars does not recover the original",
117+
sv, float_to_hex<T>(d), *backAlgo, svRef, svAlgo);
118+
fflush(stdout);
119+
break;
120+
}
121+
if (countRef != countAlgo) {
122+
incorrect = true;
123+
fmt::print(" mismatch: {} d = {}, bufRef = {}, bufAlgo = {}",
124+
sv, float_to_hex<T>(d), svRef, svAlgo);
125+
fflush(stdout);
126+
break;
127+
}
128+
}
129+
130+
fmt::print("\n");
131+
fmt::println("{:20} {:20}", algo.name, incorrect ? "no" : "yes");
132+
}
133+
}
134+
24135
template <class function_type>
25136
event_aggregate bench(const function_type &&function, size_t min_repeat = 10,
26137
size_t min_time_ns = 400'000'000,

benchmarks/exhaustivefloat32.cpp

Lines changed: 12 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,26 @@
11
#include <fmt/format.h>
22

3-
#include <array>
4-
#include <bit>
53
#include <cctype>
6-
#include <cmath>
7-
#include <cstring>
8-
#include <iostream>
9-
#include <string_view>
10-
#include <charconv>
114
#include <vector>
125

136
#include "algorithms.h"
147
#include "cxxopts.hpp"
158
#include "floatutils.h"
169
#include "benchutil.h"
1710

18-
void run_exhaustive32(bool errol, const std::vector<std::string>& algo_filter = {}) {
19-
fmt::println("{:20} {:20}", "Algorithm", "Valid shortest serialization");
20-
21-
std::array<Benchmarks::BenchArgs<float>, Benchmarks::COUNT> args;
22-
args = Benchmarks::initArgs<float>(errol);
23-
24-
for (const auto &algo : args) {
25-
if (!algo.used) {
26-
fmt::println("# skipping {}", algo.name);
27-
continue;
28-
}
29-
if (algo.func == Benchmarks::dragonbox<float>) {
30-
fmt::println("# skipping {} because it is the reference.", algo.name);
31-
continue;
32-
}
33-
if (algo_filtered_out(algo.name, algo_filter)) {
34-
fmt::println("# filtered out {}", algo.name);
35-
continue;
36-
}
11+
using Benchmarks::BenchArgs;
3712

38-
bool incorrect = false;
39-
char buf1[100], buf2[100];
40-
std::span<char> bufRef(buf1, sizeof(buf1)), bufAlgo(buf2, sizeof(buf2));
41-
fmt::print("# processing {}", algo.name);
42-
fflush(stdout);
43-
for (uint64_t i = 0; i < (1ULL << 32); ++i) {
44-
if (i % 0x2000000 == 0) {
45-
printf(".");
46-
fflush(stdout);
47-
}
48-
static_assert(sizeof(float) == sizeof(uint32_t));
49-
uint32_t i32(i);
50-
float d;
51-
std::memcpy(&d, &i32, sizeof(float));
52-
if (std::isnan(d) || std::isinf(d))
53-
continue;
54-
// Reference output, we cannot use std::to_chars here, because it produces
55-
// the shortest representation, which is not necessarily the same as the
56-
// representation using the fewest significant digits.
57-
// So we use dragonbox, which serves as the reference implementation.
58-
const size_t vRef = Benchmarks::dragonbox(d, bufRef);
59-
const size_t vAlgo = algo.func(d, bufAlgo);
60-
61-
std::string_view svRef{bufRef.data(), vRef};
62-
std::string_view svAlgo{bufAlgo.data(), vAlgo};
63-
64-
auto countRef = count_significant_digits(svRef);
65-
auto countAlgo = count_significant_digits(svAlgo);
66-
auto backRef = parse_float<float>(svRef);
67-
auto backAlgo = parse_float<float>(svAlgo);
68-
if(!backRef || !backAlgo) {
69-
incorrect = true;
70-
fmt::print(" parse error: d = {}, bufRef = {}, bufAlgo = {}",
71-
float_to_hex<float>(d), svRef, svAlgo);
72-
fflush(stdout);
73-
break;
74-
}
75-
if(*backRef != d || *backAlgo != d) {
76-
fmt::println("\n# Error: parsing the output with std::from_chars does not bring back the input.");
77-
}
78-
if(*backRef != d) {
79-
incorrect = true;
80-
fmt::print(" ref mismatch: d = {}, backRef = {}; svRef = {}, svAlgo = {}",
81-
float_to_hex<float>(d), *backRef, svRef, svAlgo);
82-
fflush(stdout);
83-
break;
84-
}
85-
if(*backAlgo != d) {
86-
incorrect = true;
87-
fmt::print(" algo mismatch: d = {}, backAlgo = {}; svRef = {}, svAlgo = {}, "
88-
"parsing the output with std::from_chars does not recover the original",
89-
float_to_hex<float>(d), *backAlgo, svRef, svAlgo);
90-
fflush(stdout);
91-
break;
92-
}
93-
if (countRef != countAlgo) {
94-
incorrect = true;
95-
fmt::print(" mismatch: d = {}, bufRef = {}, bufAlgo = {}",
96-
float_to_hex<float>(d), svRef, svAlgo);
97-
fflush(stdout);
98-
break;
99-
}
100-
}
101-
fmt::print("\n");
102-
fmt::println("{:20} {:20}", algo.name, incorrect == 0 ? "yes" : "no");
103-
}
13+
void run_exhaustive32(bool errol, const std::vector<std::string>& algo_filter = {}) {
14+
static_assert(sizeof(float) == sizeof(uint32_t));
15+
auto floats_view
16+
= std::views::iota(uint32_t{0})
17+
| std::views::take(1ULL << 32)
18+
| std::views::transform([](uint32_t i) {
19+
const float d = std::bit_cast<float>(i);
20+
return TestCase<float>{ d, std::nullopt };
21+
});
22+
23+
evaluate_properties_helper<float>(errol, algo_filter, floats_view);
10424
}
10525

10626
cxxopts::Options

benchmarks/thoroughfloat64.cpp

Lines changed: 8 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
11
#include <fmt/format.h>
22

3-
#include <array>
4-
#include <bit>
53
#include <cctype>
6-
#include <cmath>
7-
#include <cstring>
8-
#include <iostream>
94
#include <string>
10-
#include <string_view>
11-
#include <charconv>
125
#include <fstream>
136
#include <vector>
147

@@ -17,124 +10,35 @@
1710
#include "floatutils.h"
1811
#include "benchutil.h"
1912

20-
struct test_case {
21-
double value;
22-
std::string str_value;
23-
};
24-
2513
// Helper function to load doubles from a file
26-
std::vector<test_case> load_doubles_from_file(const std::string& filename) {
27-
std::vector<test_case> numbers;
14+
std::vector<TestCase<double>> load_doubles_from_file(const std::string& filename) {
15+
std::vector<TestCase<double>> numbers;
2816
std::ifstream file(filename);
2917

3018
if (!file.is_open()) {
31-
fmt::print("Error: Could not open file {}\n", filename);
19+
fmt::println("Error: Could not open file {}", filename);
3220
return numbers;
3321
}
3422

3523
for (std::string line; std::getline(file, line);) {
3624
if (auto num = parse_float<double>(line))
37-
numbers.emplace_back(*num,line);
25+
numbers.emplace_back(*num, line);
3826
else
39-
fmt::print("Warning: Could not parse '{}' as double, skipping\n", line);
27+
fmt::println("Warning: Could not parse '{}' as double, skipping", line);
4028
}
4129

4230
file.close();
4331
return numbers;
4432
}
4533

4634
void run_file_test(const std::string& filename, bool errol, const std::vector<std::string>& algo_filter = {}) {
47-
fmt::println("{:20} {:20}", "Algorithm", "Valid shortest serialization");
48-
49-
std::array<Benchmarks::BenchArgs<double>, Benchmarks::COUNT> args;
50-
args = Benchmarks::initArgs<double>(errol);
51-
52-
// Load the doubles from file
53-
auto test_values = load_doubles_from_file(filename);
35+
const auto test_values = load_doubles_from_file(filename);
5436
if (test_values.empty()) {
55-
fmt::print("No valid numbers to test\n");
37+
fmt::println("No valid numbers to test");
5638
return;
5739
}
5840

59-
for (const auto &algo : args) {
60-
if (!algo.used) {
61-
fmt::println("# skipping {}", algo.name);
62-
continue;
63-
}
64-
if (algo.func == Benchmarks::dragonbox<double>) {
65-
fmt::println("# skipping {} because it is the reference.", algo.name);
66-
continue;
67-
}
68-
if (algo_filtered_out(algo.name, algo_filter)) {
69-
fmt::println("# filtered out {}", algo.name);
70-
continue;
71-
}
72-
73-
bool incorrect = false;
74-
char buf1[100], buf2[100];
75-
std::span<char> bufRef(buf1, sizeof(buf1)), bufAlgo(buf2, sizeof(buf2));
76-
fmt::print("# processing {}", algo.name);
77-
fflush(stdout);
78-
79-
size_t total = test_values.size();
80-
for (size_t i = 0; i < total; ++i) {
81-
if (i % (total/10) == 0 && total > 10) {
82-
printf(".");
83-
fflush(stdout);
84-
}
85-
double d = test_values[i].value;
86-
const std::string& str_value = test_values[i].str_value;
87-
if (std::isnan(d) || std::isinf(d))
88-
continue;
89-
90-
const size_t vRef = Benchmarks::dragonbox(d, bufRef);
91-
const size_t vAlgo = algo.func(d, bufAlgo);
92-
93-
std::string_view svRef{bufRef.data(), vRef};
94-
std::string_view svAlgo{bufAlgo.data(), vAlgo};
95-
//fmt::print(" RESULT {}: {} ", algo.name, svAlgo);
96-
97-
auto countRef = count_significant_digits(svRef);
98-
auto countAlgo = count_significant_digits(svAlgo);
99-
auto backRef = parse_float<double>(svRef);
100-
auto backAlgo = parse_float<double>(svAlgo);
101-
102-
if(!backRef || !backAlgo) {
103-
incorrect = true;
104-
fmt::print(" parse error: case: {}; d = {}, bufRef = {}, bufAlgo = {}",
105-
str_value, float_to_hex<double>(d), svRef, svAlgo);
106-
fflush(stdout);
107-
break;
108-
}
109-
if(*backRef != d || *backAlgo != d) {
110-
fmt::println("\n# Error: parsing the output with std::from_chars does not bring back the input.");
111-
}
112-
if(*backRef != d) {
113-
incorrect = true;
114-
fmt::print(" ref mismatch:case: {}; d = {}, backRef = {}; svRef = {}, svAlgo = {}",
115-
str_value, float_to_hex<double>(d), *backRef, svRef, svAlgo);
116-
fflush(stdout);
117-
break;
118-
}
119-
if(*backAlgo != d) {
120-
incorrect = true;
121-
fmt::print(" algo mismatch: case: {}; d = {}, backAlgo = {}; svRef = {}, svAlgo = {}, "
122-
"parsing the output with std::from_chars does not recover the original",
123-
str_value, float_to_hex<double>(d), *backAlgo, svRef, svAlgo);
124-
fflush(stdout);
125-
break;
126-
}
127-
if (countRef != countAlgo) {
128-
incorrect = true;
129-
fmt::print(" mismatch: case: {}; d = {}, bufRef = {}, bufAlgo = {}",
130-
str_value, float_to_hex<double>(d), svRef, svAlgo);
131-
fflush(stdout);
132-
break;
133-
}
134-
}
135-
fmt::print("\n");
136-
fmt::println("{:20} {:20}", algo.name, incorrect == 0 ? "yes" : "no");
137-
}
41+
evaluate_properties_helper<double>(errol, algo_filter, test_values);
13842
}
13943

14044
cxxopts::Options

0 commit comments

Comments
 (0)