Skip to content

Commit 311a988

Browse files
authored
Merge pull request #29 from fastfloat/exhaustive32_count_fix_fix
fixes float_to_hex and be more thorough in the tests + use dragonbox as reference
2 parents d406f41 + f40264b commit 311a988

File tree

3 files changed

+58
-25
lines changed

3 files changed

+58
-25
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/exhaustivefloat32.cpp

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <cstring>
88
#include <iostream>
99
#include <string_view>
10+
#include <charconv>
1011

1112
#include "algorithms.h"
1213
#include "cxxopts.hpp"
@@ -37,24 +38,25 @@ size_t count_significant_digits(std::string_view num_str) {
3738
}
3839

3940
std::string float_to_hex(float f) {
40-
if (std::isnan(f) || std::isinf(f)) {
41-
return fmt::format("{}", f); // Handle special cases
42-
}
43-
44-
uint32_t bits = std::bit_cast<uint32_t>(f);
45-
int exponent;
46-
float mantissa = std::frexp(f, &exponent); // Get mantissa and exponent
47-
uint32_t mantissa_bits = bits & 0x7FFFFF; // 23-bit mantissa
48-
int exp_bits = (bits >> 23) & 0xFF; // 8-bit exponent
49-
bool sign = bits >> 31; // Sign bit
50-
51-
// Adjust for IEEE 754 representation
52-
if (exp_bits == 0 && mantissa_bits == 0) {
53-
return "0x0p+0"; // Zero case
54-
}
41+
std::ostringstream oss;
42+
oss << std::hexfloat << f;
43+
return oss.str();
44+
}
5545

56-
// Convert to hex format
57-
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;
5860
}
5961

6062
void run_exhaustive32(bool errol) {
@@ -66,7 +68,11 @@ void run_exhaustive32(bool errol) {
6668

6769
for (const auto &algo : args) {
6870
if (!algo.used) {
69-
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);
7076
continue;
7177
}
7278
bool incorrect = false;
@@ -85,15 +91,42 @@ void run_exhaustive32(bool errol) {
8591
std::memcpy(&d, &i32, sizeof(float));
8692
if (std::isnan(d) || std::isinf(d))
8793
continue;
88-
// Reference output
89-
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);
9099
const size_t vAlgo = algo.func(d, bufAlgo);
91100

92101
std::string_view svRef{bufRef.data(), vRef};
93102
std::string_view svAlgo{bufAlgo.data(), vAlgo};
94103

95104
auto countRef = count_significant_digits(svRef);
96105
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+
}
97130
if (countRef != countAlgo) {
98131
incorrect = true;
99132
fmt::print(" mismatch: d = {}, bufRef = {}, bufAlgo = {}", float_to_hex(d),
@@ -121,12 +154,12 @@ int main(int argc, char **argv) {
121154
const auto result = options.parse(argc, argv);
122155

123156
if (result["help"].as<bool>()) {
124-
std::cout << options.help() << std::endl;
157+
fmt::print("{}\n", options.help());
125158
return EXIT_SUCCESS;
126159
}
127160
run_exhaustive32(result["errol"].as<bool>());
128161
} catch (const std::exception &e) {
129-
std::cout << "error parsing options: " << e.what() << std::endl;
162+
fmt::print("error parsing options: {}\n", e.what());
130163
return EXIT_FAILURE;
131164
}
132165
}

0 commit comments

Comments
 (0)