GCC Code Coverage Report


Directory: libs/json/include/boost/json/
File: detail/charconv/detail/fast_float/parse_number.hpp
Date: 2025-12-23 17:20:53
Exec Total Coverage
Lines: 27 63 42.9%
Functions: 2 3 66.7%
Branches: 39 96 40.6%

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