// Copyright 2017 Elias Kosunen // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // This file is a part of scnlib: // https://github.com/eliaskosunen/scnlib #if defined(SCN_HEADER_ONLY) && SCN_HEADER_ONLY #define SCN_READER_FLOAT_CPP #endif #include #include #include #include #if SCN_HAS_FLOAT_CHARCONV #include #endif SCN_GCC_PUSH SCN_GCC_IGNORE("-Wold-style-cast") SCN_GCC_IGNORE("-Wnoexcept") SCN_GCC_IGNORE("-Wshift-count-overflow") SCN_GCC_IGNORE("-Wsign-conversion") SCN_CLANG_PUSH SCN_CLANG_IGNORE("-Wold-style-cast") #if SCN_CLANG >= SCN_COMPILER(13, 0, 0) SCN_CLANG_IGNORE("-Wreserved-identifier") #endif #if SCN_CLANG >= SCN_COMPILER(10, 0, 0) SCN_CLANG_IGNORE("-Wextra-semi-stmt") #endif #include SCN_CLANG_POP SCN_GCC_POP namespace scn { SCN_BEGIN_NAMESPACE namespace read_float { static bool is_hexfloat(const char* str, std::size_t len) noexcept { if (len < 3) { return false; } return str[0] == '0' && (str[1] == 'x' || str[1] == 'X'); } static bool is_hexfloat(const wchar_t* str, std::size_t len) noexcept { if (len < 3) { return false; } return str[0] == L'0' && (str[1] == L'x' || str[1] == L'X'); } namespace cstd { #if SCN_GCC >= SCN_COMPILER(7, 0, 0) SCN_GCC_PUSH SCN_GCC_IGNORE("-Wnoexcept-type") #endif template expected impl(F&& f_strtod, T huge_value, const CharT* str, size_t& chars, uint8_t options) { // Get current C locale const auto loc = std::setlocale(LC_NUMERIC, nullptr); // For whatever reason, this cannot be stored in the heap if // setlocale hasn't been called before, or msan errors with // 'use-of-unitialized-value' when resetting the locale back. // POSIX specifies that the content of loc may not be static, so // we need to save it ourselves char locbuf[64] = {0}; std::strcpy(locbuf, loc); std::setlocale(LC_NUMERIC, "C"); CharT* end{}; errno = 0; T f = f_strtod(str, &end); chars = static_cast(end - str); auto err = errno; // Reset locale std::setlocale(LC_NUMERIC, locbuf); errno = 0; SCN_GCC_COMPAT_PUSH SCN_GCC_COMPAT_IGNORE("-Wfloat-equal") // No conversion if (f == detail::zero_value::value && chars == 0) { return error(error::invalid_scanned_value, "strtod"); } // Range error if (err == ERANGE) { // Underflow if (f == detail::zero_value::value) { return error( error::value_out_of_range, "Floating-point value out of range: underflow"); } // Overflow if (f == huge_value || f == -huge_value) { return error( error::value_out_of_range, "Floating-point value out of range: overflow"); } // Subnormals cause ERANGE but a value is still returned } if (is_hexfloat(str, detail::strlen(str)) && (options & detail::float_scanner::allow_hex) == 0) { return error(error::invalid_scanned_value, "Hexfloats not allowed by the format string"); } SCN_GCC_COMPAT_POP return f; } #if SCN_GCC >= SCN_COMPILER(7, 0, 0) SCN_GCC_POP #endif template struct read; template <> struct read { static expected get(const char* str, size_t& chars, uint8_t options) { return impl(strtof, HUGE_VALF, str, chars, options); } }; template <> struct read { static expected get(const char* str, size_t& chars, uint8_t options) { return impl(strtod, HUGE_VAL, str, chars, options); } }; template <> struct read { static expected get(const char* str, size_t& chars, uint8_t options) { return impl(strtold, HUGE_VALL, str, chars, options); } }; template <> struct read { static expected get(const wchar_t* str, size_t& chars, uint8_t options) { return impl(wcstof, HUGE_VALF, str, chars, options); } }; template <> struct read { static expected get(const wchar_t* str, size_t& chars, uint8_t options) { return impl(wcstod, HUGE_VAL, str, chars, options); } }; template <> struct read { static expected get(const wchar_t* str, size_t& chars, uint8_t options) { return impl(wcstold, HUGE_VALL, str, chars, options); } }; } // namespace cstd namespace from_chars { #if SCN_HAS_FLOAT_CHARCONV template struct read { static expected get(const char* str, size_t& chars, uint8_t options) { const auto len = std::strlen(str); std::chars_format flags{}; if (((options & detail::float_scanner::allow_hex) != 0) && is_hexfloat(str, len)) { str += 2; flags = std::chars_format::hex; } else { if ((options & detail::float_scanner::allow_fixed) != 0) { flags |= std::chars_format::fixed; } if ((options & detail::float_scanner::allow_scientific) != 0) { flags |= std::chars_format::scientific; } } if (flags == static_cast(0)) { return error{error::invalid_scanned_value, "Expected a hexfloat"}; } T value{}; const auto result = std::from_chars(str, str + len, value, flags); if (result.ec == std::errc::invalid_argument) { return error(error::invalid_scanned_value, "from_chars"); } if (result.ec == std::errc::result_out_of_range) { // Out of range, may be subnormal -> fall back to strtod // On gcc std::from_chars doesn't parse subnormals return cstd::read::get(str, chars, options); } chars = static_cast(result.ptr - str); return value; } }; #else template struct read { static expected get(const char* str, size_t& chars, uint8_t options) { // Fall straight back to strtod return cstd::read::get(str, chars, options); } }; #endif } // namespace from_chars namespace fast_float { template expected impl(const char* str, size_t& chars, uint8_t options, char locale_decimal_point) { const auto len = std::strlen(str); if (((options & detail::float_scanner::allow_hex) != 0) && is_hexfloat(str, len)) { // fast_float doesn't support hexfloats return from_chars::read::get(str, chars, options); } T value{}; ::fast_float::parse_options flags{}; if ((options & detail::float_scanner::allow_fixed) != 0) { flags.format = ::fast_float::fixed; } if ((options & detail::float_scanner::allow_scientific) != 0) { flags.format = static_cast<::fast_float::chars_format>( flags.format | ::fast_float::scientific); } if ((options & detail::float_scanner::localized) != 0) { flags.decimal_point = locale_decimal_point; } 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"); } if (result.ec == std::errc::result_out_of_range) { return error(error::value_out_of_range, "fast_float"); } if (std::isinf(value)) { // fast_float represents very large or small values as inf // But, it also parses "inf", which from_chars does not if (!(len >= 3 && (str[0] == 'i' || str[0] == 'I'))) { // Input was not actually infinity -> // invalid result, fall back to from_chars return from_chars::read::get(str, chars, options); } } chars = static_cast(result.ptr - str); return value; } template struct read; template <> struct read { static expected get(const char* str, size_t& chars, uint8_t options, char locale_decimal_point) { return impl(str, chars, options, locale_decimal_point); } }; template <> struct read { static expected get(const char* str, size_t& chars, uint8_t options, char locale_decimal_points) { return impl(str, chars, options, locale_decimal_points); } }; template <> struct read { static expected get(const char* str, size_t& chars, uint8_t options, char) { // Fallback to strtod // fast_float doesn't support long double return cstd::read::get(str, chars, options); } }; } // namespace fast_float template struct read; template struct read { static expected get(const char* str, size_t& chars, uint8_t options, char locale_decimal_points) { // char -> default to fast_float, // fallback to strtod if necessary return read_float::fast_float::read::get( str, chars, options, locale_decimal_points); } }; template struct read { static expected get(const wchar_t* str, size_t& chars, uint8_t options, wchar_t) { // wchar_t -> straight to strtod return read_float::cstd::read::get(str, chars, options); } }; } // namespace read_float namespace detail { template template expected float_scanner::_read_float_impl( const CharT* str, size_t& chars, CharT locale_decimal_point) { // Parsing algorithm to use: // If CharT == wchar_t -> strtod // If CharT == char: // 1. fast_float // fallback if a hex float, or incorrectly parsed an inf // (very large or small value) // 2. std::from_chars // fallback if not available (C++17) or float is subnormal // 3. std::strtod return read_float::read::get(str, chars, format_options, locale_decimal_point); } #if SCN_INCLUDE_SOURCE_DEFINITIONS template expected float_scanner::_read_float_impl(const char*, size_t&, char); template expected float_scanner::_read_float_impl(const char*, size_t&, char); template expected float_scanner::_read_float_impl(const char*, size_t&, char); template expected float_scanner::_read_float_impl( const wchar_t*, size_t&, wchar_t); template expected float_scanner::_read_float_impl( const wchar_t*, size_t&, wchar_t); template expected float_scanner::_read_float_impl(const wchar_t*, size_t&, wchar_t); #endif } // namespace detail SCN_END_NAMESPACE } // namespace scn