Skip to content

Commit 9cd6dfc

Browse files
committed
Leave std::float32_t/std::float64_t unmodified on invalid input (fixes #424)
The fixed-width wrappers parsed into a zero-initialized temporary and copied it back unconditionally, so invalid input overwrote the destination with zero. Copy back only when the result is not invalid_argument, as required by [charconv.from.chars]/1.
1 parent 033f64f commit 9cd6dfc

2 files changed

Lines changed: 49 additions & 2 deletions

File tree

‎include/fast_float/parse_number.h‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,10 @@ template <> struct from_chars_caller<std::float32_t> {
158158
// float32_t
159159
float val = 0.0f;
160160
auto ret = from_chars_advanced(first, last, val, options);
161-
value = val;
161+
// If no characters match the pattern, value must be left unmodified.
162+
if (ret.ec != std::errc::invalid_argument) {
163+
value = val;
164+
}
162165
return ret;
163166
}
164167
};
@@ -175,7 +178,10 @@ template <> struct from_chars_caller<std::float64_t> {
175178
// float64_t
176179
double val = 0.0;
177180
auto ret = from_chars_advanced(first, last, val, options);
178-
value = val;
181+
// If no characters match the pattern, value must be left unmodified.
182+
if (ret.ec != std::errc::invalid_argument) {
183+
value = val;
184+
}
179185
return ret;
180186
}
181187
};

‎tests/fixedwidthtest.cpp‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,48 @@
22
#include <iostream>
33
#include <vector>
44
#include <cstring>
5+
#include <limits>
6+
#include <string_view>
57
#include "fast_float/fast_float.h"
68
#include <cstdint>
79

810
#if __cplusplus >= 202300L
911
#include <stdfloat>
1012
#endif
1113

14+
// If no characters match the pattern, value must be left unmodified
15+
// (see https://github.com/fastfloat/fast_float/issues/424).
16+
template <typename T> bool check_invalid_input_preserves_value() {
17+
std::vector<std::string_view> const invalid_inputs{"x", "", "-", ".", "e1",
18+
"-x", "na", "+"};
19+
for (auto const &f : invalid_inputs) {
20+
T result = T(42);
21+
auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result);
22+
if (answer.ec != std::errc::invalid_argument || answer.ptr != f.data()) {
23+
std::cerr << "Expected invalid_argument for input: \"" << f << "\""
24+
<< std::endl;
25+
return false;
26+
}
27+
if (result != T(42)) {
28+
std::cerr << "Value modified on invalid input: \"" << f << "\" got "
29+
<< result << std::endl;
30+
return false;
31+
}
32+
}
33+
// Out-of-range input must still store the documented value.
34+
std::string_view const too_large = "1e999";
35+
T result = T(42);
36+
auto answer = fast_float::from_chars(
37+
too_large.data(), too_large.data() + too_large.size(), result);
38+
if (answer.ec != std::errc::result_out_of_range ||
39+
result != std::numeric_limits<T>::infinity()) {
40+
std::cerr << "Expected infinity and result_out_of_range for \"1e999\""
41+
<< std::endl;
42+
return false;
43+
}
44+
return true;
45+
}
46+
1247
int main() {
1348
// Write some testcases for the parsing of floating point numbers in the
1449
// float32_t type. We use the from_chars function defined in this library.
@@ -33,6 +68,9 @@ int main() {
3368
return EXIT_FAILURE;
3469
}
3570
}
71+
if (!check_invalid_input_preserves_value<std::float32_t>()) {
72+
return EXIT_FAILURE;
73+
}
3674
#else
3775
std::cout << "No std::float32_t type available." << std::endl;
3876
#endif
@@ -60,6 +98,9 @@ int main() {
6098
return EXIT_FAILURE;
6199
}
62100
}
101+
if (!check_invalid_input_preserves_value<std::float64_t>()) {
102+
return EXIT_FAILURE;
103+
}
63104
#else
64105
std::cout << "No std::float64_t type available." << std::endl;
65106
#endif

0 commit comments

Comments
 (0)