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
1315size_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
5240std::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+
7262void 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