Skip to content

Commit a213fa3

Browse files
authored
Merge pull request #28 from fastfloat/exhaustive32_count_fix
count_significant_digits stop counting trailing zeroes
2 parents 5d3954c + 311a988 commit a213fa3

File tree

4 files changed

+75
-54
lines changed

4 files changed

+75
-54
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
cmake_minimum_required(VERSION 3.29)
1+
cmake_minimum_required(VERSION 3.28)
22

33
project(SimpleFastFloatBenchmark VERSION 0.1.0 LANGUAGES CXX C)
44
set(CMAKE_CXX_STANDARD 20)

benchmarks/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@ target_include_directories(benchmark_deps INTERFACE ${grisu-exact_SOURCE_DIR})
8989

9090
target_link_libraries(benchmark PUBLIC benchmark_deps)
9191

92-
if(TO_CHARS_OK)
92+
if(TO_CHARS_OK AND FROM_CHARS_OK)
9393
add_executable(exhaustivefloat32
9494
exhaustivefloat32.cpp
9595
)
9696
target_link_libraries(exhaustivefloat32 PUBLIC benchmark_deps)
97-
endif(TO_CHARS_OK)
97+
endif(TO_CHARS_OK AND FROM_CHARS_OK)
9898

benchmarks/algorithms.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include <fmt/format.h>
2121

22+
#include <array>
2223
#include <span>
2324

2425
#include "cpp/common/traits.hpp" // Teju Jagua
@@ -310,7 +311,6 @@ std::array<BenchArgs<T>, Benchmarks::COUNT> initArgs(bool errol = false) {
310311
return args;
311312
};
312313

313-
314314
} // namespace Benchmarks
315315

316316
#endif

benchmarks/exhaustivefloat32.cpp

Lines changed: 71 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,64 @@
1+
#include <fmt/format.h>
12

2-
#include "algorithms.h"
3-
#include "cxxopts.hpp"
43
#include <array>
54
#include <bit>
65
#include <cctype>
76
#include <cmath>
87
#include <cstring>
9-
#include <fmt/format.h>
108
#include <iostream>
119
#include <string_view>
10+
#include <charconv>
11+
12+
#include "algorithms.h"
13+
#include "cxxopts.hpp"
1214

1315
size_t count_significant_digits(std::string_view num_str) {
1416
size_t count = 0;
15-
bool has_decimal = false;
16-
bool in_exponent = false;
17+
size_t trailing_zeros = 0;
1718
bool leading_zero = true;
1819

1920
for (char c : num_str) {
20-
if (c == '.') {
21-
has_decimal = true;
21+
if (c == '.')
2222
continue;
23-
}
24-
if (c == 'e' || c == 'E') {
25-
in_exponent = true;
26-
continue;
27-
}
23+
if (c == 'e' || c == 'E')
24+
break; // Stop counting at exponent
2825
if (std::isdigit(static_cast<unsigned char>(c))) {
29-
if (!in_exponent) {
30-
if (leading_zero && c == '0') {
31-
// Skip leading zeros before decimal
32-
continue;
33-
}
34-
leading_zero = false;
35-
count++;
26+
if (c == '0') {
27+
if (!leading_zero)
28+
trailing_zeros++;
29+
continue;
3630
}
37-
}
38-
}
39-
40-
// Special case: "X.0" should count as 1 digit
41-
if (has_decimal && count > 1) {
42-
auto last_digit_pos = num_str.find_last_not_of("0eE+-");
43-
if (last_digit_pos != std::string_view::npos &&
44-
num_str[last_digit_pos] == '.' && count == 2) {
45-
return 1;
31+
leading_zero = false;
32+
count += trailing_zeros + 1;
33+
trailing_zeros = 0;
4634
}
4735
}
4836

4937
return count;
5038
}
5139

5240
std::string float_to_hex(float f) {
53-
if (std::isnan(f) || std::isinf(f)) {
54-
return fmt::format("{}", f); // Handle special cases
55-
}
56-
57-
uint32_t bits = std::bit_cast<uint32_t>(f);
58-
int exponent;
59-
float mantissa = std::frexp(f, &exponent); // Get mantissa and exponent
60-
uint32_t mantissa_bits = bits & 0x7FFFFF; // 23-bit mantissa
61-
int exp_bits = (bits >> 23) & 0xFF; // 8-bit exponent
62-
bool sign = bits >> 31; // Sign bit
63-
64-
// Adjust for IEEE 754 representation
65-
if (exp_bits == 0 && mantissa_bits == 0) {
66-
return "0x0p+0"; // Zero case
67-
}
41+
std::ostringstream oss;
42+
oss << std::hexfloat << f;
43+
return oss.str();
44+
}
6845

69-
// Convert to hex format
70-
return fmt::format("0x1.{:06x}p{:+d}", mantissa_bits, exponent - 23);
46+
std::optional<float> parse_float(std::string_view sv) {
47+
float result;
48+
const char* begin = sv.data();
49+
const char* end = sv.data() + sv.size();
50+
51+
auto [ptr, ec] = std::from_chars(begin, end, result);
52+
53+
// Check if parsing succeeded and consumed the entire string
54+
if (ec == std::errc{} && ptr == end) {
55+
return result;
56+
}
57+
58+
// Return nullopt if parsing failed or didn't consume all input
59+
return std::nullopt;
7160
}
61+
7262
void run_exhaustive32(bool errol) {
7363
constexpr auto precision = std::numeric_limits<float>::digits10;
7464
fmt::println("{:20} {:20}", "Algorithm", "Valid shortest serialization");
@@ -78,7 +68,11 @@ void run_exhaustive32(bool errol) {
7868

7969
for (const auto &algo : args) {
8070
if (!algo.used) {
81-
std::cout << "# skipping " << algo.name << std::endl;
71+
fmt::print("# skipping {}\n", algo.name);
72+
continue;
73+
}
74+
if (algo.func == Benchmarks::dragonbox<float>) {
75+
fmt::print("# skipping {} because it is the reference.\n", algo.name);
8276
continue;
8377
}
8478
bool incorrect = false;
@@ -97,15 +91,42 @@ void run_exhaustive32(bool errol) {
9791
std::memcpy(&d, &i32, sizeof(float));
9892
if (std::isnan(d) || std::isinf(d))
9993
continue;
100-
// Reference output
101-
const size_t vRef = Benchmarks::std_to_chars(d, bufRef);
94+
// Reference output, we cannot use std::to_chars here, because it produces
95+
// the shortest representation, which is not necessarily the same as the
96+
// as the representation using the fewest significant digits.
97+
// So we use dragonbox, which serves as the reference implementation.
98+
const size_t vRef = Benchmarks::dragonbox(d, bufRef);
10299
const size_t vAlgo = algo.func(d, bufAlgo);
103100

104101
std::string_view svRef{bufRef.data(), vRef};
105102
std::string_view svAlgo{bufAlgo.data(), vAlgo};
106103

107104
auto countRef = count_significant_digits(svRef);
108105
auto countAlgo = count_significant_digits(svAlgo);
106+
auto backRef = parse_float(svRef);
107+
auto backAlgo = parse_float(svAlgo);
108+
if(!backRef || !backAlgo) {
109+
incorrect = true;
110+
fmt::print(" parse error: d = {}, bufRef = {}, bufAlgo = {}", float_to_hex(d),
111+
svRef, svAlgo);
112+
fflush(stdout);
113+
break;
114+
}
115+
if(*backRef != d || *backAlgo != d) {
116+
fmt::println("\n# Error: parsing the output with std::from_chars does not bring back the input.");
117+
}
118+
if(*backRef != d) {
119+
incorrect = true;
120+
fmt::print(" ref mismatch: d = {}, backRef = {}", d, *backRef);
121+
fflush(stdout);
122+
break;
123+
}
124+
if(*backAlgo != d) {
125+
incorrect = true;
126+
fmt::print(" algo mismatch: d = {}, backAlgo = {}, parsing the output with std::from_chars does not recover the original", d, *backAlgo);
127+
fflush(stdout);
128+
break;
129+
}
109130
if (countRef != countAlgo) {
110131
incorrect = true;
111132
fmt::print(" mismatch: d = {}, bufRef = {}, bufAlgo = {}", float_to_hex(d),
@@ -133,12 +154,12 @@ int main(int argc, char **argv) {
133154
const auto result = options.parse(argc, argv);
134155

135156
if (result["help"].as<bool>()) {
136-
std::cout << options.help() << std::endl;
157+
fmt::print("{}\n", options.help());
137158
return EXIT_SUCCESS;
138159
}
139160
run_exhaustive32(result["errol"].as<bool>());
140161
} catch (const std::exception &e) {
141-
std::cout << "error parsing options: " << e.what() << std::endl;
162+
fmt::print("error parsing options: {}\n", e.what());
142163
return EXIT_FAILURE;
143164
}
144165
}

0 commit comments

Comments
 (0)