Skip to content

Commit 8041880

Browse files
committed
Several improvements
- using availability macros - handle leading whitespace - implement inf and nan - implement negative values
1 parent 6a59316 commit 8041880

File tree

5 files changed

+610
-28
lines changed

5 files changed

+610
-28
lines changed

libcxx/include/__charconv/from_chars_floating_point.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ _LIBCPP_BEGIN_NAMESPACE_STD
3131

3232
#if _LIBCPP_STD_VER >= 17
3333

34-
_LIBCPP_EXPORTED_FROM_ABI from_chars_result
34+
_LIBCPP_AVAILABILITY_FROM_CHARS_FLOATING_POINT _LIBCPP_EXPORTED_FROM_ABI from_chars_result
3535
__from_chars_floating_point(const char* __first, const char* __last, float& __value, chars_format __fmt);
3636

37-
_LIBCPP_EXPORTED_FROM_ABI from_chars_result
37+
_LIBCPP_AVAILABILITY_FROM_CHARS_FLOATING_POINT _LIBCPP_EXPORTED_FROM_ABI from_chars_result
3838
__from_chars_floating_point(const char* __first, const char* __last, double& __value, chars_format __fmt);
3939

4040
_LIBCPP_HIDE_FROM_ABI inline from_chars_result

libcxx/include/__configuration/availability.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@
8787
// in all versions of the library are available.
8888
#if defined(_LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS)
8989

90+
# define _LIBCPP_INTRODUCED_IN_LLVM_20 1
91+
# define _LIBCPP_INTRODUCED_IN_LLVM_20_ATTRIBUTE /* nothing */
92+
9093
# define _LIBCPP_INTRODUCED_IN_LLVM_19 1
9194
# define _LIBCPP_INTRODUCED_IN_LLVM_19_ATTRIBUTE /* nothing */
9295

@@ -132,6 +135,11 @@
132135

133136
// clang-format off
134137

138+
// LLVM 20
139+
// TODO: Fill this in
140+
# define _LIBCPP_INTRODUCED_IN_LLVM_20 0
141+
# define _LIBCPP_INTRODUCED_IN_LLVM_20_ATTRIBUTE __attribute__((unavailable))
142+
135143
// LLVM 19
136144
// TODO: Fill this in
137145
# define _LIBCPP_INTRODUCED_IN_LLVM_19 0
@@ -375,6 +383,11 @@
375383
#define _LIBCPP_AVAILABILITY_HAS_BAD_EXPECTED_ACCESS_KEY_FUNCTION _LIBCPP_INTRODUCED_IN_LLVM_19
376384
#define _LIBCPP_AVAILABILITY_BAD_EXPECTED_ACCESS_KEY_FUNCTION _LIBCPP_INTRODUCED_IN_LLVM_19_ATTRIBUTE
377385

386+
// This controls the availability of floating-point std::from_chars functions.
387+
// These overloads were added later than the integer overloads.
388+
#define _LIBCPP_AVAILABILITY_HAS_FROM_CHARS_FLOATING_POINT _LIBCPP_INTRODUCED_IN_LLVM_20
389+
#define _LIBCPP_AVAILABILITY_FROM_CHARS_FLOATING_POINT _LIBCPP_INTRODUCED_IN_LLVM_20_ATTRIBUTE
390+
378391
// Define availability attributes that depend on _LIBCPP_HAS_NO_EXCEPTIONS.
379392
// Those are defined in terms of the availability attributes above, and
380393
// should not be vendor-specific.

libcxx/src/charconv.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,12 @@ to_chars_result to_chars(char* __first, char* __last, long double __value, chars
7777

7878
from_chars_result
7979
__from_chars_floating_point(const char* __first, const char* __last, float& __value, chars_format __fmt) {
80-
return from_chars_floating_point<float>(__first, __last, __value, __fmt);
80+
return std::__from_chars_floating_point<float>(__first, __last, __value, __fmt);
8181
}
8282

8383
from_chars_result
8484
__from_chars_floating_point(const char* __first, const char* __last, double& __value, chars_format __fmt) {
85-
return from_chars_floating_point<double>(__first, __last, __value, __fmt);
85+
return std::__from_chars_floating_point<double>(__first, __last, __value, __fmt);
8686
}
8787

8888
_LIBCPP_END_NAMESPACE_STD

libcxx/src/include/from_chars_floating_point.h

Lines changed: 162 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,124 @@
1414

1515
#include <__assert>
1616
#include <__config>
17+
#include <cctype>
1718
#include <charconv>
19+
#include <concepts>
1820
#include <limits>
21+
#include <cstring>
1922
#include <type_traits>
2023

2124
// Included for the _Floating_type_traits class
2225
#include "to_chars_floating_point.h"
2326

2427
_LIBCPP_BEGIN_NAMESPACE_STD
2528

26-
template <typename _Tp, __enable_if_t<std::is_floating_point<_Tp>::value, int> = 0>
27-
from_chars_result from_chars_floating_point(const char* __first, const char* __last, _Tp& __value, chars_format __fmt) {
29+
// Parses an infinity string.
30+
// Valid strings are case insentitive and contain INF or INFINITY.
31+
//
32+
// - __first is the first argument to std::from_chars. When the string is invalid
33+
// this value is returned as ptr in the result.
34+
// - __last is the last argument of std::from_chars.
35+
// - __value is the value argument of std::from_chars,
36+
// - __ptr is the current position is the input string. This is points beyond
37+
// the initial I character.
38+
// - __negative whether a valid string represents -inf or +inf.
39+
template <floating_point _Tp>
40+
from_chars_result __from_chars_floating_point_inf(
41+
const char* const __first, const char* __last, _Tp& __value, const char* __ptr, bool __negative) {
42+
if (__last - __ptr < 2) [[unlikely]]
43+
return {__first, errc::invalid_argument};
44+
45+
if (std::tolower(__ptr[0]) != 'n' || std::tolower(__ptr[1]) != 'f') [[unlikely]]
46+
return {__first, errc::invalid_argument};
47+
48+
__ptr += 2;
49+
50+
// At this point the result is valid and contains INF.
51+
// When the remaining part contains INITY this will be consumed. Otherwise
52+
// only INF is consumed. For example INFINITZ will consume INF and ignore
53+
// INITZ.
54+
55+
if (__last - __ptr >= 5 //
56+
&& std::tolower(__ptr[0]) == 'i' //
57+
&& std::tolower(__ptr[1]) == 'n' //
58+
&& std::tolower(__ptr[2]) == 'i' //
59+
&& std::tolower(__ptr[3]) == 't' //
60+
&& std::tolower(__ptr[4]) == 'y')
61+
__ptr += 5;
62+
63+
if constexpr (numeric_limits<_Tp>::has_infinity) {
64+
if (__negative)
65+
__value = -std::numeric_limits<_Tp>::infinity();
66+
else
67+
__value = std::numeric_limits<_Tp>::infinity();
68+
69+
return {__ptr, std::errc{}};
70+
} else {
71+
return {__ptr, errc::result_out_of_range};
72+
}
73+
}
74+
75+
// Parses an infinita string.
76+
// Valid strings are case insentitive and contain INF or INFINITY.
77+
//
78+
// - __first is the first argument to std::from_chars. When the string is invalid
79+
// this value is returned as ptr in the result.
80+
// - __last is the last argument of std::from_chars.
81+
// - __value is the value argument of std::from_chars,
82+
// - __ptr is the current position is the input string. This is points beyond
83+
// the initial N character.
84+
// - __negative whether a valid string represents -nan or +nan.
85+
template <floating_point _Tp>
86+
from_chars_result __from_chars_floating_point_nan(
87+
const char* const __first, const char* __last, _Tp& __value, const char* __ptr, bool __negative) {
88+
if (__last - __ptr < 2) [[unlikely]]
89+
return {__first, errc::invalid_argument};
90+
91+
if (std::tolower(__ptr[0]) != 'a' || std::tolower(__ptr[1]) != 'n') [[unlikely]]
92+
return {__first, errc::invalid_argument};
93+
94+
__ptr += 2;
95+
96+
// At this point the result is valid and contains NAN. When the remaining
97+
// part contains ( n-char-sequence_opt ) this will be consumed. Otherwise
98+
// only NAN is consumed. For example NAN(abcd will consume NAN and ignore
99+
// (abcd.
100+
if (__last - __ptr >= 2 && __ptr[0] == '(') {
101+
size_t __offset = 1;
102+
do {
103+
if (__ptr[__offset] == ')') {
104+
__ptr += __offset + 1;
105+
break;
106+
}
107+
if (__ptr[__offset] != '_' && !std::isalnum(__ptr[__offset]))
108+
break;
109+
++__offset;
110+
} while (__ptr + __offset != __last);
111+
}
112+
113+
if (__negative)
114+
__value = -std::numeric_limits<_Tp>::quiet_NaN();
115+
else
116+
__value = std::numeric_limits<_Tp>::quiet_NaN();
117+
118+
return {__ptr, std::errc{}};
119+
}
120+
121+
template <floating_point _Tp>
122+
from_chars_result __from_chars_floating_point_decimal(
123+
const char* const __first,
124+
const char* __last,
125+
_Tp& __value,
126+
chars_format __fmt,
127+
const char* __ptr,
128+
bool __negative) {
28129
using _Traits = _Floating_type_traits<_Tp>;
29130
using _Uint_type = typename _Traits::_Uint_type;
30131
ptrdiff_t length = __last - __first;
31132
_LIBCPP_ASSERT_INTERNAL(length > 0, "");
32133

33-
// hacky parsing code as example. Not intended for actual use. I'm just going to handle the base 10
34-
// chars_format::general case. Also, no sign, inf, or nan handling.
35-
_LIBCPP_ASSERT_INTERNAL(__fmt == std::chars_format::general, "");
36-
37-
const char* src = __first; // rename to match the libc code copied for this section.
134+
const char* src = __ptr; // rename to match the libc code copied for this section.
38135

39136
_Uint_type mantissa = 0;
40137
int exponent = 0;
@@ -123,10 +220,67 @@ from_chars_result from_chars_floating_point(const char* __first, const char* __l
123220
auto result = LIBC_NAMESPACE::fputil::FPBits<_Tp>();
124221
result.set_mantissa(expanded_float.mantissa);
125222
result.set_biased_exponent(expanded_float.exponent);
126-
__value = result.get_val();
223+
if (__negative)
224+
__value = -result.get_val();
225+
else
226+
__value = result.get_val();
127227
return {src + index, {}};
128228
}
129229

230+
template <floating_point _Tp>
231+
from_chars_result
232+
__from_chars_floating_point(const char* const __first, const char* __last, _Tp& __value, chars_format __fmt) {
233+
if (__first == __last) [[unlikely]]
234+
return {__first, errc::invalid_argument};
235+
236+
const char* __ptr = __first;
237+
238+
// skip whitespace
239+
while (std::isspace(*__ptr)) {
240+
++__ptr;
241+
if (__ptr == __last) [[unlikely]]
242+
return {__first, errc::invalid_argument}; // is this valid??
243+
}
244+
245+
bool __negative = *__ptr == '-';
246+
if (__negative) {
247+
++__ptr;
248+
if (__ptr == __last) [[unlikely]]
249+
return {__first, errc::invalid_argument};
250+
}
251+
252+
if (!std::isdigit(*__ptr)) {
253+
// TODO Evaluate the other implementations
254+
// [charconv.from.chars]/6.2
255+
// if fmt has chars_format::scientific set but not chars_format::fixed,
256+
// the otherwise optional exponent part shall appear;
257+
// Since INF/NAN do not have an exponent this value is not valid.
258+
// See LWG3456
259+
if (__fmt == chars_format::scientific)
260+
return {__first, errc::invalid_argument};
261+
262+
switch (std::tolower(*__ptr)) {
263+
case 'i':
264+
return __from_chars_floating_point_inf(__first, __last, __value, __ptr + 1, __negative);
265+
case 'n':
266+
if constexpr (numeric_limits<_Tp>::has_quiet_NaN)
267+
return __from_chars_floating_point_nan(__first, __last, __value, __ptr + 1, __negative);
268+
[[fallthrough]];
269+
default:
270+
return {__first, errc::invalid_argument};
271+
}
272+
}
273+
274+
#if 1
275+
_LIBCPP_ASSERT_INTERNAL(__fmt == std::chars_format::general, "");
276+
#else
277+
if (__fmt == chars_format::hex)
278+
return std::__from_chars_floating_point_hex(__first, __last, __value);
279+
#endif
280+
281+
return std::__from_chars_floating_point_decimal(__first, __last, __value, __fmt, __ptr, __negative);
282+
}
283+
130284
_LIBCPP_END_NAMESPACE_STD
131285

132286
#endif //_LIBCPP_SRC_INCLUDE_FROM_CHARS_FLOATING_POINT_H

0 commit comments

Comments
 (0)