From a69d57871ce6fb2b758243399a3df2c5c536fc82 Mon Sep 17 00:00:00 2001 From: Elias Kosunen Date: Wed, 1 Nov 2023 19:20:09 +0200 Subject: [PATCH] Fix handling of leading + with floats --- CHANGELOG.md | 6 ++++++ include/scn/reader/float.h | 18 +++++++++++++++++- include/scn/util/span.h | 7 +++++-- src/reader_float.cpp | 9 ++++++--- test/floating.cpp | 18 ++++++++++++++++++ 5 files changed, 52 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38a32d44..42465b96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,15 @@ _Released 2023-xx-xx_ +## Features + + * Parse leading `+` signs in floats (reported in #77) + ## Fixes + * Fix `scn::wrap(std::string_view&&)` being ambiguous on gcc (reported in #83) * Fix compiler error in `scn::basic_string_view::substr` (reported in #86) + * Improve error messages given from the float parser # 1.1.2 diff --git a/include/scn/reader/float.h b/include/scn/reader/float.h index 24265a10..58f2ad1b 100644 --- a/include/scn/reader/float.h +++ b/include/scn/reader/float.h @@ -131,6 +131,7 @@ namespace scn { auto do_parse_float = [&](span s) -> error { T tmp = 0; expected ret{0}; + int sign_offset{}; if (SCN_UNLIKELY((format_options & localized_digits) != 0 || ((common_options & localized) != 0 && (format_options & allow_hex) != 0))) { @@ -145,16 +146,31 @@ namespace scn { SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE } else { + SCN_EXPECT(!s.empty()); + bool has_negative_sign = false; + if (s[0] == char_type{'+'}) { + sign_offset = 1; + } + else if (s[0] == char_type{'-'}) { + has_negative_sign = true; + sign_offset = 1; + } ret = _read_float( - tmp, s, + tmp, s.subspan(sign_offset), ctx.locale() .get((common_options & localized) != 0) .decimal_point()); + if (has_negative_sign) { + SCN_EXPECT(std::isnan(tmp) || + tmp >= static_cast(0.0)); + tmp = -tmp; + } } if (!ret) { return ret.error(); } + ret.value() += sign_offset; if (ret.value() != s.ssize()) { auto pb = putback_n(ctx.range(), s.ssize() - ret.value()); diff --git a/include/scn/util/span.h b/include/scn/util/span.h index 1abdd6c2..b29fe2e5 100644 --- a/include/scn/util/span.h +++ b/include/scn/util/span.h @@ -35,8 +35,7 @@ namespace scn { using std::input_iterator_tag; using std::output_iterator_tag; using std::random_access_iterator_tag; - struct contiguous_iterator_tag : random_access_iterator_tag { - }; + struct contiguous_iterator_tag : random_access_iterator_tag {}; } // namespace custom_ranges /** @@ -164,6 +163,10 @@ namespace scn { { return m_end - m_ptr; } + SCN_NODISCARD constexpr bool empty() const noexcept + { + return size() == 0; + } SCN_CONSTEXPR14 span first(index_type n) const { diff --git a/src/reader_float.cpp b/src/reader_float.cpp index 77c66a51..ac91a069 100644 --- a/src/reader_float.cpp +++ b/src/reader_float.cpp @@ -107,7 +107,8 @@ namespace scn { SCN_GCC_COMPAT_IGNORE("-Wfloat-equal") // No conversion if (f == detail::zero_value::value && chars == 0) { - return error(error::invalid_scanned_value, "strtod"); + return error(error::invalid_scanned_value, + "strtod failed to parse float"); } // Range error if (err == ERANGE) { @@ -295,10 +296,12 @@ namespace scn { const auto result = ::fast_float::from_chars_advanced( str, str + len, value, flags); if (result.ec == std::errc::invalid_argument) { - return error(error::invalid_scanned_value, "fast_float"); + return error(error::invalid_scanned_value, + "fast_float failed to parse float"); } if (result.ec == std::errc::result_out_of_range) { - return error(error::value_out_of_range, "fast_float"); + return error(error::value_out_of_range, + "fast_float parsed an out-of-range float"); } if (std::isinf(value)) { // fast_float represents very large or small values as inf diff --git a/test/floating.cpp b/test/floating.cpp index 815f520d..709a6401 100644 --- a/test/floating.cpp +++ b/test/floating.cpp @@ -106,6 +106,24 @@ TEST_CASE_TEMPLATE_DEFINE("floating point", T, floating_test) CHECK(std::signbit(f)); CHECK(e); } + { + value_type f{}; + auto e = do_scan("+42.0", "{}", f); + CHECK(f == doctest::Approx(42.0)); + CHECK(e); + } + { + value_type f{}; + auto e = do_scan(" +42.0", "{}", f); + CHECK(f == doctest::Approx(42.0)); + CHECK(e); + } + { + value_type f{}; + auto e = do_scan("+", "{}", f); + CHECK(!e); + CHECK(e.error() == scn::error::invalid_scanned_value); + } { value_type f{1.0}; auto e =