| 1 | // Copyright 2020-2023 Daniel Lemire |
| 2 | // Copyright 2023 Matt Borland |
| 3 | // Distributed under the Boost Software License, Version 1.0. |
| 4 | // https://www.boost.org/LICENSE_1_0.txt |
| 5 | // |
| 6 | // Derivative of: https://github.com/fastfloat/fast_float |
| 7 | |
| 8 | #ifndef BOOST_CHARCONV_DETAIL_FASTFLOAT_PARSE_NUMBER_HPP |
| 9 | #define BOOST_CHARCONV_DETAIL_FASTFLOAT_PARSE_NUMBER_HPP |
| 10 | |
| 11 | #include <boost/charconv/detail/fast_float/ascii_number.hpp> |
| 12 | #include <boost/charconv/detail/fast_float/decimal_to_binary.hpp> |
| 13 | #include <boost/charconv/detail/fast_float/digit_comparison.hpp> |
| 14 | #include <boost/charconv/detail/fast_float/float_common.hpp> |
| 15 | |
| 16 | #include <cmath> |
| 17 | #include <cstring> |
| 18 | #include <limits> |
| 19 | #include <system_error> |
| 20 | |
| 21 | namespace boost { namespace charconv { namespace detail { namespace fast_float { |
| 22 | |
| 23 | |
| 24 | namespace detail { |
| 25 | /** |
| 26 | * Special case +inf, -inf, nan, infinity, -infinity. |
| 27 | * The case comparisons could be made much faster given that we know that the |
| 28 | * strings a null-free and fixed. |
| 29 | **/ |
| 30 | |
| 31 | #if defined(__GNUC__) && __GNUC__ < 5 && !defined(__clang__) |
| 32 | # pragma GCC diagnostic push |
| 33 | # pragma GCC diagnostic ignored "-Wmissing-field-initializers" |
| 34 | #endif |
| 35 | |
| 36 | template <typename T, typename UC> |
| 37 | from_chars_result_t<UC> BOOST_CHARCONV_FASTFLOAT_CONSTEXPR14 |
| 38 | parse_infnan(UC const * first, UC const * last, T &value) noexcept { |
| 39 | from_chars_result_t<UC> answer{}; |
| 40 | answer.ptr = first; |
| 41 | answer.ec = std::errc(); // be optimistic |
| 42 | bool minusSign = false; |
| 43 | if (*first == UC('-')) { // assume first < last, so dereference without checks; C++17 20.19.3.(7.1) explicitly forbids '+' here |
| 44 | minusSign = true; |
| 45 | ++first; |
| 46 | } |
| 47 | #ifdef BOOST_CHARCONV_FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default |
| 48 | if (*first == UC('+')) { |
| 49 | ++first; |
| 50 | } |
| 51 | #endif |
| 52 | if (last - first >= 3) { |
| 53 | if (fastfloat_strncasecmp(first, str_const_nan<UC>(), 3)) { |
| 54 | answer.ptr = (first += 3); |
| 55 | value = minusSign ? -std::numeric_limits<T>::quiet_NaN() : std::numeric_limits<T>::quiet_NaN(); |
| 56 | // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). |
| 57 | if(first != last && *first == UC('(')) { |
| 58 | for(UC const * ptr = first + 1; ptr != last; ++ptr) { |
| 59 | if (*ptr == UC(')')) { |
| 60 | answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) |
| 61 | break; |
| 62 | } |
| 63 | else if(!((UC('a') <= *ptr && *ptr <= UC('z')) || (UC('A') <= *ptr && *ptr <= UC('Z')) || (UC('0') <= *ptr && *ptr <= UC('9')) || *ptr == UC('_'))) |
| 64 | break; // forbidden char, not nan(n-char-seq-opt) |
| 65 | } |
| 66 | } |
| 67 | return answer; |
| 68 | } |
| 69 | if (fastfloat_strncasecmp(first, str_const_inf<UC>(), 3)) { |
| 70 | if ((last - first >= 8) && fastfloat_strncasecmp(first + 3, str_const_inf<UC>() + 3, 5)) { |
| 71 | answer.ptr = first + 8; |
| 72 | } else { |
| 73 | answer.ptr = first + 3; |
| 74 | } |
| 75 | value = minusSign ? -std::numeric_limits<T>::infinity() : std::numeric_limits<T>::infinity(); |
| 76 | return answer; |
| 77 | } |
| 78 | } |
| 79 | answer.ec = std::errc::invalid_argument; |
| 80 | return answer; |
| 81 | } |
| 82 | |
| 83 | #if defined(__GNUC__) && __GNUC__ < 5 && !defined(__clang__) |
| 84 | # pragma GCC diagnostic pop |
| 85 | #endif |
| 86 | |
| 87 | /** |
| 88 | * Returns true if the floating-pointing rounding mode is to 'nearest'. |
| 89 | * It is the default on most system. This function is meant to be inexpensive. |
| 90 | * Credit : @mwalcott3 |
| 91 | */ |
| 92 | BOOST_FORCEINLINE bool rounds_to_nearest() noexcept { |
| 93 | // https://lemire.me/blog/2020/06/26/gcc-not-nearest/ |
| 94 | #if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) |
| 95 | return false; |
| 96 | #endif |
| 97 | // See |
| 98 | // A fast function to check your floating-point rounding mode |
| 99 | // https://lemire.me/blog/2022/11/16/a-fast-function-to-check-your-floating-point-rounding-mode/ |
| 100 | // |
| 101 | // This function is meant to be equivalent to : |
| 102 | // prior: #include <cfenv> |
| 103 | // return fegetround() == FE_TONEAREST; |
| 104 | // However, it is expected to be much faster than the fegetround() |
| 105 | // function call. |
| 106 | // |
| 107 | // The volatile keywoard prevents the compiler from computing the function |
| 108 | // at compile-time. |
| 109 | // There might be other ways to prevent compile-time optimizations (e.g., asm). |
| 110 | // The value does not need to be std::numeric_limits<float>::min(), any small |
| 111 | // value so that 1 + x should round to 1 would do (after accounting for excess |
| 112 | // precision, as in 387 instructions). |
| 113 | static volatile float fmin = std::numeric_limits<float>::min(); |
| 114 | float fmini = fmin; // we copy it so that it gets loaded at most once. |
| 115 | // |
| 116 | // Explanation: |
| 117 | // Only when fegetround() == FE_TONEAREST do we have that |
| 118 | // fmin + 1.0f == 1.0f - fmin. |
| 119 | // |
| 120 | // FE_UPWARD: |
| 121 | // fmin + 1.0f > 1 |
| 122 | // 1.0f - fmin == 1 |
| 123 | // |
| 124 | // FE_DOWNWARD or FE_TOWARDZERO: |
| 125 | // fmin + 1.0f == 1 |
| 126 | // 1.0f - fmin < 1 |
| 127 | // |
| 128 | // Note: This may fail to be accurate if fast-math has been |
| 129 | // enabled, as rounding conventions may not apply. |
| 130 | #ifdef BOOST_CHARCONV_FASTFLOAT_VISUAL_STUDIO |
| 131 | # pragma warning(push) |
| 132 | // todo: is there a VS warning? |
| 133 | // see https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013 |
| 134 | #elif defined(__clang__) |
| 135 | # pragma clang diagnostic push |
| 136 | # pragma clang diagnostic ignored "-Wfloat-equal" |
| 137 | #elif defined(__GNUC__) |
| 138 | # pragma GCC diagnostic push |
| 139 | # pragma GCC diagnostic ignored "-Wfloat-equal" |
| 140 | #endif |
| 141 | return (fmini + 1.0f == 1.0f - fmini); |
| 142 | #ifdef BOOST_CHARCONV_FASTFLOAT_VISUAL_STUDIO |
| 143 | # pragma warning(pop) |
| 144 | #elif defined(__clang__) |
| 145 | # pragma clang diagnostic pop |
| 146 | #elif defined(__GNUC__) |
| 147 | # pragma GCC diagnostic pop |
| 148 | #endif |
| 149 | } |
| 150 | |
| 151 | } // namespace detail |
| 152 | |
| 153 | template<typename T, typename UC> |
| 154 | BOOST_CHARCONV_FASTFLOAT_CONSTEXPR20 |
| 155 | from_chars_result_t<UC> from_chars(UC const * first, UC const * last, |
| 156 | T &value, chars_format fmt /*= chars_format::general*/) noexcept { |
| 157 | return from_chars_advanced(first, last, value, parse_options_t<UC>{fmt}); |
| 158 | } |
| 159 | |
| 160 | template<typename T, typename UC> |
| 161 | BOOST_CHARCONV_FASTFLOAT_CONSTEXPR20 |
| 162 | from_chars_result_t<UC> from_chars_advanced(UC const * first, UC const * last, |
| 163 | T &value, parse_options_t<UC> options) noexcept { |
| 164 | |
| 165 | static_assert (std::is_same<T, double>::value || std::is_same<T, float>::value, "only float and double are supported" ); |
| 166 | static_assert (std::is_same<UC, char>::value || |
| 167 | std::is_same<UC, wchar_t>::value || |
| 168 | std::is_same<UC, char16_t>::value || |
| 169 | std::is_same<UC, char32_t>::value , "only char, wchar_t, char16_t and char32_t are supported" ); |
| 170 | |
| 171 | from_chars_result_t<UC> answer; |
| 172 | #ifdef BOOST_CHARCONV_FASTFLOAT_SKIP_WHITE_SPACE // disabled by default |
| 173 | while ((first != last) && fast_float::is_space(uint8_t(*first))) { |
| 174 | first++; |
| 175 | } |
| 176 | #endif |
| 177 | if (first == last) { |
| 178 | answer.ec = std::errc::invalid_argument; |
| 179 | answer.ptr = first; |
| 180 | return answer; |
| 181 | } |
| 182 | parsed_number_string_t<UC> pns = parse_number_string<UC>(first, last, options); |
| 183 | if (!pns.valid) { |
| 184 | return detail::parse_infnan(first, last, value); |
| 185 | } |
| 186 | answer.ec = std::errc(); // be optimistic |
| 187 | answer.ptr = pns.lastmatch; |
| 188 | // The implementation of the Clinger's fast path is convoluted because |
| 189 | // we want round-to-nearest in all cases, irrespective of the rounding mode |
| 190 | // selected on the thread. |
| 191 | // We proceed optimistically, assuming that detail::rounds_to_nearest() returns |
| 192 | // true. |
| 193 | if (binary_format<T>::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format<T>::max_exponent_fast_path() && !pns.too_many_digits) { |
| 194 | // Unfortunately, the conventional Clinger's fast path is only possible |
| 195 | // when the system rounds to the nearest float. |
| 196 | // |
| 197 | // We expect the next branch to almost always be selected. |
| 198 | // We could check it first (before the previous branch), but |
| 199 | // there might be performance advantages at having the check |
| 200 | // be last. |
| 201 | if(!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) { |
| 202 | // We have that fegetround() == FE_TONEAREST. |
| 203 | // Next is Clinger's fast path. |
| 204 | if (pns.mantissa <=binary_format<T>::max_mantissa_fast_path()) { |
| 205 | value = T(pns.mantissa); |
| 206 | if (pns.exponent < 0) { value = value / binary_format<T>::exact_power_of_ten(-pns.exponent); } |
| 207 | else { value = value * binary_format<T>::exact_power_of_ten(pns.exponent); } |
| 208 | if (pns.negative) { value = -value; } |
| 209 | return answer; |
| 210 | } |
| 211 | } else { |
| 212 | // We do not have that fegetround() == FE_TONEAREST. |
| 213 | // Next is a modified Clinger's fast path, inspired by Jakub JelĂnek's proposal |
| 214 | if (pns.exponent >= 0 && pns.mantissa <=binary_format<T>::max_mantissa_fast_path(pns.exponent)) { |
| 215 | #if defined(__clang__) |
| 216 | // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD |
| 217 | if(pns.mantissa == 0) { |
| 218 | value = pns.negative ? -0. : 0.; |
| 219 | return answer; |
| 220 | } |
| 221 | #endif |
| 222 | value = T(pns.mantissa) * binary_format<T>::exact_power_of_ten(pns.exponent); |
| 223 | if (pns.negative) { value = -value; } |
| 224 | return answer; |
| 225 | } |
| 226 | } |
| 227 | } |
| 228 | adjusted_mantissa am = compute_float<binary_format<T>>(pns.exponent, pns.mantissa); |
| 229 | if(pns.too_many_digits && am.power2 >= 0) { |
| 230 | if(am != compute_float<binary_format<T>>(pns.exponent, pns.mantissa + 1)) { |
| 231 | am = compute_error<binary_format<T>>(pns.exponent, pns.mantissa); |
| 232 | } |
| 233 | } |
| 234 | // If we called compute_float<binary_format<T>>(pns.exponent, pns.mantissa) and we have an invalid power (am.power2 < 0), |
| 235 | // then we need to go the long way around again. This is very uncommon. |
| 236 | if(am.power2 < 0) { am = digit_comp<T>(pns, am); } |
| 237 | to_float(pns.negative, am, value); |
| 238 | // Test for over/underflow. |
| 239 | if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || am.power2 == binary_format<T>::infinite_power()) { |
| 240 | answer.ec = std::errc::result_out_of_range; |
| 241 | } |
| 242 | return answer; |
| 243 | } |
| 244 | |
| 245 | }}}} // namespace fast_float |
| 246 | |
| 247 | #endif |
| 248 | |