| Line | Branch | Exec | Source |
|---|---|---|---|
| 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_JSON_DETAIL_CHARCONV_DETAIL_FASTFLOAT_PARSE_NUMBER_HPP | ||
| 9 | #define BOOST_JSON_DETAIL_CHARCONV_DETAIL_FASTFLOAT_PARSE_NUMBER_HPP | ||
| 10 | |||
| 11 | #include <boost/json/detail/charconv/detail/fast_float/ascii_number.hpp> | ||
| 12 | #include <boost/json/detail/charconv/detail/fast_float/decimal_to_binary.hpp> | ||
| 13 | #include <boost/json/detail/charconv/detail/fast_float/digit_comparison.hpp> | ||
| 14 | #include <boost/json/detail/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 json { namespace detail { 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_JSON_CXX14_CONSTEXPR | ||
| 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 | ✗ | if (last - first >= 3) { | |
| 48 | ✗ | if (fastfloat_strncasecmp(first, str_const_nan<UC>(), 3)) { | |
| 49 | ✗ | answer.ptr = (first += 3); | |
| 50 | ✗ | value = minusSign ? -std::numeric_limits<T>::quiet_NaN() : std::numeric_limits<T>::quiet_NaN(); | |
| 51 | // 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). | ||
| 52 | ✗ | if(first != last && *first == UC('(')) { | |
| 53 | ✗ | for(UC const * ptr = first + 1; ptr != last; ++ptr) { | |
| 54 | ✗ | if (*ptr == UC(')')) { | |
| 55 | ✗ | answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) | |
| 56 | ✗ | break; | |
| 57 | } | ||
| 58 | ✗ | else if(!((UC('a') <= *ptr && *ptr <= UC('z')) || (UC('A') <= *ptr && *ptr <= UC('Z')) || (UC('0') <= *ptr && *ptr <= UC('9')) || *ptr == UC('_'))) | |
| 59 | ✗ | break; // forbidden char, not nan(n-char-seq-opt) | |
| 60 | } | ||
| 61 | } | ||
| 62 | ✗ | return answer; | |
| 63 | } | ||
| 64 | ✗ | if (fastfloat_strncasecmp(first, str_const_inf<UC>(), 3)) { | |
| 65 | ✗ | if ((last - first >= 8) && fastfloat_strncasecmp(first + 3, str_const_inf<UC>() + 3, 5)) { | |
| 66 | ✗ | answer.ptr = first + 8; | |
| 67 | } else { | ||
| 68 | ✗ | answer.ptr = first + 3; | |
| 69 | } | ||
| 70 | ✗ | value = minusSign ? -std::numeric_limits<T>::infinity() : std::numeric_limits<T>::infinity(); | |
| 71 | ✗ | return answer; | |
| 72 | } | ||
| 73 | } | ||
| 74 | ✗ | answer.ec = std::errc::invalid_argument; | |
| 75 | ✗ | return answer; | |
| 76 | } | ||
| 77 | |||
| 78 | #if defined(__GNUC__) && __GNUC__ < 5 && !defined(__clang__) | ||
| 79 | # pragma GCC diagnostic pop | ||
| 80 | #endif | ||
| 81 | |||
| 82 | /** | ||
| 83 | * Returns true if the floating-pointing rounding mode is to 'nearest'. | ||
| 84 | * It is the default on most system. This function is meant to be inexpensive. | ||
| 85 | * Credit : @mwalcott3 | ||
| 86 | */ | ||
| 87 | BOOST_FORCEINLINE bool rounds_to_nearest() noexcept { | ||
| 88 | // https://lemire.me/blog/2020/06/26/gcc-not-nearest/ | ||
| 89 | #if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) | ||
| 90 | return false; | ||
| 91 | #endif | ||
| 92 | // See | ||
| 93 | // A fast function to check your floating-point rounding mode | ||
| 94 | // https://lemire.me/blog/2022/11/16/a-fast-function-to-check-your-floating-point-rounding-mode/ | ||
| 95 | // | ||
| 96 | // This function is meant to be equivalent to : | ||
| 97 | // prior: #include <cfenv> | ||
| 98 | // return fegetround() == FE_TONEAREST; | ||
| 99 | // However, it is expected to be much faster than the fegetround() | ||
| 100 | // function call. | ||
| 101 | // | ||
| 102 | // The volatile keywoard prevents the compiler from computing the function | ||
| 103 | // at compile-time. | ||
| 104 | // There might be other ways to prevent compile-time optimizations (e.g., asm). | ||
| 105 | // The value does not need to be std::numeric_limits<float>::min(), any small | ||
| 106 | // value so that 1 + x should round to 1 would do (after accounting for excess | ||
| 107 | // precision, as in 387 instructions). | ||
| 108 | static volatile float fmin = (std::numeric_limits<float>::min)(); | ||
| 109 | 5895 | float fmini = fmin; // we copy it so that it gets loaded at most once. | |
| 110 | // | ||
| 111 | // Explanation: | ||
| 112 | // Only when fegetround() == FE_TONEAREST do we have that | ||
| 113 | // fmin + 1.0f == 1.0f - fmin. | ||
| 114 | // | ||
| 115 | // FE_UPWARD: | ||
| 116 | // fmin + 1.0f > 1 | ||
| 117 | // 1.0f - fmin == 1 | ||
| 118 | // | ||
| 119 | // FE_DOWNWARD or FE_TOWARDZERO: | ||
| 120 | // fmin + 1.0f == 1 | ||
| 121 | // 1.0f - fmin < 1 | ||
| 122 | // | ||
| 123 | // Note: This may fail to be accurate if fast-math has been | ||
| 124 | // enabled, as rounding conventions may not apply. | ||
| 125 | #ifdef BOOST_JSON_FASTFLOAT_VISUAL_STUDIO | ||
| 126 | # pragma warning(push) | ||
| 127 | // todo: is there a VS warning? | ||
| 128 | // see https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013 | ||
| 129 | #elif defined(__clang__) | ||
| 130 | # pragma clang diagnostic push | ||
| 131 | # pragma clang diagnostic ignored "-Wfloat-equal" | ||
| 132 | #elif defined(__GNUC__) | ||
| 133 | # pragma GCC diagnostic push | ||
| 134 | # pragma GCC diagnostic ignored "-Wfloat-equal" | ||
| 135 | #endif | ||
| 136 | 5895 | return (fmini + 1.0f == 1.0f - fmini); | |
| 137 | #ifdef BOOST_JSON_FASTFLOAT_VISUAL_STUDIO | ||
| 138 | # pragma warning(pop) | ||
| 139 | #elif defined(__clang__) | ||
| 140 | # pragma clang diagnostic pop | ||
| 141 | #elif defined(__GNUC__) | ||
| 142 | # pragma GCC diagnostic pop | ||
| 143 | #endif | ||
| 144 | } | ||
| 145 | |||
| 146 | } // namespace detail | ||
| 147 | |||
| 148 | template<typename T, typename UC> | ||
| 149 | BOOST_JSON_FASTFLOAT_CONSTEXPR20 | ||
| 150 | 1009310 | from_chars_result_t<UC> from_chars(UC const * first, UC const * last, | |
| 151 | T &value, chars_format fmt /*= chars_format::general*/) noexcept { | ||
| 152 | 1009310 | return from_chars_advanced(first, last, value, parse_options_t<UC>{fmt}); | |
| 153 | } | ||
| 154 | |||
| 155 | template<typename T, typename UC> | ||
| 156 | BOOST_JSON_FASTFLOAT_CONSTEXPR20 | ||
| 157 | 1009310 | from_chars_result_t<UC> from_chars_advanced(UC const * first, UC const * last, | |
| 158 | T &value, parse_options_t<UC> options) noexcept { | ||
| 159 | |||
| 160 | static_assert (std::is_same<T, double>::value || std::is_same<T, float>::value, "only float and double are supported"); | ||
| 161 | static_assert (std::is_same<UC, char>::value || | ||
| 162 | std::is_same<UC, wchar_t>::value || | ||
| 163 | std::is_same<UC, char16_t>::value || | ||
| 164 | std::is_same<UC, char32_t>::value , "only char, wchar_t, char16_t and char32_t are supported"); | ||
| 165 | |||
| 166 | from_chars_result_t<UC> answer; | ||
| 167 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1009310 times.
|
1009310 | if (first == last) { |
| 168 | ✗ | answer.ec = std::errc::invalid_argument; | |
| 169 | ✗ | answer.ptr = first; | |
| 170 | ✗ | return answer; | |
| 171 | } | ||
| 172 | 1009310 | parsed_number_string_t<UC> pns = parse_number_string<UC>(first, last, options); | |
| 173 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1009310 times.
|
1009310 | if (!pns.valid) { |
| 174 | ✗ | return detail::parse_infnan(first, last, value); | |
| 175 | } | ||
| 176 | 1009310 | answer.ec = std::errc(); // be optimistic | |
| 177 | 1009310 | answer.ptr = pns.lastmatch; | |
| 178 | // The implementation of the Clinger's fast path is convoluted because | ||
| 179 | // we want round-to-nearest in all cases, irrespective of the rounding mode | ||
| 180 | // selected on the thread. | ||
| 181 | // We proceed optimistically, assuming that detail::rounds_to_nearest() returns | ||
| 182 | // true. | ||
| 183 |
8/8✓ Branch 1 taken 546058 times.
✓ Branch 2 taken 463252 times.
✓ Branch 4 taken 79747 times.
✓ Branch 5 taken 466311 times.
✓ Branch 6 taken 5895 times.
✓ Branch 7 taken 73852 times.
✓ Branch 8 taken 5895 times.
✓ Branch 9 taken 1003415 times.
|
1009310 | if (binary_format<T>::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format<T>::max_exponent_fast_path() && !pns.too_many_digits) { |
| 184 | // Unfortunately, the conventional Clinger's fast path is only possible | ||
| 185 | // when the system rounds to the nearest float. | ||
| 186 | // | ||
| 187 | // We expect the next branch to almost always be selected. | ||
| 188 | // We could check it first (before the previous branch), but | ||
| 189 | // there might be performance advantages at having the check | ||
| 190 | // be last. | ||
| 191 |
3/6✓ Branch 0 taken 5895 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5895 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5895 times.
✗ Branch 5 not taken.
|
11790 | if(!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) { |
| 192 | // We have that fegetround() == FE_TONEAREST. | ||
| 193 | // Next is Clinger's fast path. | ||
| 194 |
2/2✓ Branch 1 taken 5570 times.
✓ Branch 2 taken 325 times.
|
5895 | if (pns.mantissa <=binary_format<T>::max_mantissa_fast_path()) { |
| 195 | 5570 | value = T(pns.mantissa); | |
| 196 |
2/2✓ Branch 0 taken 3814 times.
✓ Branch 1 taken 1756 times.
|
5570 | if (pns.exponent < 0) { value = value / binary_format<T>::exact_power_of_ten(-pns.exponent); } |
| 197 | 1756 | else { value = value * binary_format<T>::exact_power_of_ten(pns.exponent); } | |
| 198 |
2/2✓ Branch 0 taken 2258 times.
✓ Branch 1 taken 3312 times.
|
5570 | if (pns.negative) { value = -value; } |
| 199 | 5570 | return answer; | |
| 200 | } | ||
| 201 | } else { | ||
| 202 | // We do not have that fegetround() == FE_TONEAREST. | ||
| 203 | // Next is a modified Clinger's fast path, inspired by Jakub JelĂnek's proposal | ||
| 204 | ✗ | if (pns.exponent >= 0 && pns.mantissa <=binary_format<T>::max_mantissa_fast_path(pns.exponent)) { | |
| 205 | #if defined(__clang__) | ||
| 206 | // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD | ||
| 207 | if(pns.mantissa == 0) { | ||
| 208 | value = pns.negative ? -0. : 0.; | ||
| 209 | return answer; | ||
| 210 | } | ||
| 211 | #endif | ||
| 212 | ✗ | value = T(pns.mantissa) * binary_format<T>::exact_power_of_ten(pns.exponent); | |
| 213 | ✗ | if (pns.negative) { value = -value; } | |
| 214 | ✗ | return answer; | |
| 215 | } | ||
| 216 | } | ||
| 217 | } | ||
| 218 |
2/2✓ Branch 0 taken 1003739 times.
✓ Branch 1 taken 1 times.
|
1003740 | adjusted_mantissa am = compute_float<binary_format<T>>(pns.exponent, pns.mantissa); |
| 219 |
3/4✓ Branch 0 taken 1000712 times.
✓ Branch 1 taken 3028 times.
✓ Branch 2 taken 1000712 times.
✗ Branch 3 not taken.
|
1003740 | if(pns.too_many_digits && am.power2 >= 0) { |
| 220 |
3/4✓ Branch 0 taken 1000712 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 2986 times.
✓ Branch 4 taken 997726 times.
|
2001424 | if(am != compute_float<binary_format<T>>(pns.exponent, pns.mantissa + 1)) { |
| 221 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2986 times.
|
5972 | am = compute_error<binary_format<T>>(pns.exponent, pns.mantissa); |
| 222 | } | ||
| 223 | } | ||
| 224 | // If we called compute_float<binary_format<T>>(pns.exponent, pns.mantissa) and we have an invalid power (am.power2 < 0), | ||
| 225 | // then we need to go the long way around again. This is very uncommon. | ||
| 226 |
2/2✓ Branch 0 taken 2986 times.
✓ Branch 1 taken 1000754 times.
|
1003740 | if(am.power2 < 0) { am = digit_comp<T>(pns, am); } |
| 227 | 1003740 | to_float(pns.negative, am, value); | |
| 228 | // Test for over/underflow. | ||
| 229 |
9/10✓ Branch 0 taken 1003739 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 30968 times.
✓ Branch 3 taken 972771 times.
✓ Branch 4 taken 30968 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 30608 times.
✓ Branch 8 taken 973132 times.
✓ Branch 9 taken 30608 times.
✓ Branch 10 taken 973132 times.
|
1003740 | if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || am.power2 == binary_format<T>::infinite_power()) { |
| 230 | 30608 | answer.ec = std::errc::result_out_of_range; | |
| 231 | } | ||
| 232 | 1003740 | return answer; | |
| 233 | } | ||
| 234 | |||
| 235 | }}}}}} // namespace fast_float | ||
| 236 | |||
| 237 | #endif | ||
| 238 |