[libcxx-commits] [libc] [libcxx] [llvm] [libcxx][libc] Hand in Hand PoC with from_chars (PR #91651)
Mark de Wever via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Sep 25 08:10:19 PDT 2024
https://github.com/mordante updated https://github.com/llvm/llvm-project/pull/91651
>From 6aeac27019dce67cc8910c6ca979cc555024c42f Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Tue, 7 May 2024 16:50:24 -0700
Subject: [PATCH 01/14] [libcxx][libc] Hand in Hand PoC with from_chars
DO NOT MERGE, PROOF OF CONCEPT ONLY.
This patch aims to demonstrate the utility of sharing code between libc
and libc++ by using the libc float conversion code in the libc++
function from_chars. This patch adds from_chars for float and double
(long double is possible but was causing errors so was skipped here), as
well as a test to demonstrate that it works.
This is very much just a proof of concept, not intended to be committed
as-is. The from_chars code written is copied from the libc parsing code
and is not functionally complete, nor does it follow the correct coding
style.
---
libc/shared/str_to_float.h | 29 ++++
libcxx/include/CMakeLists.txt | 1 +
.../__charconv/from_chars_floating_point.h | 68 +++++++++
libcxx/include/charconv | 1 +
libcxx/src/CMakeLists.txt | 5 +-
libcxx/src/charconv.cpp | 17 +++
.../src/include/from_chars_floating_point.h | 135 ++++++++++++++++++
.../charconv.from.chars/float.pass.cpp | 70 +++++++++
libcxx/test/support/charconv_test_helpers.h | 2 +
9 files changed, 326 insertions(+), 2 deletions(-)
create mode 100644 libc/shared/str_to_float.h
create mode 100644 libcxx/include/__charconv/from_chars_floating_point.h
create mode 100644 libcxx/src/include/from_chars_floating_point.h
create mode 100644 libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
diff --git a/libc/shared/str_to_float.h b/libc/shared/str_to_float.h
new file mode 100644
index 00000000000000..da70db11f6b82b
--- /dev/null
+++ b/libc/shared/str_to_float.h
@@ -0,0 +1,29 @@
+//===-- String to float conversion utils ------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SHARED_STR_TO_FLOAT_H
+#define LLVM_LIBC_SHARED_STR_TO_FLOAT_H
+
+#include "src/__support/str_to_float.h"
+
+namespace LIBC_NAMESPACE::shared {
+
+// WARNING: This is a proof of concept. In future the interface point for libcxx
+// won't be using libc internal classes.
+
+template <class T>
+inline internal::FloatConvertReturn<T> decimal_exp_to_float(
+ internal::ExpandedFloat<T> init_num, bool truncated,
+ internal::RoundDirection round, const char *__restrict num_start,
+ const size_t num_len = cpp::numeric_limits<size_t>::max()) {
+ return internal::decimal_exp_to_float(init_num, truncated, round, num_start,
+ num_len);
+}
+} // namespace LIBC_NAMESPACE::shared
+
+#endif // LLVM_LIBC_SHARED_STR_TO_FLOAT_H
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 32579272858a8e..43f9e5071f3b02 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -235,6 +235,7 @@ set(files
__bit/rotate.h
__bit_reference
__charconv/chars_format.h
+ __charconv/from_chars_floating_point.h
__charconv/from_chars_integral.h
__charconv/from_chars_result.h
__charconv/tables.h
diff --git a/libcxx/include/__charconv/from_chars_floating_point.h b/libcxx/include/__charconv/from_chars_floating_point.h
new file mode 100644
index 00000000000000..dd305ca8ea1dcf
--- /dev/null
+++ b/libcxx/include/__charconv/from_chars_floating_point.h
@@ -0,0 +1,68 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___CHARCONV_FROM_CHARS_FLOATING_POINT_H
+#define _LIBCPP___CHARCONV_FROM_CHARS_FLOATING_POINT_H
+
+#include <__assert>
+#include <__charconv/chars_format.h>
+#include <__charconv/from_chars_result.h>
+#include <__charconv/traits.h>
+#include <__config>
+#include <__system_error/errc.h>
+#include <__type_traits/enable_if.h>
+#include <__type_traits/integral_constant.h>
+#include <__type_traits/is_floating_point.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 17
+
+from_chars_result from_chars_floating_point(
+ const char* __first, const char* __last, float& value, chars_format fmt = chars_format::general);
+
+from_chars_result from_chars_floating_point(
+ const char* __first, const char* __last, double& value, chars_format fmt = chars_format::general);
+
+// template <typename _Tp, __enable_if_t<is_floating_point<_Tp>::value, int> = 0>
+// inline from_chars_result
+// from_chars(const char* __first, const char* __last, _Tp& __value, chars_format fmt = chars_format::general) {
+// return std::from_chars_floating_point(__first, __last, __value, fmt);
+// }
+
+// inline from_chars_result
+// from_chars(const char* __first, const char* __last, float& __value, chars_format fmt = chars_format::general) {
+// return std::from_chars_floating_point(__first, __last, __value, fmt);
+// }
+
+// inline from_chars_result
+// from_chars(const char* __first, const char* __last, double& __value, chars_format fmt = chars_format::general) {
+// return std::from_chars_floating_point(__first, __last, __value, fmt);
+// }
+
+from_chars_result
+from_chars(const char* __first, const char* __last, float& __value, chars_format fmt = chars_format::general);
+
+from_chars_result
+from_chars(const char* __first, const char* __last, double& __value, chars_format fmt = chars_format::general);
+
+#endif // _LIBCPP_STD_VER >= 17
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___CHARCONV_FROM_CHARS_FLOATING_POINT_H
diff --git a/libcxx/include/charconv b/libcxx/include/charconv
index a2e270e9316dc7..a8d5a5802d890a 100644
--- a/libcxx/include/charconv
+++ b/libcxx/include/charconv
@@ -73,6 +73,7 @@ namespace std {
#if _LIBCPP_STD_VER >= 17
# include <__charconv/chars_format.h>
+# include <__charconv/from_chars_floating_point.h>
# include <__charconv/from_chars_integral.h>
# include <__charconv/from_chars_result.h>
# include <__charconv/tables.h>
diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt
index fe9d2666fa4caa..41be5dcb2cbc4e 100644
--- a/libcxx/src/CMakeLists.txt
+++ b/libcxx/src/CMakeLists.txt
@@ -31,6 +31,7 @@ set(LIBCXX_SOURCES
include/ryu/f2s.h
include/ryu/ryu.h
include/to_chars_floating_point.h
+ include/from_chars_floating_point.h
legacy_pointer_safety.cpp
memory.cpp
memory_resource.cpp
@@ -179,7 +180,7 @@ split_list(LIBCXX_LINK_FLAGS)
# Build the shared library.
if (LIBCXX_ENABLE_SHARED)
add_library(cxx_shared SHARED ${exclude_from_all} ${LIBCXX_SOURCES} ${LIBCXX_HEADERS})
- target_include_directories(cxx_shared PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
+ target_include_directories(cxx_shared PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../../libc) #TODO: Do this properly
target_link_libraries(cxx_shared PUBLIC cxx-headers libcxx-libc-shared
PRIVATE ${LIBCXX_LIBRARIES})
set_target_properties(cxx_shared
@@ -272,7 +273,7 @@ set(CMAKE_STATIC_LIBRARY_PREFIX "lib")
# Build the static library.
if (LIBCXX_ENABLE_STATIC)
add_library(cxx_static STATIC ${exclude_from_all} ${LIBCXX_SOURCES} ${LIBCXX_HEADERS})
- target_include_directories(cxx_static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
+ target_include_directories(cxx_static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../../libc) #TODO: Do this properly
target_link_libraries(cxx_static PUBLIC cxx-headers libcxx-libc-static
PRIVATE ${LIBCXX_LIBRARIES}
PRIVATE libcxx-abi-static)
diff --git a/libcxx/src/charconv.cpp b/libcxx/src/charconv.cpp
index 4fd7a2c2c0f038..7d04de2dc4b35f 100644
--- a/libcxx/src/charconv.cpp
+++ b/libcxx/src/charconv.cpp
@@ -9,6 +9,7 @@
#include <charconv>
#include <string.h>
+#include "include/from_chars_floating_point.h"
#include "include/to_chars_floating_point.h"
_LIBCPP_BEGIN_NAMESPACE_STD
@@ -74,4 +75,20 @@ to_chars_result to_chars(char* __first, char* __last, long double __value, chars
__first, __last, static_cast<double>(__value), __fmt, __precision);
}
+from_chars_result from_chars_floating_point(const char* __first, const char* __last, float& value, chars_format fmt) {
+ return from_chars_floating_point<float>(__first, __last, value, fmt);
+}
+
+from_chars_result from_chars_floating_point(const char* __first, const char* __last, double& value, chars_format fmt) {
+ return from_chars_floating_point<double>(__first, __last, value, fmt);
+}
+
+from_chars_result from_chars(const char* __first, const char* __last, float& __value, chars_format fmt) {
+ return std::from_chars_floating_point(__first, __last, __value, fmt);
+}
+
+from_chars_result from_chars(const char* __first, const char* __last, double& __value, chars_format fmt) {
+ return std::from_chars_floating_point(__first, __last, __value, fmt);
+}
+
_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/src/include/from_chars_floating_point.h b/libcxx/src/include/from_chars_floating_point.h
new file mode 100644
index 00000000000000..2efb3bcb4d7802
--- /dev/null
+++ b/libcxx/src/include/from_chars_floating_point.h
@@ -0,0 +1,135 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP_SRC_INCLUDE_FROM_CHARS_FLOATING_POINT_H
+#define _LIBCPP_SRC_INCLUDE_FROM_CHARS_FLOATING_POINT_H
+
+// NEVER DO THIS FOR REAL, this is just for demonstration purposes.
+#define LIBC_NAMESPACE libc_namespace_in_libcxx
+
+// This header is in the shared LLVM-libc header library.
+#include "shared/str_to_float.h"
+
+#include <__assert>
+#include <__config>
+#include <charconv>
+#include <limits>
+#include <type_traits>
+
+// Included for the _Floating_type_traits class
+#include "to_chars_floating_point.h"
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+template <typename _Tp, __enable_if_t<std::is_floating_point<_Tp>::value, int> = 0>
+from_chars_result from_chars_floating_point(const char* __first, const char* __last, _Tp& value, chars_format fmt) {
+ using _Traits = _Floating_type_traits<_Tp>;
+ using _Uint_type = typename _Traits::_Uint_type;
+ ptrdiff_t length = __last - __first;
+ _LIBCPP_ASSERT_INTERNAL(length > 0, "");
+
+ // hacky parsing code as example. Not intended for actual use. I'm just going to handle the base 10
+ // chars_format::general case. Also, no sign, inf, or nan handling.
+ _LIBCPP_ASSERT_INTERNAL(fmt == std::chars_format::general, "");
+
+ const char* src = __first; // rename to match the libc code copied for this section.
+
+ _Uint_type mantissa = 0;
+ int exponent = 0;
+ bool truncated = false;
+ bool seen_digit = false;
+ bool after_decimal = false;
+ size_t index = 0;
+ const size_t BASE = 10;
+ constexpr char EXPONENT_MARKER = 'e';
+ constexpr char DECIMAL_POINT = '.';
+
+ // The loop fills the mantissa with as many digits as it can hold
+ const _Uint_type bitstype_max_div_by_base = numeric_limits<_Uint_type>::max() / BASE;
+ while (index < length) {
+ if (LIBC_NAMESPACE::internal::isdigit(src[index])) {
+ uint32_t digit = src[index] - '0';
+ seen_digit = true;
+
+ if (mantissa < bitstype_max_div_by_base) {
+ mantissa = (mantissa * BASE) + digit;
+ if (after_decimal) {
+ --exponent;
+ }
+ } else {
+ if (digit > 0)
+ truncated = true;
+ if (!after_decimal)
+ ++exponent;
+ }
+
+ ++index;
+ continue;
+ }
+ if (src[index] == DECIMAL_POINT) {
+ if (after_decimal) {
+ break; // this means that src[index] points to a second decimal point, ending the number.
+ }
+ after_decimal = true;
+ ++index;
+ continue;
+ }
+ // The character is neither a digit nor a decimal point.
+ break;
+ }
+
+ if (!seen_digit)
+ return {src + index, {}};
+
+ if (index < length && LIBC_NAMESPACE::internal::tolower(src[index]) == EXPONENT_MARKER) {
+ bool has_sign = false;
+ if (index + 1 < length && (src[index + 1] == '+' || src[index + 1] == '-')) {
+ has_sign = true;
+ }
+ if (index + 1 + static_cast<size_t>(has_sign) < length &&
+ LIBC_NAMESPACE::internal::isdigit(src[index + 1 + static_cast<size_t>(has_sign)])) {
+ ++index;
+ auto result = LIBC_NAMESPACE::internal::strtointeger<int32_t>(src + index, 10);
+ // if (result.has_error())
+ // output.error = result.error;
+ int32_t add_to_exponent = result.value;
+ index += result.parsed_len;
+
+ // Here we do this operation as int64 to avoid overflow.
+ int64_t temp_exponent = static_cast<int64_t>(exponent) + static_cast<int64_t>(add_to_exponent);
+
+ // If the result is in the valid range, then we use it. The valid range is
+ // also within the int32 range, so this prevents overflow issues.
+ if (temp_exponent > LIBC_NAMESPACE::fputil::FPBits<_Tp>::MAX_BIASED_EXPONENT) {
+ exponent = LIBC_NAMESPACE::fputil::FPBits<_Tp>::MAX_BIASED_EXPONENT;
+ } else if (temp_exponent < -LIBC_NAMESPACE::fputil::FPBits<_Tp>::MAX_BIASED_EXPONENT) {
+ exponent = -LIBC_NAMESPACE::fputil::FPBits<_Tp>::MAX_BIASED_EXPONENT;
+ } else {
+ exponent = static_cast<int32_t>(temp_exponent);
+ }
+ }
+ }
+
+ LIBC_NAMESPACE::internal::ExpandedFloat<_Tp> expanded_float = {0, 0};
+ if (mantissa != 0) {
+ auto temp = LIBC_NAMESPACE::shared::decimal_exp_to_float<_Tp>(
+ {mantissa, exponent}, truncated, LIBC_NAMESPACE::internal::RoundDirection::Nearest, src, length);
+ expanded_float = temp.num;
+ // Note: there's also an error value in temp.error. I'm not doing that error handling right now though.
+ }
+
+ auto result = LIBC_NAMESPACE::fputil::FPBits<_Tp>();
+ result.set_mantissa(expanded_float.mantissa);
+ result.set_biased_exponent(expanded_float.exponent);
+ value = result.get_val();
+ return {src + index, {}};
+}
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif //_LIBCPP_SRC_INCLUDE_FROM_CHARS_FLOATING_POINT_H
diff --git a/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp b/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
new file mode 100644
index 00000000000000..3419fc478fec26
--- /dev/null
+++ b/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
@@ -0,0 +1,70 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14
+
+// <charconv>
+
+// constexpr from_chars_result from_chars(const char* first, const char* last,
+// Float& value, chars_format fmt = chars_format::general)
+
+#include <charconv>
+#include "test_macros.h"
+#include "charconv_test_helpers.h"
+
+template <typename T>
+struct test_basics {
+ TEST_CONSTEXPR_CXX23 void operator()() {
+ std::from_chars_result r;
+ T x;
+
+ {
+ char s[] = "001x";
+
+ // the expected form of the subject sequence is a nonempty sequence of
+ // decimal digits optionally containing a decimal-point character, then
+ // an optional exponent part as defined in 6.4.4.3, excluding any digit
+ // separators (6.4.4.2); (C23 7.24.1.5)
+ r = std::from_chars(s, s + sizeof(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == T(1.0));
+ }
+
+ {
+ char s[] = "1.5e10";
+
+ r = std::from_chars(s, s + sizeof(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 6);
+ assert(x == T(1.5e10));
+ }
+
+ {
+ char s[] = "20040229";
+
+ // This number is halfway between two float values.
+ r = std::from_chars(s, s + sizeof(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 8);
+ assert(x == T(20040229));
+ }
+ }
+};
+
+TEST_CONSTEXPR_CXX23 bool test() {
+ run<test_basics>(all_floats);
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+
+ return 0;
+}
diff --git a/libcxx/test/support/charconv_test_helpers.h b/libcxx/test/support/charconv_test_helpers.h
index f5fbedbeb0dcdd..fcae09478457b6 100644
--- a/libcxx/test/support/charconv_test_helpers.h
+++ b/libcxx/test/support/charconv_test_helpers.h
@@ -317,6 +317,8 @@ auto all_unsigned = type_list<
>();
auto integrals = concat(all_signed, all_unsigned);
+auto all_floats = type_list< float, double >(); //TODO: Add long double
+
template <template <typename> class Fn, typename... Ts>
TEST_CONSTEXPR_CXX23 void
run(type_list<Ts...>)
>From 78842bcc3d1575fbfba09da0ad84558eff51d794 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Thu, 9 May 2024 16:04:43 -0700
Subject: [PATCH 02/14] fix warnings and add to modulemap
---
libcxx/include/module.modulemap | 19 ++++++++++---------
.../src/include/from_chars_floating_point.h | 8 ++++----
2 files changed, 14 insertions(+), 13 deletions(-)
diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index 13d0dce34d97e3..6c0ee59e2cc121 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -1075,18 +1075,19 @@ module std_private_bit_invert_if [system] { header "__bit/invert_if.h" }
module std_private_bit_popcount [system] { header "__bit/popcount.h" }
module std_private_bit_rotate [system] { header "__bit/rotate.h" }
-module std_private_charconv_chars_format [system] { header "__charconv/chars_format.h" }
-module std_private_charconv_from_chars_integral [system] { header "__charconv/from_chars_integral.h" }
-module std_private_charconv_from_chars_result [system] { header "__charconv/from_chars_result.h" }
-module std_private_charconv_tables [system] { header "__charconv/tables.h" }
-module std_private_charconv_to_chars [system] { header "__charconv/to_chars.h" }
-module std_private_charconv_to_chars_base_10 [system] { header "__charconv/to_chars_base_10.h" }
-module std_private_charconv_to_chars_floating_point [system] { header "__charconv/to_chars_floating_point.h" }
-module std_private_charconv_to_chars_integral [system] {
+module std_private_charconv_chars_format [system] { header "__charconv/chars_format.h" }
+module std_private_charconv_from_chars_integral [system] { header "__charconv/from_chars_integral.h" }
+module std_private_charconv_from_chars_result [system] { header "__charconv/from_chars_result.h" }
+module std_private_charconv_tables [system] { header "__charconv/tables.h" }
+module std_private_charconv_to_chars [system] { header "__charconv/to_chars.h" }
+module std_private_charconv_to_chars_base_10 [system] { header "__charconv/to_chars_base_10.h" }
+module std_private_charconv_to_chars_floating_point [system] { header "__charconv/to_chars_floating_point.h" }
+module std_private_charconv_from_chars_floating_point [system] { header "__charconv/from_chars_floating_point.h" }
+module std_private_charconv_to_chars_integral [system] {
header "__charconv/to_chars_integral.h"
export std_private_charconv_traits
}
-module std_private_charconv_to_chars_result [system] {
+module std_private_charconv_to_chars_result [system] {
header "__charconv/to_chars_result.h"
export *
}
diff --git a/libcxx/src/include/from_chars_floating_point.h b/libcxx/src/include/from_chars_floating_point.h
index 2efb3bcb4d7802..2a9f4bc84eb8c9 100644
--- a/libcxx/src/include/from_chars_floating_point.h
+++ b/libcxx/src/include/from_chars_floating_point.h
@@ -51,7 +51,7 @@ from_chars_result from_chars_floating_point(const char* __first, const char* __l
// The loop fills the mantissa with as many digits as it can hold
const _Uint_type bitstype_max_div_by_base = numeric_limits<_Uint_type>::max() / BASE;
- while (index < length) {
+ while (index < static_cast<size_t>(length)) {
if (LIBC_NAMESPACE::internal::isdigit(src[index])) {
uint32_t digit = src[index] - '0';
seen_digit = true;
@@ -86,12 +86,12 @@ from_chars_result from_chars_floating_point(const char* __first, const char* __l
if (!seen_digit)
return {src + index, {}};
- if (index < length && LIBC_NAMESPACE::internal::tolower(src[index]) == EXPONENT_MARKER) {
+ if (index < static_cast<size_t>(length) && LIBC_NAMESPACE::internal::tolower(src[index]) == EXPONENT_MARKER) {
bool has_sign = false;
- if (index + 1 < length && (src[index + 1] == '+' || src[index + 1] == '-')) {
+ if (index + 1 < static_cast<size_t>(length) && (src[index + 1] == '+' || src[index + 1] == '-')) {
has_sign = true;
}
- if (index + 1 + static_cast<size_t>(has_sign) < length &&
+ if (index + 1 + static_cast<size_t>(has_sign) < static_cast<size_t>(length) &&
LIBC_NAMESPACE::internal::isdigit(src[index + 1 + static_cast<size_t>(has_sign)])) {
++index;
auto result = LIBC_NAMESPACE::internal::strtointeger<int32_t>(src + index, 10);
>From da3b02f7202414133f8d0b4c53dfe40bbb9c90f0 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Fri, 24 May 2024 15:28:04 -0700
Subject: [PATCH 03/14] fix lint warnings and linkage errors
---
.../__charconv/from_chars_floating_point.h | 4 ++--
libcxx/src/charconv.cpp | 20 +++++++++++--------
.../src/include/from_chars_floating_point.h | 6 +++---
.../charconv.from.chars/float.pass.cpp | 4 ++--
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/libcxx/include/__charconv/from_chars_floating_point.h b/libcxx/include/__charconv/from_chars_floating_point.h
index dd305ca8ea1dcf..d575dbbf611fef 100644
--- a/libcxx/include/__charconv/from_chars_floating_point.h
+++ b/libcxx/include/__charconv/from_chars_floating_point.h
@@ -32,10 +32,10 @@ _LIBCPP_BEGIN_NAMESPACE_STD
#if _LIBCPP_STD_VER >= 17
from_chars_result from_chars_floating_point(
- const char* __first, const char* __last, float& value, chars_format fmt = chars_format::general);
+ const char* __first, const char* __last, float& __value, chars_format __fmt = chars_format::general);
from_chars_result from_chars_floating_point(
- const char* __first, const char* __last, double& value, chars_format fmt = chars_format::general);
+ const char* __first, const char* __last, double& __value, chars_format __fmt = chars_format::general);
// template <typename _Tp, __enable_if_t<is_floating_point<_Tp>::value, int> = 0>
// inline from_chars_result
diff --git a/libcxx/src/charconv.cpp b/libcxx/src/charconv.cpp
index 7d04de2dc4b35f..978ca4cde8c80d 100644
--- a/libcxx/src/charconv.cpp
+++ b/libcxx/src/charconv.cpp
@@ -75,20 +75,24 @@ to_chars_result to_chars(char* __first, char* __last, long double __value, chars
__first, __last, static_cast<double>(__value), __fmt, __precision);
}
-from_chars_result from_chars_floating_point(const char* __first, const char* __last, float& value, chars_format fmt) {
- return from_chars_floating_point<float>(__first, __last, value, fmt);
+from_chars_result
+from_chars_floating_point(const char* __first, const char* __last, float& __value, chars_format __fmt) {
+ return from_chars_floating_point<float>(__first, __last, __value, __fmt);
}
-from_chars_result from_chars_floating_point(const char* __first, const char* __last, double& value, chars_format fmt) {
- return from_chars_floating_point<double>(__first, __last, value, fmt);
+from_chars_result
+from_chars_floating_point(const char* __first, const char* __last, double& __value, chars_format __fmt) {
+ return from_chars_floating_point<double>(__first, __last, __value, __fmt);
}
-from_chars_result from_chars(const char* __first, const char* __last, float& __value, chars_format fmt) {
- return std::from_chars_floating_point(__first, __last, __value, fmt);
+_LIBCPP_EXPORTED_FROM_ABI from_chars_result
+from_chars(const char* __first, const char* __last, float& __value, chars_format __fmt) {
+ return from_chars_floating_point(__first, __last, __value, __fmt);
}
-from_chars_result from_chars(const char* __first, const char* __last, double& __value, chars_format fmt) {
- return std::from_chars_floating_point(__first, __last, __value, fmt);
+_LIBCPP_EXPORTED_FROM_ABI from_chars_result
+from_chars(const char* __first, const char* __last, double& __value, chars_format __fmt) {
+ return from_chars_floating_point(__first, __last, __value, __fmt);
}
_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/src/include/from_chars_floating_point.h b/libcxx/src/include/from_chars_floating_point.h
index 2a9f4bc84eb8c9..fa6a2b3c5e5342 100644
--- a/libcxx/src/include/from_chars_floating_point.h
+++ b/libcxx/src/include/from_chars_floating_point.h
@@ -27,7 +27,7 @@
_LIBCPP_BEGIN_NAMESPACE_STD
template <typename _Tp, __enable_if_t<std::is_floating_point<_Tp>::value, int> = 0>
-from_chars_result from_chars_floating_point(const char* __first, const char* __last, _Tp& value, chars_format fmt) {
+from_chars_result from_chars_floating_point(const char* __first, const char* __last, _Tp& __value, chars_format __fmt) {
using _Traits = _Floating_type_traits<_Tp>;
using _Uint_type = typename _Traits::_Uint_type;
ptrdiff_t length = __last - __first;
@@ -35,7 +35,7 @@ from_chars_result from_chars_floating_point(const char* __first, const char* __l
// hacky parsing code as example. Not intended for actual use. I'm just going to handle the base 10
// chars_format::general case. Also, no sign, inf, or nan handling.
- _LIBCPP_ASSERT_INTERNAL(fmt == std::chars_format::general, "");
+ _LIBCPP_ASSERT_INTERNAL(__fmt == std::chars_format::general, "");
const char* src = __first; // rename to match the libc code copied for this section.
@@ -126,7 +126,7 @@ from_chars_result from_chars_floating_point(const char* __first, const char* __l
auto result = LIBC_NAMESPACE::fputil::FPBits<_Tp>();
result.set_mantissa(expanded_float.mantissa);
result.set_biased_exponent(expanded_float.exponent);
- value = result.get_val();
+ __value = result.get_val();
return {src + index, {}};
}
diff --git a/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp b/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
index 3419fc478fec26..bf39f649084b0b 100644
--- a/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
@@ -19,7 +19,7 @@
template <typename T>
struct test_basics {
- TEST_CONSTEXPR_CXX23 void operator()() {
+ void operator()() {
std::from_chars_result r;
T x;
@@ -57,7 +57,7 @@ struct test_basics {
}
};
-TEST_CONSTEXPR_CXX23 bool test() {
+bool test() {
run<test_basics>(all_floats);
return true;
>From 1d25671854e74f8c6133e03de45f14e86d7d7ea8 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Tue, 30 Jul 2024 13:17:09 -0700
Subject: [PATCH 04/14] address comments and create runtimes target
---
.../__charconv/from_chars_floating_point.h | 36 +++++++------------
libcxx/include/charconv | 6 ++++
libcxx/src/CMakeLists.txt | 10 +++---
libcxx/src/charconv.cpp | 14 ++------
.../src/include/from_chars_floating_point.h | 3 --
.../charconv.from.chars/float.pass.cpp | 12 ++-----
runtimes/cmake/Modules/FindLibcUtils.cmake | 8 +++++
7 files changed, 37 insertions(+), 52 deletions(-)
create mode 100644 runtimes/cmake/Modules/FindLibcUtils.cmake
diff --git a/libcxx/include/__charconv/from_chars_floating_point.h b/libcxx/include/__charconv/from_chars_floating_point.h
index d575dbbf611fef..15fe88e64f2b51 100644
--- a/libcxx/include/__charconv/from_chars_floating_point.h
+++ b/libcxx/include/__charconv/from_chars_floating_point.h
@@ -31,33 +31,21 @@ _LIBCPP_BEGIN_NAMESPACE_STD
#if _LIBCPP_STD_VER >= 17
-from_chars_result from_chars_floating_point(
- const char* __first, const char* __last, float& __value, chars_format __fmt = chars_format::general);
+_LIBCPP_EXPORTED_FROM_ABI from_chars_result
+__from_chars_floating_point(const char* __first, const char* __last, float& __value, chars_format __fmt);
-from_chars_result from_chars_floating_point(
- const char* __first, const char* __last, double& __value, chars_format __fmt = chars_format::general);
+_LIBCPP_EXPORTED_FROM_ABI from_chars_result
+__from_chars_floating_point(const char* __first, const char* __last, double& __value, chars_format __fmt);
-// template <typename _Tp, __enable_if_t<is_floating_point<_Tp>::value, int> = 0>
-// inline from_chars_result
-// from_chars(const char* __first, const char* __last, _Tp& __value, chars_format fmt = chars_format::general) {
-// return std::from_chars_floating_point(__first, __last, __value, fmt);
-// }
+_LIBCPP_HIDE_FROM_ABI inline from_chars_result
+from_chars(const char* __first, const char* __last, float& __value, chars_format __fmt = chars_format::general) {
+ return std::__from_chars_floating_point(__first, __last, __value, __fmt);
+}
-// inline from_chars_result
-// from_chars(const char* __first, const char* __last, float& __value, chars_format fmt = chars_format::general) {
-// return std::from_chars_floating_point(__first, __last, __value, fmt);
-// }
-
-// inline from_chars_result
-// from_chars(const char* __first, const char* __last, double& __value, chars_format fmt = chars_format::general) {
-// return std::from_chars_floating_point(__first, __last, __value, fmt);
-// }
-
-from_chars_result
-from_chars(const char* __first, const char* __last, float& __value, chars_format fmt = chars_format::general);
-
-from_chars_result
-from_chars(const char* __first, const char* __last, double& __value, chars_format fmt = chars_format::general);
+_LIBCPP_HIDE_FROM_ABI inline from_chars_result
+from_chars(const char* __first, const char* __last, double& __value, chars_format __fmt = chars_format::general) {
+ return std::__from_chars_floating_point(__first, __last, __value, __fmt);
+}
#endif // _LIBCPP_STD_VER >= 17
diff --git a/libcxx/include/charconv b/libcxx/include/charconv
index a8d5a5802d890a..dfef8194a2a67b 100644
--- a/libcxx/include/charconv
+++ b/libcxx/include/charconv
@@ -65,6 +65,12 @@ namespace std {
constexpr from_chars_result from_chars(const char* first, const char* last,
see below& value, int base = 10); // constexpr since C++23
+ constexpr from_chars_result from_chars(const char* first, const char* last,
+ float& value, chars_format fmt);
+
+ constexpr from_chars_result from_chars(const char* first, const char* last,
+ double& value, chars_format fmt);
+
} // namespace std
*/
diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt
index 41be5dcb2cbc4e..6183f42252d82b 100644
--- a/libcxx/src/CMakeLists.txt
+++ b/libcxx/src/CMakeLists.txt
@@ -177,11 +177,13 @@ endif()
split_list(LIBCXX_COMPILE_FLAGS)
split_list(LIBCXX_LINK_FLAGS)
+include(FindLibcUtils)
+
# Build the shared library.
if (LIBCXX_ENABLE_SHARED)
add_library(cxx_shared SHARED ${exclude_from_all} ${LIBCXX_SOURCES} ${LIBCXX_HEADERS})
- target_include_directories(cxx_shared PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../../libc) #TODO: Do this properly
- target_link_libraries(cxx_shared PUBLIC cxx-headers libcxx-libc-shared
+ target_include_directories(cxx_shared PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
+ target_link_libraries(cxx_shared PUBLIC cxx-headers libcxx-libc-shared llvm-libc-shared-utilities
PRIVATE ${LIBCXX_LIBRARIES})
set_target_properties(cxx_shared
PROPERTIES
@@ -273,8 +275,8 @@ set(CMAKE_STATIC_LIBRARY_PREFIX "lib")
# Build the static library.
if (LIBCXX_ENABLE_STATIC)
add_library(cxx_static STATIC ${exclude_from_all} ${LIBCXX_SOURCES} ${LIBCXX_HEADERS})
- target_include_directories(cxx_static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../../libc) #TODO: Do this properly
- target_link_libraries(cxx_static PUBLIC cxx-headers libcxx-libc-static
+ target_include_directories(cxx_static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
+ target_link_libraries(cxx_static PUBLIC cxx-headers libcxx-libc-static llvm-libc-shared-utilities
PRIVATE ${LIBCXX_LIBRARIES}
PRIVATE libcxx-abi-static)
set_target_properties(cxx_static
diff --git a/libcxx/src/charconv.cpp b/libcxx/src/charconv.cpp
index 978ca4cde8c80d..767b6cc9f28dd7 100644
--- a/libcxx/src/charconv.cpp
+++ b/libcxx/src/charconv.cpp
@@ -76,23 +76,13 @@ to_chars_result to_chars(char* __first, char* __last, long double __value, chars
}
from_chars_result
-from_chars_floating_point(const char* __first, const char* __last, float& __value, chars_format __fmt) {
+__from_chars_floating_point(const char* __first, const char* __last, float& __value, chars_format __fmt) {
return from_chars_floating_point<float>(__first, __last, __value, __fmt);
}
from_chars_result
-from_chars_floating_point(const char* __first, const char* __last, double& __value, chars_format __fmt) {
+__from_chars_floating_point(const char* __first, const char* __last, double& __value, chars_format __fmt) {
return from_chars_floating_point<double>(__first, __last, __value, __fmt);
}
-_LIBCPP_EXPORTED_FROM_ABI from_chars_result
-from_chars(const char* __first, const char* __last, float& __value, chars_format __fmt) {
- return from_chars_floating_point(__first, __last, __value, __fmt);
-}
-
-_LIBCPP_EXPORTED_FROM_ABI from_chars_result
-from_chars(const char* __first, const char* __last, double& __value, chars_format __fmt) {
- return from_chars_floating_point(__first, __last, __value, __fmt);
-}
-
_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/src/include/from_chars_floating_point.h b/libcxx/src/include/from_chars_floating_point.h
index fa6a2b3c5e5342..74a45d8a6fb2e9 100644
--- a/libcxx/src/include/from_chars_floating_point.h
+++ b/libcxx/src/include/from_chars_floating_point.h
@@ -9,9 +9,6 @@
#ifndef _LIBCPP_SRC_INCLUDE_FROM_CHARS_FLOATING_POINT_H
#define _LIBCPP_SRC_INCLUDE_FROM_CHARS_FLOATING_POINT_H
-// NEVER DO THIS FOR REAL, this is just for demonstration purposes.
-#define LIBC_NAMESPACE libc_namespace_in_libcxx
-
// This header is in the shared LLVM-libc header library.
#include "shared/str_to_float.h"
diff --git a/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp b/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
index bf39f649084b0b..2f13210702beaa 100644
--- a/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
@@ -10,8 +10,8 @@
// <charconv>
-// constexpr from_chars_result from_chars(const char* first, const char* last,
-// Float& value, chars_format fmt = chars_format::general)
+// from_chars_result from_chars(const char* first, const char* last,
+// Float& value, chars_format fmt = chars_format::general)
#include <charconv>
#include "test_macros.h"
@@ -57,14 +57,8 @@ struct test_basics {
}
};
-bool test() {
- run<test_basics>(all_floats);
-
- return true;
-}
-
int main(int, char**) {
- test();
+ run<test_basics>(all_floats);
return 0;
}
diff --git a/runtimes/cmake/Modules/FindLibcUtils.cmake b/runtimes/cmake/Modules/FindLibcUtils.cmake
new file mode 100644
index 00000000000000..a1378401629fa6
--- /dev/null
+++ b/runtimes/cmake/Modules/FindLibcUtils.cmake
@@ -0,0 +1,8 @@
+add_library(llvm-libc-shared-utilities INTERFACE)
+# TODO: Reorganize the libc shared section so that it can be included without
+# adding the root "libc" directory to the include path.
+# TODO: Find a better way to solve the problem that ${CMAKE_SOURCE_DIR} is
+# rooted in the runtimes directory, which is why we need the ".."
+target_include_directories(llvm-libc-shared-utilities INTERFACE ${CMAKE_SOURCE_DIR}/../libc)
+target_compile_definitions(llvm-libc-shared-utilities INTERFACE LIBC_NAMESPACE=__llvm_libc_shared_utils)
+target_compile_features(llvm-libc-shared-utilities INTERFACE cxx_std_17)
>From 6a59316b87f94a35e112d1f3d39eac99b0824211 Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Sun, 4 Aug 2024 16:40:26 +0200
Subject: [PATCH 05/14] Updates (part of) the ABI lists.
Other can be updated when their CI run fails.
---
...64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist | 2 ++
...powerpc-ibm-aix.libcxxabi.v1.stable.exceptions.nonew.abilist | 2 ++
...werpc64-ibm-aix.libcxxabi.v1.stable.exceptions.nonew.abilist | 2 ++
...64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist | 2 ++
...linux-android21.libcxxabi.v1.stable.exceptions.nonew.abilist | 2 ++
...unknown-freebsd.libcxxabi.v1.stable.exceptions.nonew.abilist | 2 ++
...known-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist | 2 ++
7 files changed, 14 insertions(+)
diff --git a/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist
index 917388f86811fe..7e227263ae10ae 100644
--- a/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist
+++ b/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist
@@ -1587,6 +1587,8 @@
{'is_defined': True, 'name': '__ZNSt3__123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIxNS_22__cxx_atomic_base_implIxEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'}
+{'is_defined': True, 'name': '__ZNSt3__127__from_chars_floating_pointEPKcS1_RdNS_12chars_formatE', 'type': 'FUNC'}
+{'is_defined': True, 'name': '__ZNSt3__127__from_chars_floating_pointEPKcS1_RfNS_12chars_formatE', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__131__arrive_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseEh', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__132__destroy_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseE', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__134__construct_barrier_algorithm_baseERl', 'type': 'FUNC'}
diff --git a/libcxx/lib/abi/powerpc-ibm-aix.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/powerpc-ibm-aix.libcxxabi.v1.stable.exceptions.nonew.abilist
index 033d9f9987fa80..e0aca153ca0a2a 100644
--- a/libcxx/lib/abi/powerpc-ibm-aix.libcxxabi.v1.stable.exceptions.nonew.abilist
+++ b/libcxx/lib/abi/powerpc-ibm-aix.libcxxabi.v1.stable.exceptions.nonew.abilist
@@ -572,6 +572,8 @@
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIiNS_22__cxx_atomic_base_implIiEEEE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKv', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
+{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__127__from_chars_floating_pointEPKcS1_RdNS_12chars_formatE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
+{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__127__from_chars_floating_pointEPKcS1_RfNS_12chars_formatE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__131__arrive_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseEh', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__132__destroy_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__134__construct_barrier_algorithm_baseERl', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
diff --git a/libcxx/lib/abi/powerpc64-ibm-aix.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/powerpc64-ibm-aix.libcxxabi.v1.stable.exceptions.nonew.abilist
index 332d8abeb03e3a..d3c3012c443b0a 100644
--- a/libcxx/lib/abi/powerpc64-ibm-aix.libcxxabi.v1.stable.exceptions.nonew.abilist
+++ b/libcxx/lib/abi/powerpc64-ibm-aix.libcxxabi.v1.stable.exceptions.nonew.abilist
@@ -572,6 +572,8 @@
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIlNS_22__cxx_atomic_base_implIlEEEE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKv', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
+{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__127__from_chars_floating_pointEPKcS1_RdNS_12chars_formatE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
+{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__127__from_chars_floating_pointEPKcS1_RfNS_12chars_formatE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__131__arrive_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseEh', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__132__destroy_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__134__construct_barrier_algorithm_baseERl', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
diff --git a/libcxx/lib/abi/x86_64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/x86_64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist
index defe235a283c21..a9dd2a5851f7f5 100644
--- a/libcxx/lib/abi/x86_64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist
+++ b/libcxx/lib/abi/x86_64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist
@@ -1587,6 +1587,8 @@
{'is_defined': True, 'name': '__ZNSt3__123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIxNS_22__cxx_atomic_base_implIxEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'}
+{'is_defined': True, 'name': '__ZNSt3__127__from_chars_floating_pointEPKcS1_RdNS_12chars_formatE', 'type': 'FUNC'}
+{'is_defined': True, 'name': '__ZNSt3__127__from_chars_floating_pointEPKcS1_RfNS_12chars_formatE', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__131__arrive_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseEh', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__132__destroy_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseE', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__134__construct_barrier_algorithm_baseERl', 'type': 'FUNC'}
diff --git a/libcxx/lib/abi/x86_64-linux-android21.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/x86_64-linux-android21.libcxxabi.v1.stable.exceptions.nonew.abilist
index 6b77cda1e2866d..55c69232375b07 100644
--- a/libcxx/lib/abi/x86_64-linux-android21.libcxxabi.v1.stable.exceptions.nonew.abilist
+++ b/libcxx/lib/abi/x86_64-linux-android21.libcxxabi.v1.stable.exceptions.nonew.abilist
@@ -1220,6 +1220,8 @@
{'is_defined': True, 'name': '_ZNSt6__ndk123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIiNS_22__cxx_atomic_base_implIiEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt6__ndk127__from_chars_floating_pointEPKcS1_RdNS_12chars_formatE', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt6__ndk127__from_chars_floating_pointEPKcS1_RfNS_12chars_formatE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk131__arrive_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseEh', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk132__destroy_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk134__construct_barrier_algorithm_baseERl', 'type': 'FUNC'}
diff --git a/libcxx/lib/abi/x86_64-unknown-freebsd.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/x86_64-unknown-freebsd.libcxxabi.v1.stable.exceptions.nonew.abilist
index 3458b333dd6a9b..356440b6cfd152 100644
--- a/libcxx/lib/abi/x86_64-unknown-freebsd.libcxxabi.v1.stable.exceptions.nonew.abilist
+++ b/libcxx/lib/abi/x86_64-unknown-freebsd.libcxxabi.v1.stable.exceptions.nonew.abilist
@@ -1235,6 +1235,8 @@
{'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIlNS_22__cxx_atomic_base_implIlEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt3__127__from_chars_floating_pointEPKcS1_RdNS_12chars_formatE', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt3__127__from_chars_floating_pointEPKcS1_RfNS_12chars_formatE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__131__arrive_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseEh', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__132__destroy_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__134__construct_barrier_algorithm_baseERl', 'type': 'FUNC'}
diff --git a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist
index bdf90ba25c7fd9..0fc3be345fa8d3 100644
--- a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist
+++ b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist
@@ -1233,6 +1233,8 @@
{'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIiNS_22__cxx_atomic_base_implIiEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt3__127__from_chars_floating_pointEPKcS1_RdNS_12chars_formatE', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt3__127__from_chars_floating_pointEPKcS1_RfNS_12chars_formatE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__131__arrive_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseEh', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__132__destroy_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__134__construct_barrier_algorithm_baseERl', 'type': 'FUNC'}
>From 8041880b30c7a63fa06d62d9167829c62625ca3a Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Sun, 4 Aug 2024 18:02:20 +0200
Subject: [PATCH 06/14] Several improvements
- using availability macros
- handle leading whitespace
- implement inf and nan
- implement negative values
---
.../__charconv/from_chars_floating_point.h | 4 +-
libcxx/include/__configuration/availability.h | 13 +
libcxx/src/charconv.cpp | 4 +-
.../src/include/from_chars_floating_point.h | 170 ++++++-
.../charconv.from.chars/float.pass.cpp | 447 +++++++++++++++++-
5 files changed, 610 insertions(+), 28 deletions(-)
diff --git a/libcxx/include/__charconv/from_chars_floating_point.h b/libcxx/include/__charconv/from_chars_floating_point.h
index 15fe88e64f2b51..33ecf7012cfb6a 100644
--- a/libcxx/include/__charconv/from_chars_floating_point.h
+++ b/libcxx/include/__charconv/from_chars_floating_point.h
@@ -31,10 +31,10 @@ _LIBCPP_BEGIN_NAMESPACE_STD
#if _LIBCPP_STD_VER >= 17
-_LIBCPP_EXPORTED_FROM_ABI from_chars_result
+_LIBCPP_AVAILABILITY_FROM_CHARS_FLOATING_POINT _LIBCPP_EXPORTED_FROM_ABI from_chars_result
__from_chars_floating_point(const char* __first, const char* __last, float& __value, chars_format __fmt);
-_LIBCPP_EXPORTED_FROM_ABI from_chars_result
+_LIBCPP_AVAILABILITY_FROM_CHARS_FLOATING_POINT _LIBCPP_EXPORTED_FROM_ABI from_chars_result
__from_chars_floating_point(const char* __first, const char* __last, double& __value, chars_format __fmt);
_LIBCPP_HIDE_FROM_ABI inline from_chars_result
diff --git a/libcxx/include/__configuration/availability.h b/libcxx/include/__configuration/availability.h
index ab483a07c9c137..edd9df87ebca99 100644
--- a/libcxx/include/__configuration/availability.h
+++ b/libcxx/include/__configuration/availability.h
@@ -87,6 +87,9 @@
// in all versions of the library are available.
#if defined(_LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS)
+# define _LIBCPP_INTRODUCED_IN_LLVM_20 1
+# define _LIBCPP_INTRODUCED_IN_LLVM_20_ATTRIBUTE /* nothing */
+
# define _LIBCPP_INTRODUCED_IN_LLVM_19 1
# define _LIBCPP_INTRODUCED_IN_LLVM_19_ATTRIBUTE /* nothing */
@@ -132,6 +135,11 @@
// clang-format off
+// LLVM 20
+// TODO: Fill this in
+# define _LIBCPP_INTRODUCED_IN_LLVM_20 0
+# define _LIBCPP_INTRODUCED_IN_LLVM_20_ATTRIBUTE __attribute__((unavailable))
+
// LLVM 19
// TODO: Fill this in
# define _LIBCPP_INTRODUCED_IN_LLVM_19 0
@@ -375,6 +383,11 @@
#define _LIBCPP_AVAILABILITY_HAS_BAD_EXPECTED_ACCESS_KEY_FUNCTION _LIBCPP_INTRODUCED_IN_LLVM_19
#define _LIBCPP_AVAILABILITY_BAD_EXPECTED_ACCESS_KEY_FUNCTION _LIBCPP_INTRODUCED_IN_LLVM_19_ATTRIBUTE
+// This controls the availability of floating-point std::from_chars functions.
+// These overloads were added later than the integer overloads.
+#define _LIBCPP_AVAILABILITY_HAS_FROM_CHARS_FLOATING_POINT _LIBCPP_INTRODUCED_IN_LLVM_20
+#define _LIBCPP_AVAILABILITY_FROM_CHARS_FLOATING_POINT _LIBCPP_INTRODUCED_IN_LLVM_20_ATTRIBUTE
+
// Define availability attributes that depend on _LIBCPP_HAS_NO_EXCEPTIONS.
// Those are defined in terms of the availability attributes above, and
// should not be vendor-specific.
diff --git a/libcxx/src/charconv.cpp b/libcxx/src/charconv.cpp
index 767b6cc9f28dd7..8299df46a6292e 100644
--- a/libcxx/src/charconv.cpp
+++ b/libcxx/src/charconv.cpp
@@ -77,12 +77,12 @@ to_chars_result to_chars(char* __first, char* __last, long double __value, chars
from_chars_result
__from_chars_floating_point(const char* __first, const char* __last, float& __value, chars_format __fmt) {
- return from_chars_floating_point<float>(__first, __last, __value, __fmt);
+ return std::__from_chars_floating_point<float>(__first, __last, __value, __fmt);
}
from_chars_result
__from_chars_floating_point(const char* __first, const char* __last, double& __value, chars_format __fmt) {
- return from_chars_floating_point<double>(__first, __last, __value, __fmt);
+ return std::__from_chars_floating_point<double>(__first, __last, __value, __fmt);
}
_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/src/include/from_chars_floating_point.h b/libcxx/src/include/from_chars_floating_point.h
index 74a45d8a6fb2e9..d40a230b884a04 100644
--- a/libcxx/src/include/from_chars_floating_point.h
+++ b/libcxx/src/include/from_chars_floating_point.h
@@ -14,8 +14,11 @@
#include <__assert>
#include <__config>
+#include <cctype>
#include <charconv>
+#include <concepts>
#include <limits>
+#include <cstring>
#include <type_traits>
// Included for the _Floating_type_traits class
@@ -23,18 +26,112 @@
_LIBCPP_BEGIN_NAMESPACE_STD
-template <typename _Tp, __enable_if_t<std::is_floating_point<_Tp>::value, int> = 0>
-from_chars_result from_chars_floating_point(const char* __first, const char* __last, _Tp& __value, chars_format __fmt) {
+// Parses an infinity string.
+// Valid strings are case insentitive and contain INF or INFINITY.
+//
+// - __first is the first argument to std::from_chars. When the string is invalid
+// this value is returned as ptr in the result.
+// - __last is the last argument of std::from_chars.
+// - __value is the value argument of std::from_chars,
+// - __ptr is the current position is the input string. This is points beyond
+// the initial I character.
+// - __negative whether a valid string represents -inf or +inf.
+template <floating_point _Tp>
+from_chars_result __from_chars_floating_point_inf(
+ const char* const __first, const char* __last, _Tp& __value, const char* __ptr, bool __negative) {
+ if (__last - __ptr < 2) [[unlikely]]
+ return {__first, errc::invalid_argument};
+
+ if (std::tolower(__ptr[0]) != 'n' || std::tolower(__ptr[1]) != 'f') [[unlikely]]
+ return {__first, errc::invalid_argument};
+
+ __ptr += 2;
+
+ // At this point the result is valid and contains INF.
+ // When the remaining part contains INITY this will be consumed. Otherwise
+ // only INF is consumed. For example INFINITZ will consume INF and ignore
+ // INITZ.
+
+ if (__last - __ptr >= 5 //
+ && std::tolower(__ptr[0]) == 'i' //
+ && std::tolower(__ptr[1]) == 'n' //
+ && std::tolower(__ptr[2]) == 'i' //
+ && std::tolower(__ptr[3]) == 't' //
+ && std::tolower(__ptr[4]) == 'y')
+ __ptr += 5;
+
+ if constexpr (numeric_limits<_Tp>::has_infinity) {
+ if (__negative)
+ __value = -std::numeric_limits<_Tp>::infinity();
+ else
+ __value = std::numeric_limits<_Tp>::infinity();
+
+ return {__ptr, std::errc{}};
+ } else {
+ return {__ptr, errc::result_out_of_range};
+ }
+}
+
+// Parses an infinita string.
+// Valid strings are case insentitive and contain INF or INFINITY.
+//
+// - __first is the first argument to std::from_chars. When the string is invalid
+// this value is returned as ptr in the result.
+// - __last is the last argument of std::from_chars.
+// - __value is the value argument of std::from_chars,
+// - __ptr is the current position is the input string. This is points beyond
+// the initial N character.
+// - __negative whether a valid string represents -nan or +nan.
+template <floating_point _Tp>
+from_chars_result __from_chars_floating_point_nan(
+ const char* const __first, const char* __last, _Tp& __value, const char* __ptr, bool __negative) {
+ if (__last - __ptr < 2) [[unlikely]]
+ return {__first, errc::invalid_argument};
+
+ if (std::tolower(__ptr[0]) != 'a' || std::tolower(__ptr[1]) != 'n') [[unlikely]]
+ return {__first, errc::invalid_argument};
+
+ __ptr += 2;
+
+ // At this point the result is valid and contains NAN. When the remaining
+ // part contains ( n-char-sequence_opt ) this will be consumed. Otherwise
+ // only NAN is consumed. For example NAN(abcd will consume NAN and ignore
+ // (abcd.
+ if (__last - __ptr >= 2 && __ptr[0] == '(') {
+ size_t __offset = 1;
+ do {
+ if (__ptr[__offset] == ')') {
+ __ptr += __offset + 1;
+ break;
+ }
+ if (__ptr[__offset] != '_' && !std::isalnum(__ptr[__offset]))
+ break;
+ ++__offset;
+ } while (__ptr + __offset != __last);
+ }
+
+ if (__negative)
+ __value = -std::numeric_limits<_Tp>::quiet_NaN();
+ else
+ __value = std::numeric_limits<_Tp>::quiet_NaN();
+
+ return {__ptr, std::errc{}};
+}
+
+template <floating_point _Tp>
+from_chars_result __from_chars_floating_point_decimal(
+ const char* const __first,
+ const char* __last,
+ _Tp& __value,
+ chars_format __fmt,
+ const char* __ptr,
+ bool __negative) {
using _Traits = _Floating_type_traits<_Tp>;
using _Uint_type = typename _Traits::_Uint_type;
ptrdiff_t length = __last - __first;
_LIBCPP_ASSERT_INTERNAL(length > 0, "");
- // hacky parsing code as example. Not intended for actual use. I'm just going to handle the base 10
- // chars_format::general case. Also, no sign, inf, or nan handling.
- _LIBCPP_ASSERT_INTERNAL(__fmt == std::chars_format::general, "");
-
- const char* src = __first; // rename to match the libc code copied for this section.
+ const char* src = __ptr; // rename to match the libc code copied for this section.
_Uint_type mantissa = 0;
int exponent = 0;
@@ -123,10 +220,67 @@ from_chars_result from_chars_floating_point(const char* __first, const char* __l
auto result = LIBC_NAMESPACE::fputil::FPBits<_Tp>();
result.set_mantissa(expanded_float.mantissa);
result.set_biased_exponent(expanded_float.exponent);
- __value = result.get_val();
+ if (__negative)
+ __value = -result.get_val();
+ else
+ __value = result.get_val();
return {src + index, {}};
}
+template <floating_point _Tp>
+from_chars_result
+__from_chars_floating_point(const char* const __first, const char* __last, _Tp& __value, chars_format __fmt) {
+ if (__first == __last) [[unlikely]]
+ return {__first, errc::invalid_argument};
+
+ const char* __ptr = __first;
+
+ // skip whitespace
+ while (std::isspace(*__ptr)) {
+ ++__ptr;
+ if (__ptr == __last) [[unlikely]]
+ return {__first, errc::invalid_argument}; // is this valid??
+ }
+
+ bool __negative = *__ptr == '-';
+ if (__negative) {
+ ++__ptr;
+ if (__ptr == __last) [[unlikely]]
+ return {__first, errc::invalid_argument};
+ }
+
+ if (!std::isdigit(*__ptr)) {
+ // TODO Evaluate the other implementations
+ // [charconv.from.chars]/6.2
+ // if fmt has chars_format::scientific set but not chars_format::fixed,
+ // the otherwise optional exponent part shall appear;
+ // Since INF/NAN do not have an exponent this value is not valid.
+ // See LWG3456
+ if (__fmt == chars_format::scientific)
+ return {__first, errc::invalid_argument};
+
+ switch (std::tolower(*__ptr)) {
+ case 'i':
+ return __from_chars_floating_point_inf(__first, __last, __value, __ptr + 1, __negative);
+ case 'n':
+ if constexpr (numeric_limits<_Tp>::has_quiet_NaN)
+ return __from_chars_floating_point_nan(__first, __last, __value, __ptr + 1, __negative);
+ [[fallthrough]];
+ default:
+ return {__first, errc::invalid_argument};
+ }
+ }
+
+#if 1
+ _LIBCPP_ASSERT_INTERNAL(__fmt == std::chars_format::general, "");
+#else
+ if (__fmt == chars_format::hex)
+ return std::__from_chars_floating_point_hex(__first, __last, __value);
+#endif
+
+ return std::__from_chars_floating_point_decimal(__first, __last, __value, __fmt, __ptr, __negative);
+}
+
_LIBCPP_END_NAMESPACE_STD
#endif //_LIBCPP_SRC_INCLUDE_FROM_CHARS_FLOATING_POINT_H
diff --git a/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp b/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
index 2f13210702beaa..99699c037b4615 100644
--- a/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
@@ -8,57 +8,472 @@
// UNSUPPORTED: c++03, c++11, c++14
+// ADDITIONAL_COMPILE_FLAGS: -O0 -g
+
// <charconv>
// from_chars_result from_chars(const char* first, const char* last,
// Float& value, chars_format fmt = chars_format::general)
+#include <array>
#include <charconv>
-#include "test_macros.h"
+#include <cmath>
+#include <limits>
+
#include "charconv_test_helpers.h"
+#include "test_macros.h"
+
+template <class F>
+void test_infinity(std::chars_format fmt) {
+ const char* s = "-InFiNiTyXXX";
+ { // I
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s + 1, s + 2, value, fmt);
+
+ assert(result.ec == std::errc::invalid_argument);
+ assert(result.ptr == s + 1);
+ assert(value == F(0.25));
+ }
+ { // In
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s + 1, s + 3, value, fmt);
+
+ assert(result.ec == std::errc::invalid_argument);
+ assert(result.ptr == s + 1);
+ assert(value == F(0.25));
+ }
+ { // InF
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s + 1, s + 4, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 4);
+ assert(value == std::numeric_limits<F>::infinity());
+ }
+ { // -InF
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s, s + 4, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 4);
+ assert(value == -std::numeric_limits<F>::infinity());
+ }
+ { // InFi
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s + 1, s + 5, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 4);
+ assert(value == std::numeric_limits<F>::infinity());
+ }
+ { // -InFiN
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s, s + 6, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 4);
+ assert(value == -std::numeric_limits<F>::infinity());
+ }
+ { // InFiNi
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s + 1, s + 7, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 4);
+ assert(value == std::numeric_limits<F>::infinity());
+ }
+ { // -InFiNiT
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s, s + 8, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 4);
+ assert(value == -std::numeric_limits<F>::infinity());
+ }
+ { // InFiNiTy
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s + 1, s + 9, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 9);
+ assert(value == std::numeric_limits<F>::infinity());
+ }
+ { // -InFiNiTy
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s, s + 9, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 9);
+ assert(value == -std::numeric_limits<F>::infinity());
+ }
+ { // InFiNiTyXXX
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s + 1, s + 12, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 9);
+ assert(value == std::numeric_limits<F>::infinity());
+ }
+ { // -InFiNiTyXXX
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s, s + 12, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 9);
+ assert(value == -std::numeric_limits<F>::infinity());
+ }
+}
+
+template <class F>
+void test_nan(std::chars_format fmt) {
+ {
+ const char* s = "-NaN(1_A)XXX";
+ { // N
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s + 1, s + 2, value, fmt);
+
+ assert(result.ec == std::errc::invalid_argument);
+ assert(result.ptr == s + 1);
+ assert(value == F(0.25));
+ }
+ { // Na
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s + 1, s + 3, value, fmt);
+
+ assert(result.ec == std::errc::invalid_argument);
+ assert(result.ptr == s + 1);
+ assert(value == F(0.25));
+ }
+ { // NaN
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s + 1, s + 4, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 4);
+ assert(std::isnan(value));
+ assert(!std::signbit(value));
+ }
+ { // -NaN
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s + 0, s + 4, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 4);
+ assert(std::isnan(value));
+ assert(std::signbit(value));
+ }
+ { // NaN(
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s + 1, s + 5, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 4);
+ assert(std::isnan(value));
+ assert(!std::signbit(value));
+ }
+ { // -NaN(1
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s, s + 6, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 4);
+ assert(std::isnan(value));
+ assert(std::signbit(value));
+ }
+ { // NaN(1_
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s + 1, s + 7, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 4);
+ assert(std::isnan(value));
+ assert(!std::signbit(value));
+ }
+ { // -NaN(1_A
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s, s + 8, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 4);
+ assert(std::isnan(value));
+ assert(std::signbit(value));
+ }
+ { // NaN(1_A)
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s + 1, s + 9, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 9);
+ assert(std::isnan(value));
+ assert(!std::signbit(value));
+ }
+ { // -NaN(1_A)
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s, s + 9, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 9);
+ assert(std::isnan(value));
+ assert(std::signbit(value));
+ }
+ { // NaN(1_A)XXX
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s + 1, s + 12, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 9);
+ assert(std::isnan(value));
+ assert(!std::signbit(value));
+ }
+ { // -NaN(1_A)XXX
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s, s + 12, value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 9);
+ assert(std::isnan(value));
+ assert(std::signbit(value));
+ }
+ }
+ {
+ const char* s = "NaN()";
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s, s + std::strlen(s), value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s + 5);
+ assert(std::isnan(value));
+ assert(!std::signbit(value));
+ }
+ { // validates a n-char-sequences with an invalid value
+ std::array s = {'N', 'a', 'N', '(', ' ', ')'};
+ s[4] = 'a';
+ {
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(s.data(), s.data() + s.size(), value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s.data() + s.size());
+ assert(std::isnan(value));
+ assert(!std::signbit(value));
+ }
+ for (auto c : "!@#$%^&*(-=+[]{}|\\;:'\",./<>?~` \t\v\r\n") {
+ F value = 0.25;
+ s[4] = c;
+ std::from_chars_result result = std::from_chars(s.data(), s.data() + s.size(), value, fmt);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == s.data() + 3);
+ assert(std::isnan(value));
+ assert(!std::signbit(value));
+ }
+ }
+}
+
+template <class F>
+void test_fmt_independent(std::chars_format fmt) {
+ { // first == last
+ F value = 0.25;
+ std::from_chars_result result = std::from_chars(nullptr, nullptr, value, fmt);
+
+ assert(result.ec == std::errc::invalid_argument);
+ assert(result.ptr == nullptr);
+ assert(value == F(0.25));
+ }
+ { // only a sign
+ F value = 0.25;
+ const char* s = "-";
+ std::from_chars_result result = std::from_chars(s, s + std::strlen(s), value, fmt);
+
+ assert(result.ec == std::errc::invalid_argument);
+ assert(result.ptr == s);
+ assert(value == F(0.25));
+ }
+ if (fmt != std::chars_format::scientific) {
+ test_infinity<F>(fmt);
+ test_nan<F>(fmt);
+ } else {
+ { // infinity
+ F value = 0.25;
+ const char* s = "inf";
+ std::from_chars_result result = std::from_chars(s, s + std::strlen(s), value, fmt);
+
+ assert(result.ec == std::errc::invalid_argument);
+ assert(result.ptr == s);
+ assert(value == F(0.25));
+ }
+ { // nan
+ F value = 0.25;
+ const char* s = "nan";
+ std::from_chars_result result = std::from_chars(s, s + std::strlen(s), value, fmt);
+
+ assert(result.ec == std::errc::invalid_argument);
+ assert(result.ptr == s);
+ assert(value == F(0.25));
+ }
+ }
+ { // start with decimal separator
+ F value = 0.25;
+ const char* s = ".";
+ std::from_chars_result result = std::from_chars(s, s + std::strlen(s), value, fmt);
-template <typename T>
+ assert(result.ec == std::errc::invalid_argument);
+ assert(result.ptr == s);
+ assert(value == F(0.25));
+ }
+ { // Invalid sign
+ F value = 0.25;
+ const char* s = "+0.25";
+ std::from_chars_result result = std::from_chars(s, s + std::strlen(s), value, fmt);
+
+ assert(result.ec == std::errc::invalid_argument);
+ assert(result.ptr == s);
+ assert(value == F(0.25));
+ }
+}
+
+template <class F>
struct test_basics {
+ void operator()() {
+ for (auto fmt :
+ {std::chars_format::scientific, std::chars_format::fixed, std::chars_format::hex, std::chars_format::general})
+ test_fmt_independent<F>(fmt);
+ }
+};
+
+template <class F>
+struct test_general {
void operator()() {
std::from_chars_result r;
- T x;
+ F x;
- {
- char s[] = "001x";
+ { // number followed by non-numeric valies
+ const char* s = "001x";
// the expected form of the subject sequence is a nonempty sequence of
// decimal digits optionally containing a decimal-point character, then
// an optional exponent part as defined in 6.4.4.3, excluding any digit
// separators (6.4.4.2); (C23 7.24.1.5)
- r = std::from_chars(s, s + sizeof(s), x);
+ r = std::from_chars(s, s + std::strlen(s), x);
assert(r.ec == std::errc{});
assert(r.ptr == s + 3);
- assert(x == T(1.0));
+ assert(x == F(1.0));
}
- {
- char s[] = "1.5e10";
+ { // double deciamal point
+ const char* s = "1.25.78";
+
+ // This number is halfway between two float values.
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(1.25));
+ }
- r = std::from_chars(s, s + sizeof(s), x);
+ { // exponenent no sign
+ const char* s = "1.5e10";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
assert(r.ec == std::errc{});
assert(r.ptr == s + 6);
- assert(x == T(1.5e10));
+ assert(x == F(1.5e10));
}
+ { // exponenent + sign
+ const char* s = "1.5e+10";
- {
- char s[] = "20040229";
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 7);
+ assert(x == F(1.5e10));
+ }
+ { // exponenent - sign
+ const char* s = "1.5e-10";
- // This number is halfway between two float values.
- r = std::from_chars(s, s + sizeof(s), x);
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 7);
+ assert(x == F(1.5e-10));
+ }
+ { // exponent double sign
+ const char* s = "1.25e++12";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(1.25));
+ }
+ { // double exponent
+ const char* s = "1.25e0e12";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 6);
+ assert(x == F(1.25));
+ }
+ { // This number is halfway between two float values.
+ const char* s = "20040229";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
assert(r.ec == std::errc{});
assert(r.ptr == s + 8);
- assert(x == T(20040229));
+ assert(x == F(20040229));
+ }
+ { // Shifting mantissa exponent and no exponent
+ const char* s = "123.456";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 7);
+ assert(x == F(1.23456e2));
+ }
+ { // Shifting mantissa exponent and an exponent
+ const char* s = "123.456e3";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 9);
+ assert(x == F(1.23456e5));
+ }
+ { // Mantissa overflow
+ {
+ const char* s = "0.111111111111111111111111111111111111111111";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + std::strlen(s));
+ assert(x == F(0.111111111111111111111111111111111111111111));
+ }
+ {
+ const char* s = "111111111111.111111111111111111111111111111111111111111";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + std::strlen(s));
+ assert(x == F(111111111111.111111111111111111111111111111111111111111));
+ }
+ }
+ { // Leading whitespace
+ const char* s = " \t\v\r\n0.25";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + std::strlen(s));
+ assert(x == F(0.25));
+ }
+ { // Negative value
+ const char* s = "-0.25";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + std::strlen(s));
+ assert(x == F(-0.25));
}
}
};
int main(int, char**) {
run<test_basics>(all_floats);
+ run<test_general>(all_floats);
return 0;
}
>From f2072fcc3a4357e1aa128aaab6bffd81348a5b01 Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Sun, 4 Aug 2024 18:02:20 +0200
Subject: [PATCH 07/14] Several improvements
- address review comment
- CI fixes
- implemented scientific
- implemented fixed
- adjust pattern to subject sequence instead of allowing whitespace
- Implemented out of range issues
- Partly enabled the MSVC tests for from_chars floating-point
- Fixes exponent out of bounds reading
---
.../src/include/from_chars_floating_point.h | 151 +++--
.../charconv.from.chars/float.pass.cpp | 575 +++++++++++++++++-
.../float_from_chars_test_cases.hpp | 1 -
.../utilities/charconv/charconv.msvc/test.cpp | 50 +-
.../charconv/charconv.msvc/test.pass.cpp | 3 +
.../test/support/msvc_stdlib_force_include.h | 1 +
libcxx/test/support/test_macros.h | 4 +
7 files changed, 706 insertions(+), 79 deletions(-)
diff --git a/libcxx/src/include/from_chars_floating_point.h b/libcxx/src/include/from_chars_floating_point.h
index d40a230b884a04..b7d142394620ce 100644
--- a/libcxx/src/include/from_chars_floating_point.h
+++ b/libcxx/src/include/from_chars_floating_point.h
@@ -18,7 +18,6 @@
#include <charconv>
#include <concepts>
#include <limits>
-#include <cstring>
#include <type_traits>
// Included for the _Floating_type_traits class
@@ -36,9 +35,9 @@ _LIBCPP_BEGIN_NAMESPACE_STD
// - __ptr is the current position is the input string. This is points beyond
// the initial I character.
// - __negative whether a valid string represents -inf or +inf.
-template <floating_point _Tp>
+template <floating_point _Fp>
from_chars_result __from_chars_floating_point_inf(
- const char* const __first, const char* __last, _Tp& __value, const char* __ptr, bool __negative) {
+ const char* const __first, const char* __last, _Fp& __value, const char* __ptr, bool __negative) {
if (__last - __ptr < 2) [[unlikely]]
return {__first, errc::invalid_argument};
@@ -60,11 +59,11 @@ from_chars_result __from_chars_floating_point_inf(
&& std::tolower(__ptr[4]) == 'y')
__ptr += 5;
- if constexpr (numeric_limits<_Tp>::has_infinity) {
+ if constexpr (numeric_limits<_Fp>::has_infinity) {
if (__negative)
- __value = -std::numeric_limits<_Tp>::infinity();
+ __value = -std::numeric_limits<_Fp>::infinity();
else
- __value = std::numeric_limits<_Tp>::infinity();
+ __value = std::numeric_limits<_Fp>::infinity();
return {__ptr, std::errc{}};
} else {
@@ -72,7 +71,7 @@ from_chars_result __from_chars_floating_point_inf(
}
}
-// Parses an infinita string.
+// Parses a nan string.
// Valid strings are case insentitive and contain INF or INFINITY.
//
// - __first is the first argument to std::from_chars. When the string is invalid
@@ -82,9 +81,9 @@ from_chars_result __from_chars_floating_point_inf(
// - __ptr is the current position is the input string. This is points beyond
// the initial N character.
// - __negative whether a valid string represents -nan or +nan.
-template <floating_point _Tp>
+template <floating_point _Fp>
from_chars_result __from_chars_floating_point_nan(
- const char* const __first, const char* __last, _Tp& __value, const char* __ptr, bool __negative) {
+ const char* const __first, const char* __last, _Fp& __value, const char* __ptr, bool __negative) {
if (__last - __ptr < 2) [[unlikely]]
return {__first, errc::invalid_argument};
@@ -111,32 +110,33 @@ from_chars_result __from_chars_floating_point_nan(
}
if (__negative)
- __value = -std::numeric_limits<_Tp>::quiet_NaN();
+ __value = -std::numeric_limits<_Fp>::quiet_NaN();
else
- __value = std::numeric_limits<_Tp>::quiet_NaN();
+ __value = std::numeric_limits<_Fp>::quiet_NaN();
return {__ptr, std::errc{}};
}
-template <floating_point _Tp>
+template <floating_point _Fp>
from_chars_result __from_chars_floating_point_decimal(
const char* const __first,
const char* __last,
- _Tp& __value,
+ _Fp& __value,
chars_format __fmt,
const char* __ptr,
bool __negative) {
- using _Traits = _Floating_type_traits<_Tp>;
+ using _Traits = _Floating_type_traits<_Fp>;
using _Uint_type = typename _Traits::_Uint_type;
- ptrdiff_t length = __last - __first;
- _LIBCPP_ASSERT_INTERNAL(length > 0, "");
- const char* src = __ptr; // rename to match the libc code copied for this section.
+ const char* src = __ptr; // rename to match the libc code copied for this section.
+ ptrdiff_t length = __last - src;
+ _LIBCPP_ASSERT_INTERNAL(length > 0, "");
_Uint_type mantissa = 0;
int exponent = 0;
bool truncated = false;
bool seen_digit = false;
+ bool has_valid_exponent = false;
bool after_decimal = false;
size_t index = 0;
const size_t BASE = 10;
@@ -145,6 +145,7 @@ from_chars_result __from_chars_floating_point_decimal(
// The loop fills the mantissa with as many digits as it can hold
const _Uint_type bitstype_max_div_by_base = numeric_limits<_Uint_type>::max() / BASE;
+
while (index < static_cast<size_t>(length)) {
if (LIBC_NAMESPACE::internal::isdigit(src[index])) {
uint32_t digit = src[index] - '0';
@@ -178,7 +179,7 @@ from_chars_result __from_chars_floating_point_decimal(
}
if (!seen_digit)
- return {src + index, {}};
+ return {__first, errc::invalid_argument};
if (index < static_cast<size_t>(length) && LIBC_NAMESPACE::internal::tolower(src[index]) == EXPONENT_MARKER) {
bool has_sign = false;
@@ -187,8 +188,10 @@ from_chars_result __from_chars_floating_point_decimal(
}
if (index + 1 + static_cast<size_t>(has_sign) < static_cast<size_t>(length) &&
LIBC_NAMESPACE::internal::isdigit(src[index + 1 + static_cast<size_t>(has_sign)])) {
+ has_valid_exponent = true;
++index;
- auto result = LIBC_NAMESPACE::internal::strtointeger<int32_t>(src + index, 10);
+ auto result =
+ LIBC_NAMESPACE::internal::strtointeger<int32_t>(src + index, 10, static_cast<size_t>(length) - index);
// if (result.has_error())
// output.error = result.error;
int32_t add_to_exponent = result.value;
@@ -199,57 +202,102 @@ from_chars_result __from_chars_floating_point_decimal(
// If the result is in the valid range, then we use it. The valid range is
// also within the int32 range, so this prevents overflow issues.
- if (temp_exponent > LIBC_NAMESPACE::fputil::FPBits<_Tp>::MAX_BIASED_EXPONENT) {
- exponent = LIBC_NAMESPACE::fputil::FPBits<_Tp>::MAX_BIASED_EXPONENT;
- } else if (temp_exponent < -LIBC_NAMESPACE::fputil::FPBits<_Tp>::MAX_BIASED_EXPONENT) {
- exponent = -LIBC_NAMESPACE::fputil::FPBits<_Tp>::MAX_BIASED_EXPONENT;
+ if (temp_exponent > LIBC_NAMESPACE::fputil::FPBits<_Fp>::MAX_BIASED_EXPONENT) {
+ exponent = LIBC_NAMESPACE::fputil::FPBits<_Fp>::MAX_BIASED_EXPONENT;
+ } else if (temp_exponent < -LIBC_NAMESPACE::fputil::FPBits<_Fp>::MAX_BIASED_EXPONENT) {
+ exponent = -LIBC_NAMESPACE::fputil::FPBits<_Fp>::MAX_BIASED_EXPONENT;
} else {
exponent = static_cast<int32_t>(temp_exponent);
}
}
}
- LIBC_NAMESPACE::internal::ExpandedFloat<_Tp> expanded_float = {0, 0};
+ // [charconv.from.chars]
+ switch (__fmt) {
+ case chars_format::scientific:
+ // 6.2 if fmt has chars_format::scientific set but not chars_format::fixed,
+ // the otherwise optional exponent part shall appear;
+ if (!has_valid_exponent)
+ return {__first, errc::invalid_argument};
+ break;
+ case chars_format::fixed:
+ // 6.3 if fmt has chars_format::fixed set but not chars_format::scientific,
+ // the optional exponent part shall not appear;
+ if (has_valid_exponent)
+ return {__first, errc::invalid_argument};
+ break;
+ case chars_format::general:
+ case chars_format::hex: // impossible but it silences the compiler
+ break;
+ }
+
+ LIBC_NAMESPACE::internal::ExpandedFloat<_Fp> expanded_float = {0, 0};
+ errc status{};
if (mantissa != 0) {
- auto temp = LIBC_NAMESPACE::shared::decimal_exp_to_float<_Tp>(
+ auto temp = LIBC_NAMESPACE::shared::decimal_exp_to_float<_Fp>(
{mantissa, exponent}, truncated, LIBC_NAMESPACE::internal::RoundDirection::Nearest, src, length);
expanded_float = temp.num;
- // Note: there's also an error value in temp.error. I'm not doing that error handling right now though.
+ if (temp.error == ERANGE) {
+ status = errc::result_out_of_range;
+ }
}
- auto result = LIBC_NAMESPACE::fputil::FPBits<_Tp>();
+ auto result = LIBC_NAMESPACE::fputil::FPBits<_Fp>();
result.set_mantissa(expanded_float.mantissa);
result.set_biased_exponent(expanded_float.exponent);
+
+ // C17 7.12.1/6
+ // The result underflows if the magnitude of the mathematical result is so
+ // small that the mathematical re- sult cannot be represented, without
+ // extraordinary roundoff error, in an object of the specified type.237) If
+ // the result underflows, the function returns an implementation-defined
+ // value whose magnitude is no greater than the smallest normalized positive
+ // number in the specified type; if the integer expression math_errhandling
+ // & MATH_ERRNO is nonzero, whether errno acquires the value ERANGE is
+ // implementation-defined; if the integer expression math_errhandling &
+ // MATH_ERREXCEPT is nonzero, whether the "underflow" floating-point
+ // exception is raised is implementation-defined.
+ //
+ // LLLVM-LIBC sets ERAGNE for subnormal values
+ //
+ // [charconv.from.chars]/1
+ // ... If the parsed value is not in the range representable by the type of
+ // value, value is unmodified and the member ec of the return value is
+ // equal to errc::result_out_of_range. ...
+ //
+ // Undo the ERANGE for subnormal values.
+ if (status == errc::result_out_of_range && result.is_subnormal() && !result.is_zero())
+ status = errc{};
+
if (__negative)
__value = -result.get_val();
else
__value = result.get_val();
- return {src + index, {}};
+
+ return {src + index, status};
}
-template <floating_point _Tp>
+template <floating_point _Fp>
from_chars_result
-__from_chars_floating_point(const char* const __first, const char* __last, _Tp& __value, chars_format __fmt) {
+__from_chars_floating_point(const char* const __first, const char* __last, _Fp& __value, chars_format __fmt) {
if (__first == __last) [[unlikely]]
return {__first, errc::invalid_argument};
const char* __ptr = __first;
-
- // skip whitespace
- while (std::isspace(*__ptr)) {
- ++__ptr;
- if (__ptr == __last) [[unlikely]]
- return {__first, errc::invalid_argument}; // is this valid??
- }
-
- bool __negative = *__ptr == '-';
+ bool __negative = *__ptr == '-';
if (__negative) {
++__ptr;
if (__ptr == __last) [[unlikely]]
return {__first, errc::invalid_argument};
}
- if (!std::isdigit(*__ptr)) {
+ // [charconv.from.chars]
+ // [Note 1: If the pattern allows for an optional sign, but the string has
+ // no digit characters following the sign, no characters match the pattern.
+ // — end note]
+ // This is true for integrals, floating point allows -.0
+ switch (std::tolower(*__ptr)) {
+ case 'i':
// TODO Evaluate the other implementations
// [charconv.from.chars]/6.2
// if fmt has chars_format::scientific set but not chars_format::fixed,
@@ -259,20 +307,23 @@ __from_chars_floating_point(const char* const __first, const char* __last, _Tp&
if (__fmt == chars_format::scientific)
return {__first, errc::invalid_argument};
- switch (std::tolower(*__ptr)) {
- case 'i':
- return __from_chars_floating_point_inf(__first, __last, __value, __ptr + 1, __negative);
- case 'n':
- if constexpr (numeric_limits<_Tp>::has_quiet_NaN)
- return __from_chars_floating_point_nan(__first, __last, __value, __ptr + 1, __negative);
- [[fallthrough]];
- default:
+ return __from_chars_floating_point_inf(__first, __last, __value, __ptr + 1, __negative);
+ case 'n':
+ // TODO Evaluate the other implementations
+ // [charconv.from.chars]/6.2
+ // if fmt has chars_format::scientific set but not chars_format::fixed,
+ // the otherwise optional exponent part shall appear;
+ // Since INF/NAN do not have an exponent this value is not valid.
+ // See LWG3456
+ if (__fmt == chars_format::scientific)
return {__first, errc::invalid_argument};
- }
+ if constexpr (numeric_limits<_Fp>::has_quiet_NaN)
+ return __from_chars_floating_point_nan(__first, __last, __value, __ptr + 1, __negative);
+ return {__first, errc::invalid_argument};
}
#if 1
- _LIBCPP_ASSERT_INTERNAL(__fmt == std::chars_format::general, "");
+ _LIBCPP_ASSERT_INTERNAL(__fmt != std::chars_format::hex, "");
#else
if (__fmt == chars_format::hex)
return std::__from_chars_floating_point_hex(__first, __last, __value);
diff --git a/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp b/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
index 99699c037b4615..4f55cdec77ba9c 100644
--- a/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
@@ -18,7 +18,10 @@
#include <array>
#include <charconv>
#include <cmath>
+#include <cstring>
#include <limits>
+#include <stdexcept>
+#include <system_error>
#include "charconv_test_helpers.h"
#include "test_macros.h"
@@ -312,7 +315,7 @@ void test_fmt_independent(std::chars_format fmt) {
assert(value == F(0.25));
}
}
- { // start with decimal separator
+ { // only decimal separator
F value = 0.25;
const char* s = ".";
std::from_chars_result result = std::from_chars(s, s + std::strlen(s), value, fmt);
@@ -321,7 +324,16 @@ void test_fmt_independent(std::chars_format fmt) {
assert(result.ptr == s);
assert(value == F(0.25));
}
- { // Invalid sign
+ { // sign and decimal separator
+ F value = 0.25;
+ const char* s = "-.";
+ std::from_chars_result result = std::from_chars(s, s + std::strlen(s), value, fmt);
+
+ assert(result.ec == std::errc::invalid_argument);
+ assert(result.ptr == s);
+ assert(value == F(0.25));
+ }
+ { // + sign is not allowed
F value = 0.25;
const char* s = "+0.25";
std::from_chars_result result = std::from_chars(s, s + std::strlen(s), value, fmt);
@@ -335,19 +347,422 @@ void test_fmt_independent(std::chars_format fmt) {
template <class F>
struct test_basics {
void operator()() {
- for (auto fmt :
- {std::chars_format::scientific, std::chars_format::fixed, std::chars_format::hex, std::chars_format::general})
+ for (auto fmt : {std::chars_format::scientific,
+ std::chars_format::fixed,
+ /*std::chars_format::hex,*/ std::chars_format::general})
test_fmt_independent<F>(fmt);
}
};
+template <class F>
+struct test_fixed {
+ void operator()() {
+ std::from_chars_result r;
+ F x = 0.25;
+
+ // *** Failures
+
+ { // Starts with invalid character
+ std::array s = {' ', '1'};
+ for (auto c : "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "`~!@#$%^&*()_=[]{}\\|;:'\",/<>? \t\v\r\n") {
+ s[0] = c;
+ r = std::from_chars(s.data(), s.data() + s.size(), x, std::chars_format::fixed);
+
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s.data());
+ assert(x == F(0.25));
+ }
+ }
+
+ { // exponenent no sign
+ const char* s = "1.5e10";
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
+ { // exponenent capitalized no sign
+ const char* s = "1.5E10";
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
+ { // exponenent + sign
+ const char* s = "1.5e+10";
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
+ { // exponenent - sign
+ const char* s = "1.5e-10";
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
+ { // double exponent
+ const char* s = "1.25e0e12";
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
+ { // Shifting mantissa exponent and an exponent
+ const char* s = "123.456e3";
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
+
+ // *** Success
+
+ { // number followed by non-numeric values
+ const char* s = "001x";
+
+ // the expected form of the subject sequence is a nonempty sequence of
+ // decimal digits optionally containing a decimal-point character, then
+ // an optional exponent part as defined in 6.4.4.3, excluding any digit
+ // separators (6.4.4.2); (C23 7.24.1.5)
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.0));
+ }
+ { // no leading digit
+ const char* s = ".5";
+
+ // the expected form of the subject sequence is a nonempty sequence of
+ // decimal digits optionally containing a decimal-point character, then
+ // an optional exponent part as defined in 6.4.4.3, excluding any digit
+ // separators (6.4.4.2); (C23 7.24.1.5)
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 2);
+ assert(x == F(0.5));
+ }
+ { // negative sign and no leading digit
+ const char* s = "-.5";
+
+ // the expected form of the subject sequence is a nonempty sequence of
+ // decimal digits optionally containing a decimal-point character, then
+ // an optional exponent part as defined in 6.4.4.3, excluding any digit
+ // separators (6.4.4.2); (C23 7.24.1.5)
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(-0.5));
+ }
+
+ { // double deciamal point
+ const char* s = "1.25.78";
+
+ // This number is halfway between two float values.
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(1.25));
+ }
+ { // Exponent no number
+ const char* s = "1.5e";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
+ { // Exponent sign no number
+ const char* s = "1.5e+";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
+ { // exponent double sign
+ const char* s = "1.25e++12";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(1.25));
+ }
+ { // This number is halfway between two float values.
+ const char* s = "20040229";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 8);
+ assert(x == F(20040229));
+ }
+ { // Shifting mantissa exponent and no exponent
+ const char* s = "123.456";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 7);
+ assert(x == F(1.23456e2));
+ }
+ { // Mantissa overflow
+ {
+ const char* s = "0.111111111111111111111111111111111111111111";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + std::strlen(s));
+ assert(x == F(0.111111111111111111111111111111111111111111));
+ }
+ {
+ const char* s = "111111111111.111111111111111111111111111111111111111111";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + std::strlen(s));
+ assert(x == F(111111111111.111111111111111111111111111111111111111111));
+ }
+ }
+ { // Negative value
+ const char* s = "-0.25";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + std::strlen(s));
+ assert(x == F(-0.25));
+ }
+ }
+};
+
+template <class F>
+struct test_scientific {
+ void operator()() {
+ std::from_chars_result r;
+ F x = 0.25;
+
+ // *** Failures
+
+ { // Starts with invalid character
+ std::array s = {' ', '1', 'e', '0'};
+ for (auto c : "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "`~!@#$%^&*()_=[]{}\\|;:'\",/<>? \t\v\r\n") {
+ s[0] = c;
+ r = std::from_chars(s.data(), s.data() + s.size(), x, std::chars_format::scientific);
+
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s.data());
+ assert(x == F(0.25));
+ }
+ }
+ { // No exponent
+ const char* s = "1.23";
+ r = std::from_chars(s, s + strlen(s), x, std::chars_format::scientific);
+
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
+ { // Exponent no number
+ const char* s = "1.23e";
+ r = std::from_chars(s, s + strlen(s), x, std::chars_format::scientific);
+
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
+ { // Exponent sign no number
+ const char* s = "1.5e+";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
+ { // exponent double sign
+ const char* s = "1.25e++12";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
+
+ // *** Success
+
+ { // number followed by non-numeric values
+ const char* s = "001e0x";
+
+ // the expected form of the subject sequence is a nonempty sequence of
+ // decimal digits optionally containing a decimal-point character, then
+ // an optional exponent part as defined in 6.4.4.3, excluding any digit
+ // separators (6.4.4.2); (C23 7.24.1.5)
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 5);
+ assert(x == F(1.0));
+ }
+
+ { // double deciamal point
+ const char* s = "1.25e0.78";
+
+ // This number is halfway between two float values.
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 6);
+ assert(x == F(1.25));
+ }
+
+ { // exponenent no sign
+ const char* s = "1.5e10";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 6);
+ assert(x == F(1.5e10));
+ }
+ { // exponenent capitalized no sign
+ const char* s = "1.5E10";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 6);
+ assert(x == F(1.5e10));
+ }
+ { // exponenent + sign
+ const char* s = "1.5e+10";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 7);
+ assert(x == F(1.5e10));
+ }
+ { // exponenent - sign
+ const char* s = "1.5e-10";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 7);
+ assert(x == F(1.5e-10));
+ }
+ { // double exponent
+ const char* s = "1.25e0e12";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 6);
+ assert(x == F(1.25));
+ }
+ { // This number is halfway between two float values.
+ const char* s = "20040229e0";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 10);
+ assert(x == F(20040229));
+ }
+ { // Shifting mantissa exponent and an exponent
+ const char* s = "123.456e3";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 9);
+ assert(x == F(1.23456e5));
+ }
+ { // Mantissa overflow
+ {
+ const char* s = "0.111111111111111111111111111111111111111111e0";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + std::strlen(s));
+ assert(x == F(0.111111111111111111111111111111111111111111));
+ }
+ {
+ const char* s = "111111111111.111111111111111111111111111111111111111111e0";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + std::strlen(s));
+ assert(x == F(111111111111.111111111111111111111111111111111111111111));
+ }
+ }
+ { // Negative value
+ const char* s = "-0.25e0";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + std::strlen(s));
+ assert(x == F(-0.25));
+ }
+ { // value is too big -> +inf
+ const char* s = "1e9999999999999999999999999999999999999999";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc::result_out_of_range);
+ assert(r.ptr == s + strlen(s));
+ assert(x == std::numeric_limits<F>::infinity());
+ }
+ { // negative value is too big -> -inf
+ const char* s = "-1e9999999999999999999999999999999999999999";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc::result_out_of_range);
+ assert(r.ptr == s + strlen(s));
+ assert(x == -std::numeric_limits<F>::infinity());
+ }
+ { // value is too small -> 0
+ const char* s = "1e-9999999999999999999999999999999999999999";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc::result_out_of_range);
+ assert(r.ptr == s + strlen(s));
+ assert(x == F(0.0));
+ }
+ { // negative value is too small -> -0
+ const char* s = "-1e-9999999999999999999999999999999999999999";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc::result_out_of_range);
+ assert(r.ptr == s + strlen(s));
+ assert(x == F(-0.0));
+ }
+ }
+};
+
template <class F>
struct test_general {
void operator()() {
std::from_chars_result r;
- F x;
+ F x = 0.25;
+
+ // *** Failures
+
+ { // Starts with invalid character
+ std::array s = {' ', '1'};
+ for (auto c : "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "`~!@#$%^&*()_=[]{}\\|;:'\",/<>? \t\v\r\n") {
+ s[0] = c;
+ r = std::from_chars(s.data(), s.data() + s.size(), x);
- { // number followed by non-numeric valies
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s.data());
+ assert(x == F(0.25));
+ }
+ }
+
+ // *** Success
+
+ { // number followed by non-numeric values
const char* s = "001x";
// the expected form of the subject sequence is a nonempty sequence of
@@ -359,7 +774,54 @@ struct test_general {
assert(r.ptr == s + 3);
assert(x == F(1.0));
}
+ { // no leading digit
+ const char* s = ".5e0";
+
+ // the expected form of the subject sequence is a nonempty sequence of
+ // decimal digits optionally containing a decimal-point character, then
+ // an optional exponent part as defined in 6.4.4.3, excluding any digit
+ // separators (6.4.4.2); (C23 7.24.1.5)
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(0.5));
+ }
+ { // negative sign and no leading digit
+ const char* s = "-.5e0";
+
+ // the expected form of the subject sequence is a nonempty sequence of
+ // decimal digits optionally containing a decimal-point character, then
+ // an optional exponent part as defined in 6.4.4.3, excluding any digit
+ // separators (6.4.4.2); (C23 7.24.1.5)
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 5);
+ assert(x == F(-0.5));
+ }
+ { // no leading digit
+ const char* s = ".5";
+
+ // the expected form of the subject sequence is a nonempty sequence of
+ // decimal digits optionally containing a decimal-point character, then
+ // an optional exponent part as defined in 6.4.4.3, excluding any digit
+ // separators (6.4.4.2); (C23 7.24.1.5)
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 2);
+ assert(x == F(0.5));
+ }
+ { // negative sign and no leading digit
+ const char* s = "-.5";
+ // the expected form of the subject sequence is a nonempty sequence of
+ // decimal digits optionally containing a decimal-point character, then
+ // an optional exponent part as defined in 6.4.4.3, excluding any digit
+ // separators (6.4.4.2); (C23 7.24.1.5)
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(-0.5));
+ }
{ // double deciamal point
const char* s = "1.25.78";
@@ -369,7 +831,6 @@ struct test_general {
assert(r.ptr == s + 4);
assert(x == F(1.25));
}
-
{ // exponenent no sign
const char* s = "1.5e10";
@@ -378,6 +839,14 @@ struct test_general {
assert(r.ptr == s + 6);
assert(x == F(1.5e10));
}
+ { // exponenent capitalized no sign
+ const char* s = "1.5E10";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 6);
+ assert(x == F(1.5e10));
+ }
{ // exponenent + sign
const char* s = "1.5e+10";
@@ -394,6 +863,22 @@ struct test_general {
assert(r.ptr == s + 7);
assert(x == F(1.5e-10));
}
+ { // Exponent no number
+ const char* s = "1.5e";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
+ { // Exponent sign no number
+ const char* s = "1.5e+";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
{ // exponent double sign
const char* s = "1.25e++12";
@@ -452,14 +937,6 @@ struct test_general {
assert(x == F(111111111111.111111111111111111111111111111111111111111));
}
}
- { // Leading whitespace
- const char* s = " \t\v\r\n0.25";
-
- r = std::from_chars(s, s + std::strlen(s), x);
- assert(r.ec == std::errc{});
- assert(r.ptr == s + std::strlen(s));
- assert(x == F(0.25));
- }
{ // Negative value
const char* s = "-0.25";
@@ -468,12 +945,80 @@ struct test_general {
assert(r.ptr == s + std::strlen(s));
assert(x == F(-0.25));
}
+ { // value is too big -> +inf
+ const char* s = "1e9999999999999999999999999999999999999999";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc::result_out_of_range);
+ assert(r.ptr == s + strlen(s));
+ assert(x == std::numeric_limits<F>::infinity());
+ }
+ { // negative value is too big -> -inf
+ const char* s = "-1e9999999999999999999999999999999999999999";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc::result_out_of_range);
+ assert(r.ptr == s + strlen(s));
+ assert(x == -std::numeric_limits<F>::infinity());
+ }
+ { // value is too small -> 0
+ const char* s = "1e-9999999999999999999999999999999999999999";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc::result_out_of_range);
+ assert(r.ptr == s + strlen(s));
+ assert(x == F(0.0));
+ }
+ { // negative value is too small -> -0
+ const char* s = "-1e-9999999999999999999999999999999999999999";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc::result_out_of_range);
+ assert(r.ptr == s + strlen(s));
+ assert(x == F(-0.0));
+ }
}
};
+// The test
+// test/std/utilities/charconv/charconv.msvc/test.cpp
+// uses random values. This tests contains errors found by this test.
+void test_random_errors() {
+ {
+ const char* s = "4.219902180869891e-2788";
+ const char* last = s + std::strlen(s) - 1;
+
+ // last + 1 contains a digit. When that value is parsed the exponent is
+ // e-2788 which returns std::errc::result_out_of_range and the value 0.
+ // the proper exponent is e-278, which can be represented by a double.
+
+ double value = 0.25;
+ std::from_chars_result result = std::from_chars(s, last, value);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == last);
+ assert(value == 4.219902180869891e-278);
+ }
+ {
+ const char* s = "7.411412e-39U";
+ const char* last = s + std::strlen(s) - 1;
+
+ float value = 0.25;
+ std::from_chars_result result = std::from_chars(s, last, value);
+
+ assert(result.ec == std::errc{});
+ assert(result.ptr == last);
+ assert(value == 7.411412e-39F);
+ }
+}
+
int main(int, char**) {
run<test_basics>(all_floats);
+ run<test_scientific>(all_floats);
+ run<test_fixed>(all_floats);
run<test_general>(all_floats);
+ test_random_errors();
+
return 0;
}
diff --git a/libcxx/test/std/utilities/charconv/charconv.msvc/float_from_chars_test_cases.hpp b/libcxx/test/std/utilities/charconv/charconv.msvc/float_from_chars_test_cases.hpp
index c0fbfcb132f7ef..9fd5b0768442eb 100644
--- a/libcxx/test/std/utilities/charconv/charconv.msvc/float_from_chars_test_cases.hpp
+++ b/libcxx/test/std/utilities/charconv/charconv.msvc/float_from_chars_test_cases.hpp
@@ -121,7 +121,6 @@ inline constexpr FloatFromCharsTestCase float_from_chars_test_cases[] = {
"2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222"
"2222222222222222222e-45",
chars_format::scientific, 1006, errc{}, 0x0.000004p-126f},
-
// VSO-733765 "<charconv>: [Feedback] double std::from_chars behavior on exponent out of range"
// LWG-3081 "Floating point from_chars API does not distinguish between overflow and underflow"
// These test cases exercise every overflow/underflow codepath.
diff --git a/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp b/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp
index 30ee9adcd74bf0..a5218c6b6d7971 100644
--- a/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp
@@ -27,6 +27,8 @@
#include <utility>
#include <vector>
+#include <ranges> // TODO REMOVE
+
#include "double_fixed_precision_to_chars_test_cases_1.hpp"
#include "double_fixed_precision_to_chars_test_cases_2.hpp"
#include "double_fixed_precision_to_chars_test_cases_3.hpp"
@@ -45,6 +47,7 @@
#include "float_hex_precision_to_chars_test_cases.hpp"
#include "float_scientific_precision_to_chars_test_cases.hpp"
#include "float_to_chars_test_cases.hpp"
+#include "floating_point_test_cases.hpp"
using namespace std;
@@ -389,7 +392,22 @@ void test_from_chars(const string_view input, const BaseOrFmt base_or_fmt, const
if constexpr (is_integral_v<T>) {
assert(mode == TestFromCharsMode::Normal);
}
-
+#if 1
+ else {
+ // Not implemented yet.
+ if (base_or_fmt == std::chars_format::hex)
+ return;
+ // LWG3456. Pattern used by std::from_chars is underspecified
+ if (base_or_fmt == std::chars_format::scientific && correct_ec == std::errc{} && opt_correct &&
+ !std::isfinite(*opt_correct))
+ // Based on the wording in the Standard scientific can't be NaN or Inf.
+ return;
+ if (base_or_fmt == std::chars_format::fixed && correct_ec == std::errc{} &&
+ std::ranges::contains(input, 'e', [](char c) { return std::tolower(c); }))
+ // Fixed should not contain an exponent. MSVC terminates at e, libc++ marks as invalid.
+ return;
+ }
+#endif
constexpr T unmodified = 111;
T dest = unmodified;
@@ -589,8 +607,8 @@ void test_floating_prefix(const conditional_t<IsDouble, std::uint64_t, std::uint
// "-1.2345678901234567e-100" or "-1.23456789e-10"
constexpr std::size_t buffer_size = IsDouble ? 24 : 15;
char buffer[buffer_size];
-// TODO Enable once std::from_chars has floating point support.
-#if 0
+
+#ifdef TEST_HAS_FROM_CHARS_FLOATING_POINT
FloatingType val;
#endif
@@ -614,8 +632,7 @@ void test_floating_prefix(const conditional_t<IsDouble, std::uint64_t, std::uint
{
const auto to_result = to_chars(buffer, end(buffer), input, chars_format::scientific);
assert_message_bits(to_result.ec == errc{}, "to_result.ec", bits);
-// TODO Enable once std::from_chars has floating point support.
-#if 0
+#ifdef TEST_HAS_FROM_CHARS_FLOATING_POINT
const char* const last = to_result.ptr;
const auto from_result = from_chars(buffer, last, val);
@@ -623,7 +640,7 @@ void test_floating_prefix(const conditional_t<IsDouble, std::uint64_t, std::uint
assert_message_bits(from_result.ptr == last, "from_result.ptr", bits);
assert_message_bits(from_result.ec == errc{}, "from_result.ec", bits);
assert_message_bits(_Bit_cast<UIntType>(val) == bits, "round-trip", bits);
-#endif
+#endif // TEST_HAS_FROM_CHARS_FLOATING_POINT
}
{
@@ -786,8 +803,7 @@ void test_floating_prefixes(mt19937_64& mt64) {
}
}
-// TODO Enable once std::from_chars has floating point support.
-#if 0
+#ifdef TEST_HAS_FROM_CHARS_FLOATING_POINT
template <typename T>
void test_floating_from_chars(const chars_format fmt) {
test_from_chars<T>("", fmt, 0, inv_arg); // no characters
@@ -855,11 +871,20 @@ void test_floating_from_chars(const chars_format fmt) {
// The UCRT considers indeterminate NaN to be negative quiet NaN with no payload bits set.
// It parses "nan(ind)" and "-nan(ind)" identically.
+ //
+ //
+ // TODO Evaluate this code, probably needs to be commented out.
+ //
+ //
+#if 0
test_from_chars<T>("nan(InD)", fmt, 8, errc{}, -qnan);
+#endif
test_from_chars<T>("-nan(InD)", fmt, 9, errc{}, -qnan);
+#if 0
test_from_chars<T>("nan(SnAn)", fmt, 9, errc{}, nullopt, TestFromCharsMode::SignalingNaN);
test_from_chars<T>("-nan(SnAn)", fmt, 10, errc{}, nullopt, TestFromCharsMode::SignalingNaN);
+#endif
switch (fmt) {
case chars_format::general:
@@ -941,7 +966,7 @@ void test_floating_from_chars(const chars_format fmt) {
break;
}
}
-#endif
+#endif // TEST_HAS_FROM_CHARS_FLOATING_POINT
template <typename T>
void test_floating_to_chars(
@@ -953,13 +978,11 @@ void test_floating_to_chars(
void all_floating_tests(mt19937_64& mt64) {
test_floating_prefixes(mt64);
-// TODO Enable once std::from_chars has floating point support.
-#if 0
+#ifdef TEST_HAS_FROM_CHARS_FLOATING_POINT
for (const auto& fmt : {chars_format::general, chars_format::scientific, chars_format::fixed, chars_format::hex}) {
test_floating_from_chars<float>(fmt);
test_floating_from_chars<double>(fmt);
}
-
// Test rounding.
// See float_from_chars_test_cases.hpp in this directory.
@@ -993,7 +1016,8 @@ void all_floating_tests(mt19937_64& mt64) {
for (const auto& p : floating_point_test_cases_double) {
test_from_chars<double>(p.first, chars_format::general, strlen(p.first), errc{}, _Bit_cast<double>(p.second));
}
-#endif
+#endif // TEST_HAS_FROM_CHARS_FLOATING_POINT
+
// See float_to_chars_test_cases.hpp in this directory.
for (const auto& t : float_to_chars_test_cases) {
if (t.fmt == chars_format{}) {
diff --git a/libcxx/test/std/utilities/charconv/charconv.msvc/test.pass.cpp b/libcxx/test/std/utilities/charconv/charconv.msvc/test.pass.cpp
index 09ef70ea9924e8..190805687beb8c 100644
--- a/libcxx/test/std/utilities/charconv/charconv.msvc/test.pass.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.msvc/test.pass.cpp
@@ -8,6 +8,8 @@
// UNSUPPORTED: c++03, c++11, c++14
+// ADDITIONAL_COMPILE_FLAGS: -O0 -g
+
// to_chars requires functions in the dylib that have not been introduced in older
// versions of the dylib on macOS.
// XFAIL: availability-fp_to_chars-missing
@@ -22,6 +24,7 @@
// <charconv>
#include <type_traits>
+#include "test_macros.h"
// Work-around for sprintf_s's usage in the Microsoft tests.
#ifndef _WIN32
diff --git a/libcxx/test/support/msvc_stdlib_force_include.h b/libcxx/test/support/msvc_stdlib_force_include.h
index 785670224c3b18..6bfe6e39b711e9 100644
--- a/libcxx/test/support/msvc_stdlib_force_include.h
+++ b/libcxx/test/support/msvc_stdlib_force_include.h
@@ -103,5 +103,6 @@ const AssertionDialogAvoider assertion_dialog_avoider{};
#define TEST_ABI_MICROSOFT
#define _LIBCPP_AVAILABILITY_THROW_BAD_ANY_CAST
+#define _LIBCPP_AVAILABILITY_HAS_FROM_CHARS_FLOATING_POINT 1
#endif // SUPPORT_MSVC_STDLIB_FORCE_INCLUDE_H
diff --git a/libcxx/test/support/test_macros.h b/libcxx/test/support/test_macros.h
index 15fc5b69b52076..07845458871ba2 100644
--- a/libcxx/test/support/test_macros.h
+++ b/libcxx/test/support/test_macros.h
@@ -260,6 +260,10 @@
#define TEST_IGNORE_NODISCARD (void)
+#if defined(_LIBCPP_AVAILABILITY_HAS_FROM_CHARS_FLOATING_POINT) && _LIBCPP_AVAILABILITY_HAS_FROM_CHARS_FLOATING_POINT
+# define TEST_HAS_FROM_CHARS_FLOATING_POINT
+#endif
+
namespace test_macros_detail {
template <class T, class U>
struct is_same { enum { value = 0};} ;
>From 7912d3aa5539926beb16d08e4ad8e74170e1c401 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Fri, 16 Aug 2024 15:55:02 -0700
Subject: [PATCH 08/14] address some comments, clean up libc side a bit
---
libc/shared/str_to_float.h | 4 ++--
libcxx/src/CMakeLists.txt | 12 ++++++----
libcxx/src/charconv.cpp | 4 ++--
.../src/include/from_chars_floating_point.h | 24 ++++++++++---------
.../float_from_chars_test_cases.hpp | 1 +
.../cmake/Modules/FindLibcCommonUtils.cmake | 13 ++++++++++
runtimes/cmake/Modules/FindLibcUtils.cmake | 8 -------
7 files changed, 38 insertions(+), 28 deletions(-)
create mode 100644 runtimes/cmake/Modules/FindLibcCommonUtils.cmake
delete mode 100644 runtimes/cmake/Modules/FindLibcUtils.cmake
diff --git a/libc/shared/str_to_float.h b/libc/shared/str_to_float.h
index da70db11f6b82b..02f5b99ade9f99 100644
--- a/libc/shared/str_to_float.h
+++ b/libc/shared/str_to_float.h
@@ -13,8 +13,8 @@
namespace LIBC_NAMESPACE::shared {
-// WARNING: This is a proof of concept. In future the interface point for libcxx
-// won't be using libc internal classes.
+using internal::ExpandedFloat;
+using internal::RoundDirection;
template <class T>
inline internal::FloatConvertReturn<T> decimal_exp_to_float(
diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt
index 6183f42252d82b..78c43ebfed0288 100644
--- a/libcxx/src/CMakeLists.txt
+++ b/libcxx/src/CMakeLists.txt
@@ -177,14 +177,15 @@ endif()
split_list(LIBCXX_COMPILE_FLAGS)
split_list(LIBCXX_LINK_FLAGS)
-include(FindLibcUtils)
+include(FindLibcCommonUtils)
# Build the shared library.
if (LIBCXX_ENABLE_SHARED)
add_library(cxx_shared SHARED ${exclude_from_all} ${LIBCXX_SOURCES} ${LIBCXX_HEADERS})
target_include_directories(cxx_shared PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
- target_link_libraries(cxx_shared PUBLIC cxx-headers libcxx-libc-shared llvm-libc-shared-utilities
- PRIVATE ${LIBCXX_LIBRARIES})
+ target_link_libraries(cxx_shared PUBLIC cxx-headers libcxx-libc-shared
+ PRIVATE ${LIBCXX_LIBRARIES}
+ PRIVATE llvm-libc-common-utilities)
set_target_properties(cxx_shared
PROPERTIES
COMPILE_FLAGS "${LIBCXX_COMPILE_FLAGS}"
@@ -276,9 +277,10 @@ set(CMAKE_STATIC_LIBRARY_PREFIX "lib")
if (LIBCXX_ENABLE_STATIC)
add_library(cxx_static STATIC ${exclude_from_all} ${LIBCXX_SOURCES} ${LIBCXX_HEADERS})
target_include_directories(cxx_static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
- target_link_libraries(cxx_static PUBLIC cxx-headers libcxx-libc-static llvm-libc-shared-utilities
+ target_link_libraries(cxx_static PUBLIC cxx-headers libcxx-libc-static
PRIVATE ${LIBCXX_LIBRARIES}
- PRIVATE libcxx-abi-static)
+ PRIVATE libcxx-abi-static
+ PRIVATE llvm-libc-common-utilities)
set_target_properties(cxx_static
PROPERTIES
COMPILE_FLAGS "${LIBCXX_COMPILE_FLAGS}"
diff --git a/libcxx/src/charconv.cpp b/libcxx/src/charconv.cpp
index 8299df46a6292e..a9a73065c64ebc 100644
--- a/libcxx/src/charconv.cpp
+++ b/libcxx/src/charconv.cpp
@@ -77,12 +77,12 @@ to_chars_result to_chars(char* __first, char* __last, long double __value, chars
from_chars_result
__from_chars_floating_point(const char* __first, const char* __last, float& __value, chars_format __fmt) {
- return std::__from_chars_floating_point<float>(__first, __last, __value, __fmt);
+ return std::__from_chars_floating_point_impl<float>(__first, __last, __value, __fmt);
}
from_chars_result
__from_chars_floating_point(const char* __first, const char* __last, double& __value, chars_format __fmt) {
- return std::__from_chars_floating_point<double>(__first, __last, __value, __fmt);
+ return std::__from_chars_floating_point_impl<double>(__first, __last, __value, __fmt);
}
_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/src/include/from_chars_floating_point.h b/libcxx/src/include/from_chars_floating_point.h
index b7d142394620ce..fab9783d50c09e 100644
--- a/libcxx/src/include/from_chars_floating_point.h
+++ b/libcxx/src/include/from_chars_floating_point.h
@@ -130,7 +130,7 @@ from_chars_result __from_chars_floating_point_decimal(
const char* src = __ptr; // rename to match the libc code copied for this section.
ptrdiff_t length = __last - src;
- _LIBCPP_ASSERT_INTERNAL(length > 0, "");
+ _LIBCPP_ASSERT_INTERNAL(length > 0, "Last must be after start");
_Uint_type mantissa = 0;
int exponent = 0;
@@ -147,7 +147,7 @@ from_chars_result __from_chars_floating_point_decimal(
const _Uint_type bitstype_max_div_by_base = numeric_limits<_Uint_type>::max() / BASE;
while (index < static_cast<size_t>(length)) {
- if (LIBC_NAMESPACE::internal::isdigit(src[index])) {
+ if (std::isdigit(src[index])) {
uint32_t digit = src[index] - '0';
seen_digit = true;
@@ -181,13 +181,13 @@ from_chars_result __from_chars_floating_point_decimal(
if (!seen_digit)
return {__first, errc::invalid_argument};
- if (index < static_cast<size_t>(length) && LIBC_NAMESPACE::internal::tolower(src[index]) == EXPONENT_MARKER) {
+ if (index < static_cast<size_t>(length) && std::tolower(src[index]) == EXPONENT_MARKER) {
bool has_sign = false;
if (index + 1 < static_cast<size_t>(length) && (src[index + 1] == '+' || src[index + 1] == '-')) {
has_sign = true;
}
if (index + 1 + static_cast<size_t>(has_sign) < static_cast<size_t>(length) &&
- LIBC_NAMESPACE::internal::isdigit(src[index + 1 + static_cast<size_t>(has_sign)])) {
+ std::isdigit(src[index + 1 + static_cast<size_t>(has_sign)])) {
has_valid_exponent = true;
++index;
auto result =
@@ -231,11 +231,11 @@ from_chars_result __from_chars_floating_point_decimal(
break;
}
- LIBC_NAMESPACE::internal::ExpandedFloat<_Fp> expanded_float = {0, 0};
+ LIBC_NAMESPACE::shared::ExpandedFloat<_Fp> expanded_float = {0, 0};
errc status{};
if (mantissa != 0) {
auto temp = LIBC_NAMESPACE::shared::decimal_exp_to_float<_Fp>(
- {mantissa, exponent}, truncated, LIBC_NAMESPACE::internal::RoundDirection::Nearest, src, length);
+ {mantissa, exponent}, truncated, LIBC_NAMESPACE::shared::RoundDirection::Nearest, src, length);
expanded_float = temp.num;
if (temp.error == ERANGE) {
status = errc::result_out_of_range;
@@ -248,7 +248,7 @@ from_chars_result __from_chars_floating_point_decimal(
// C17 7.12.1/6
// The result underflows if the magnitude of the mathematical result is so
- // small that the mathematical re- sult cannot be represented, without
+ // small that the mathematical result cannot be represented, without
// extraordinary roundoff error, in an object of the specified type.237) If
// the result underflows, the function returns an implementation-defined
// value whose magnitude is no greater than the smallest normalized positive
@@ -258,7 +258,7 @@ from_chars_result __from_chars_floating_point_decimal(
// MATH_ERREXCEPT is nonzero, whether the "underflow" floating-point
// exception is raised is implementation-defined.
//
- // LLLVM-LIBC sets ERAGNE for subnormal values
+ // LLVM-LIBC sets ERANGE for subnormal values
//
// [charconv.from.chars]/1
// ... If the parsed value is not in the range representable by the type of
@@ -279,7 +279,7 @@ from_chars_result __from_chars_floating_point_decimal(
template <floating_point _Fp>
from_chars_result
-__from_chars_floating_point(const char* const __first, const char* __last, _Fp& __value, chars_format __fmt) {
+__from_chars_floating_point_impl(const char* const __first, const char* __last, _Fp& __value, chars_format __fmt) {
if (__first == __last) [[unlikely]]
return {__first, errc::invalid_argument};
@@ -307,7 +307,7 @@ __from_chars_floating_point(const char* const __first, const char* __last, _Fp&
if (__fmt == chars_format::scientific)
return {__first, errc::invalid_argument};
- return __from_chars_floating_point_inf(__first, __last, __value, __ptr + 1, __negative);
+ return std::__from_chars_floating_point_inf(__first, __last, __value, __ptr + 1, __negative);
case 'n':
// TODO Evaluate the other implementations
// [charconv.from.chars]/6.2
@@ -318,7 +318,9 @@ __from_chars_floating_point(const char* const __first, const char* __last, _Fp&
if (__fmt == chars_format::scientific)
return {__first, errc::invalid_argument};
if constexpr (numeric_limits<_Fp>::has_quiet_NaN)
- return __from_chars_floating_point_nan(__first, __last, __value, __ptr + 1, __negative);
+ // NOTE: The pointer passed here will be parsed in the default C locale.
+ // This is standard behavior (see https://eel.is/c++draft/charconv.from.chars), but may be unexpected.
+ return std::__from_chars_floating_point_nan(__first, __last, __value, __ptr + 1, __negative);
return {__first, errc::invalid_argument};
}
diff --git a/libcxx/test/std/utilities/charconv/charconv.msvc/float_from_chars_test_cases.hpp b/libcxx/test/std/utilities/charconv/charconv.msvc/float_from_chars_test_cases.hpp
index 9fd5b0768442eb..c0fbfcb132f7ef 100644
--- a/libcxx/test/std/utilities/charconv/charconv.msvc/float_from_chars_test_cases.hpp
+++ b/libcxx/test/std/utilities/charconv/charconv.msvc/float_from_chars_test_cases.hpp
@@ -121,6 +121,7 @@ inline constexpr FloatFromCharsTestCase float_from_chars_test_cases[] = {
"2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222"
"2222222222222222222e-45",
chars_format::scientific, 1006, errc{}, 0x0.000004p-126f},
+
// VSO-733765 "<charconv>: [Feedback] double std::from_chars behavior on exponent out of range"
// LWG-3081 "Floating point from_chars API does not distinguish between overflow and underflow"
// These test cases exercise every overflow/underflow codepath.
diff --git a/runtimes/cmake/Modules/FindLibcCommonUtils.cmake b/runtimes/cmake/Modules/FindLibcCommonUtils.cmake
new file mode 100644
index 00000000000000..d4fc949c3de7ae
--- /dev/null
+++ b/runtimes/cmake/Modules/FindLibcCommonUtils.cmake
@@ -0,0 +1,13 @@
+# //===--------------------------------------------------------------------===//
+# //
+# // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# // See https://llvm.org/LICENSE.txt for details.
+# // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+# //
+# //===--------------------------------------------------------------------===//
+add_library(llvm-libc-common-utilities INTERFACE)
+# TODO: Reorganize the libc shared section so that it can be included without
+# adding the root "libc" directory to the include path.
+target_include_directories(llvm-libc-common-utilities INTERFACE ${CMAKE_CURRENT_LIST_DIR}/../../../libc)
+target_compile_definitions(llvm-libc-common-utilities INTERFACE LIBC_NAMESPACE=__llvm_libc_common_utils)
+target_compile_features(llvm-libc-common-utilities INTERFACE cxx_std_17)
diff --git a/runtimes/cmake/Modules/FindLibcUtils.cmake b/runtimes/cmake/Modules/FindLibcUtils.cmake
deleted file mode 100644
index a1378401629fa6..00000000000000
--- a/runtimes/cmake/Modules/FindLibcUtils.cmake
+++ /dev/null
@@ -1,8 +0,0 @@
-add_library(llvm-libc-shared-utilities INTERFACE)
-# TODO: Reorganize the libc shared section so that it can be included without
-# adding the root "libc" directory to the include path.
-# TODO: Find a better way to solve the problem that ${CMAKE_SOURCE_DIR} is
-# rooted in the runtimes directory, which is why we need the ".."
-target_include_directories(llvm-libc-shared-utilities INTERFACE ${CMAKE_SOURCE_DIR}/../libc)
-target_compile_definitions(llvm-libc-shared-utilities INTERFACE LIBC_NAMESPACE=__llvm_libc_shared_utils)
-target_compile_features(llvm-libc-shared-utilities INTERFACE cxx_std_17)
>From 95ea5fba90704406c468c7102b128c5676712a1b Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Sun, 4 Aug 2024 18:02:20 +0200
Subject: [PATCH 09/14] Several improvements
- CI fixes
- updates documentation
- Implements LWG3456 (Option B). This matches MSVC STL and libstdc++
---
libcxx/docs/Status/Cxx17.rst | 2 +-
libcxx/docs/Status/Cxx2cIssues.csv | 1 +
.../__charconv/from_chars_floating_point.h | 8 +-
libcxx/lib/abi/CHANGELOG.TXT | 10 ++
...bcxxabi.v1.stable.exceptions.nonew.abilist | 2 +
.../src/include/from_chars_floating_point.h | 33 +++--
.../charconv.from.chars/float.pass.cpp | 123 ++++++++----------
.../utilities/charconv/charconv.msvc/test.cpp | 11 --
8 files changed, 84 insertions(+), 106 deletions(-)
diff --git a/libcxx/docs/Status/Cxx17.rst b/libcxx/docs/Status/Cxx17.rst
index 3f1f2071300c80..11eba47645df12 100644
--- a/libcxx/docs/Status/Cxx17.rst
+++ b/libcxx/docs/Status/Cxx17.rst
@@ -40,7 +40,7 @@ Paper Status
.. note::
- .. [#note-P0067] P0067: ``std::(to|from)_chars`` for integrals has been available since version 7.0. ``std::to_chars`` for ``float`` and ``double`` since version 14.0 ``std::to_chars`` for ``long double`` uses the implementation for ``double``.
+ .. [#note-P0067] P0067: ``std::(to|from)_chars`` for integrals has been available since version 7.0. ``std::to_chars`` for ``float`` and ``double`` since version 14.0 ``std::to_chars`` for ``long double`` uses the implementation for ``double``. ``std::from_chars`` for ``float`` and ``double`` since version 20.0.
.. [#note-P0226] P0226: Progress is tracked `here <https://https://libcxx.llvm.org/Status/SpecialMath.html>`_.
.. [#note-P0607] P0607: The parts of P0607 that are not done are the ``<regex>`` bits.
.. [#note-P0154] P0154: The required macros are only implemented as of clang 19.
diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv
index a92a1fef779800..6e5ce18debb2fd 100644
--- a/libcxx/docs/Status/Cxx2cIssues.csv
+++ b/libcxx/docs/Status/Cxx2cIssues.csv
@@ -78,4 +78,5 @@
"","","","","",""
"`LWG3343 <https://wg21.link/LWG3343>`__","Ordering of calls to ``unlock()`` and ``notify_all()`` in Effects element of ``notify_all_at_thread_exit()`` should be reversed","Not Yet Adopted","|Complete|","16.0",""
"XXXX","The sys_info range should be affected by save","Not Yet Adopted","|Complete|","19.0",""
+"`LWG3456 <https://wg21.link/LWG3343>`__","Pattern used by std::from_chars is underspecified (option B)",,"Not Yet Adopted","|Complete|","20.0",""
"","","","","",""
diff --git a/libcxx/include/__charconv/from_chars_floating_point.h b/libcxx/include/__charconv/from_chars_floating_point.h
index 33ecf7012cfb6a..bd92b0a5e981fb 100644
--- a/libcxx/include/__charconv/from_chars_floating_point.h
+++ b/libcxx/include/__charconv/from_chars_floating_point.h
@@ -31,18 +31,18 @@ _LIBCPP_BEGIN_NAMESPACE_STD
#if _LIBCPP_STD_VER >= 17
-_LIBCPP_AVAILABILITY_FROM_CHARS_FLOATING_POINT _LIBCPP_EXPORTED_FROM_ABI from_chars_result
+_LIBCPP_EXPORTED_FROM_ABI from_chars_result
__from_chars_floating_point(const char* __first, const char* __last, float& __value, chars_format __fmt);
-_LIBCPP_AVAILABILITY_FROM_CHARS_FLOATING_POINT _LIBCPP_EXPORTED_FROM_ABI from_chars_result
+_LIBCPP_EXPORTED_FROM_ABI from_chars_result
__from_chars_floating_point(const char* __first, const char* __last, double& __value, chars_format __fmt);
-_LIBCPP_HIDE_FROM_ABI inline from_chars_result
+_LIBCPP_AVAILABILITY_FROM_CHARS_FLOATING_POINT _LIBCPP_HIDE_FROM_ABI inline from_chars_result
from_chars(const char* __first, const char* __last, float& __value, chars_format __fmt = chars_format::general) {
return std::__from_chars_floating_point(__first, __last, __value, __fmt);
}
-_LIBCPP_HIDE_FROM_ABI inline from_chars_result
+_LIBCPP_AVAILABILITY_FROM_CHARS_FLOATING_POINT _LIBCPP_HIDE_FROM_ABI inline from_chars_result
from_chars(const char* __first, const char* __last, double& __value, chars_format __fmt = chars_format::general) {
return std::__from_chars_floating_point(__first, __last, __value, __fmt);
}
diff --git a/libcxx/lib/abi/CHANGELOG.TXT b/libcxx/lib/abi/CHANGELOG.TXT
index 32526f1786c6d9..7bfdb825bdacb2 100644
--- a/libcxx/lib/abi/CHANGELOG.TXT
+++ b/libcxx/lib/abi/CHANGELOG.TXT
@@ -12,6 +12,16 @@ To generate a summary, re-generate the new ABI list using the
New entries should be added directly below the "Version" header.
+------------
+Version 20.0
+------------
+
+* [libcxx][libc] Implements from_chars floating-point
+
+ All platforms
+ Symbol added: _ZNSt6__ndk127__from_chars_floating_pointEPKcS1_RdNS_12chars_formatE
+ Symbol added: _ZNSt6__ndk127__from_chars_floating_pointEPKcS1_RfNS_12chars_formatE
+
------------
Version 19.0
------------
diff --git a/libcxx/lib/abi/i686-linux-android21.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/i686-linux-android21.libcxxabi.v1.stable.exceptions.nonew.abilist
index 8af5db472f7c3a..14b51a5d5615b4 100644
--- a/libcxx/lib/abi/i686-linux-android21.libcxxabi.v1.stable.exceptions.nonew.abilist
+++ b/libcxx/lib/abi/i686-linux-android21.libcxxabi.v1.stable.exceptions.nonew.abilist
@@ -1220,6 +1220,8 @@
{'is_defined': True, 'name': '_ZNSt6__ndk123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIiNS_22__cxx_atomic_base_implIiEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt6__ndk127__from_chars_floating_pointEPKcS1_RdNS_12chars_formatE', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt6__ndk127__from_chars_floating_pointEPKcS1_RfNS_12chars_formatE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk131__arrive_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseEh', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk132__destroy_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk134__construct_barrier_algorithm_baseERi', 'type': 'FUNC'}
diff --git a/libcxx/src/include/from_chars_floating_point.h b/libcxx/src/include/from_chars_floating_point.h
index fab9783d50c09e..983eff4a632407 100644
--- a/libcxx/src/include/from_chars_floating_point.h
+++ b/libcxx/src/include/from_chars_floating_point.h
@@ -181,7 +181,12 @@ from_chars_result __from_chars_floating_point_decimal(
if (!seen_digit)
return {__first, errc::invalid_argument};
- if (index < static_cast<size_t>(length) && std::tolower(src[index]) == EXPONENT_MARKER) {
+ // LWG3456 Pattern used by std::from_chars is underspecified
+ // This changes fixed to ignore a possible exponent instead of making its
+ // existance an error.
+ if (__fmt != chars_format::fixed && index < static_cast<size_t>(length) &&
+ std::tolower(src[index]) == EXPONENT_MARKER) {
+
bool has_sign = false;
if (index + 1 < static_cast<size_t>(length) && (src[index + 1] == '+' || src[index + 1] == '-')) {
has_sign = true;
@@ -296,27 +301,19 @@ __from_chars_floating_point_impl(const char* const __first, const char* __last,
// no digit characters following the sign, no characters match the pattern.
// — end note]
// This is true for integrals, floating point allows -.0
+
+ // [charconv.from.chars]/6.2
+ // if fmt has chars_format::scientific set but not chars_format::fixed, the
+ // otherwise optional exponent part shall appear;
+ // Since INF/NAN do not have an exponent this value is not valid.
+ //
+ // LWG3456 Pattern used by std::from_chars is underspecified
+ // Does not address this point, but proposed option B does solve this issue,
+ // Both MSVC STL and libstdc++ implement this this behaviour.
switch (std::tolower(*__ptr)) {
case 'i':
- // TODO Evaluate the other implementations
- // [charconv.from.chars]/6.2
- // if fmt has chars_format::scientific set but not chars_format::fixed,
- // the otherwise optional exponent part shall appear;
- // Since INF/NAN do not have an exponent this value is not valid.
- // See LWG3456
- if (__fmt == chars_format::scientific)
- return {__first, errc::invalid_argument};
-
return std::__from_chars_floating_point_inf(__first, __last, __value, __ptr + 1, __negative);
case 'n':
- // TODO Evaluate the other implementations
- // [charconv.from.chars]/6.2
- // if fmt has chars_format::scientific set but not chars_format::fixed,
- // the otherwise optional exponent part shall appear;
- // Since INF/NAN do not have an exponent this value is not valid.
- // See LWG3456
- if (__fmt == chars_format::scientific)
- return {__first, errc::invalid_argument};
if constexpr (numeric_limits<_Fp>::has_quiet_NaN)
// NOTE: The pointer passed here will be parsed in the default C locale.
// This is standard behavior (see https://eel.is/c++draft/charconv.from.chars), but may be unexpected.
diff --git a/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp b/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
index 4f55cdec77ba9c..f70e52a95cdf17 100644
--- a/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
@@ -275,6 +275,9 @@ void test_nan(std::chars_format fmt) {
template <class F>
void test_fmt_independent(std::chars_format fmt) {
+ test_infinity<F>(fmt);
+ test_nan<F>(fmt);
+
{ // first == last
F value = 0.25;
std::from_chars_result result = std::from_chars(nullptr, nullptr, value, fmt);
@@ -292,29 +295,6 @@ void test_fmt_independent(std::chars_format fmt) {
assert(result.ptr == s);
assert(value == F(0.25));
}
- if (fmt != std::chars_format::scientific) {
- test_infinity<F>(fmt);
- test_nan<F>(fmt);
- } else {
- { // infinity
- F value = 0.25;
- const char* s = "inf";
- std::from_chars_result result = std::from_chars(s, s + std::strlen(s), value, fmt);
-
- assert(result.ec == std::errc::invalid_argument);
- assert(result.ptr == s);
- assert(value == F(0.25));
- }
- { // nan
- F value = 0.25;
- const char* s = "nan";
- std::from_chars_result result = std::from_chars(s, s + std::strlen(s), value, fmt);
-
- assert(result.ec == std::errc::invalid_argument);
- assert(result.ptr == s);
- assert(value == F(0.25));
- }
- }
{ // only decimal separator
F value = 0.25;
const char* s = ".";
@@ -376,55 +356,6 @@ struct test_fixed {
}
}
- { // exponenent no sign
- const char* s = "1.5e10";
- r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
-
- assert(r.ec == std::errc::invalid_argument);
- assert(r.ptr == s);
- assert(x == F(0.25));
- }
- { // exponenent capitalized no sign
- const char* s = "1.5E10";
- r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
-
- assert(r.ec == std::errc::invalid_argument);
- assert(r.ptr == s);
- assert(x == F(0.25));
- }
- { // exponenent + sign
- const char* s = "1.5e+10";
- r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
-
- assert(r.ec == std::errc::invalid_argument);
- assert(r.ptr == s);
- assert(x == F(0.25));
- }
- { // exponenent - sign
- const char* s = "1.5e-10";
- r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
-
- assert(r.ec == std::errc::invalid_argument);
- assert(r.ptr == s);
- assert(x == F(0.25));
- }
- { // double exponent
- const char* s = "1.25e0e12";
- r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
-
- assert(r.ec == std::errc::invalid_argument);
- assert(r.ptr == s);
- assert(x == F(0.25));
- }
- { // Shifting mantissa exponent and an exponent
- const char* s = "123.456e3";
- r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
-
- assert(r.ec == std::errc::invalid_argument);
- assert(r.ptr == s);
- assert(x == F(0.25));
- }
-
// *** Success
{ // number followed by non-numeric values
@@ -473,6 +404,38 @@ struct test_fixed {
assert(r.ptr == s + 4);
assert(x == F(1.25));
}
+ { // exponenent no sign
+ const char* s = "1.5e10";
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
+ { // exponenent capitalized no sign
+ const char* s = "1.5E10";
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
+ { // exponenent + sign
+ const char* s = "1.5e+10";
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
+ { // exponenent - sign
+ const char* s = "1.5e-10";
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
{ // Exponent no number
const char* s = "1.5e";
@@ -489,6 +452,14 @@ struct test_fixed {
assert(r.ptr == s + 3);
assert(x == F(1.5));
}
+ { // double exponent
+ const char* s = "1.25e0e12";
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(1.25));
+ }
{ // exponent double sign
const char* s = "1.25e++12";
@@ -513,6 +484,14 @@ struct test_fixed {
assert(r.ptr == s + 7);
assert(x == F(1.23456e2));
}
+ { // Shifting mantissa exponent and an exponent
+ const char* s = "123.456e3";
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 7);
+ assert(x == F(123.456));
+ }
{ // Mantissa overflow
{
const char* s = "0.111111111111111111111111111111111111111111";
diff --git a/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp b/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp
index a5218c6b6d7971..19cdbf098156da 100644
--- a/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp
@@ -27,8 +27,6 @@
#include <utility>
#include <vector>
-#include <ranges> // TODO REMOVE
-
#include "double_fixed_precision_to_chars_test_cases_1.hpp"
#include "double_fixed_precision_to_chars_test_cases_2.hpp"
#include "double_fixed_precision_to_chars_test_cases_3.hpp"
@@ -397,15 +395,6 @@ void test_from_chars(const string_view input, const BaseOrFmt base_or_fmt, const
// Not implemented yet.
if (base_or_fmt == std::chars_format::hex)
return;
- // LWG3456. Pattern used by std::from_chars is underspecified
- if (base_or_fmt == std::chars_format::scientific && correct_ec == std::errc{} && opt_correct &&
- !std::isfinite(*opt_correct))
- // Based on the wording in the Standard scientific can't be NaN or Inf.
- return;
- if (base_or_fmt == std::chars_format::fixed && correct_ec == std::errc{} &&
- std::ranges::contains(input, 'e', [](char c) { return std::tolower(c); }))
- // Fixed should not contain an exponent. MSVC terminates at e, libc++ marks as invalid.
- return;
}
#endif
constexpr T unmodified = 111;
>From b5a9ffecd5d630f8f1197b8e804d1a2db2aa547f Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Sat, 17 Aug 2024 10:41:36 +0200
Subject: [PATCH 10/14] Implements the hex format.
---
libc/shared/str_to_float.h | 7 +
.../src/include/from_chars_floating_point.h | 165 ++++-
.../charconv.from.chars/float.pass.cpp | 574 +++++++++++++++++-
.../utilities/charconv/charconv.msvc/test.cpp | 15 +-
4 files changed, 717 insertions(+), 44 deletions(-)
diff --git a/libc/shared/str_to_float.h b/libc/shared/str_to_float.h
index 02f5b99ade9f99..d4e9f54aabb485 100644
--- a/libc/shared/str_to_float.h
+++ b/libc/shared/str_to_float.h
@@ -16,6 +16,13 @@ namespace LIBC_NAMESPACE::shared {
using internal::ExpandedFloat;
using internal::RoundDirection;
+template <class T>
+inline internal::FloatConvertReturn<T>
+binary_exp_to_float(internal::ExpandedFloat<T> init_num, bool truncated,
+ internal::RoundDirection round) {
+ return internal::binary_exp_to_float(init_num, truncated, round);
+}
+
template <class T>
inline internal::FloatConvertReturn<T> decimal_exp_to_float(
internal::ExpandedFloat<T> init_num, bool truncated,
diff --git a/libcxx/src/include/from_chars_floating_point.h b/libcxx/src/include/from_chars_floating_point.h
index 983eff4a632407..371af1cbb0a78b 100644
--- a/libcxx/src/include/from_chars_floating_point.h
+++ b/libcxx/src/include/from_chars_floating_point.h
@@ -18,6 +18,7 @@
#include <charconv>
#include <concepts>
#include <limits>
+#include <optional>
#include <type_traits>
// Included for the _Floating_type_traits class
@@ -117,6 +118,164 @@ from_chars_result __from_chars_floating_point_nan(
return {__ptr, std::errc{}};
}
+template <class _Tp>
+struct __fractional_constant_result {
+ size_t __offset{size_t(-1)};
+ _Tp __mantissa{0};
+ int __exponent{0};
+ bool __truncated{false};
+ bool __valid{false};
+};
+
+template <class _Tp>
+__fractional_constant_result<_Tp> __parse_fractional_hex_constant(const char* __input, size_t __n, size_t __offset) {
+ __fractional_constant_result<_Tp> __result;
+
+ const _Tp __mantissa_truncate_threshold = numeric_limits<_Tp>::max() / 16;
+ bool __fraction = false;
+ for (; __offset < __n; ++__offset) {
+ if (std::isxdigit(__input[__offset])) {
+ __result.__valid = true;
+
+ uint32_t __digit = __input[__offset] - '0';
+ switch (std::tolower(__input[__offset])) {
+ case 'a':
+ __digit = 10;
+ break;
+ case 'b':
+ __digit = 11;
+ break;
+ case 'c':
+ __digit = 12;
+ break;
+ case 'd':
+ __digit = 13;
+ break;
+ case 'e':
+ __digit = 14;
+ break;
+ case 'f':
+ __digit = 15;
+ break;
+ }
+
+ if (__result.__mantissa < __mantissa_truncate_threshold) {
+ __result.__mantissa = (__result.__mantissa * 16) + __digit;
+ if (__fraction)
+ __result.__exponent -= 4;
+ } else {
+ if (__digit > 0)
+ __result.__truncated = true;
+ if (!__fraction)
+ __result.__exponent += 4;
+ }
+ } else if (__input[__offset] == '.') {
+ if (__fraction)
+ break; // this means that __input[__offset] points to a second decimal point, ending the number.
+
+ __fraction = true;
+ } else
+ break;
+ }
+
+ __result.__offset = __offset;
+ return __result;
+}
+
+// Here we do this operation as int64 to avoid overflow.
+int32_t __merge_exponents(int64_t __fractional, int64_t __exponent, int __max_biased_exponent) {
+ int64_t __sum = __fractional + __exponent;
+
+ if (__sum > __max_biased_exponent)
+ return __max_biased_exponent;
+
+ if (__sum < -__max_biased_exponent)
+ return -__max_biased_exponent;
+
+ return __sum;
+}
+
+template <floating_point _Fp>
+from_chars_result __from_chars_floating_point_hex(
+ const char* const __first, const char* __last, _Fp& __value, const char* __ptr, bool __negative) {
+ size_t __n = __last - __first;
+ size_t __offset = __ptr - __first;
+
+ auto __fractional =
+ __parse_fractional_hex_constant<typename _Floating_type_traits<_Fp>::_Uint_type>(__first, __n, __offset);
+ if (!__fractional.__valid)
+ return {__first, errc::invalid_argument};
+
+ __offset = __fractional.__offset;
+
+ optional<int> __exponent;
+ if (__offset + 1 < __n && // an exponent always needs at least one digit.
+ std::tolower(__first[__offset]) == 'p') {
+ ++__offset; // assumes a valid exponent.
+ LIBC_NAMESPACE ::StrToNumResult<int32_t> __e =
+ LIBC_NAMESPACE::internal::strtointeger<int32_t>(__first + __offset, 10, __n - __offset);
+ // __result.error contains the errno value, 0 or ERANGE these are not interesting.
+ // If the number of characters parsed is 0 it means there was no number.
+ if (__e.parsed_len != 0) {
+ __offset += __e.parsed_len;
+ __exponent = __merge_exponents(
+ __fractional.__exponent, __e.value, LIBC_NAMESPACE::fputil::FPBits<_Fp>::MAX_BIASED_EXPONENT);
+ } else
+ --__offset; // the assumption of a valid exponent was not true, undo eating the exponent character.
+ }
+
+ if (!__exponent)
+ __exponent =
+ __merge_exponents(__fractional.__exponent, 0, LIBC_NAMESPACE::fputil::FPBits<_Fp>::MAX_BIASED_EXPONENT);
+
+ LIBC_NAMESPACE::internal::ExpandedFloat<_Fp> expanded_float = {0, 0};
+ errc status{};
+ if (__fractional.__mantissa != 0) {
+ auto temp = LIBC_NAMESPACE::shared::binary_exp_to_float<_Fp>(
+ {__fractional.__mantissa, *__exponent},
+ __fractional.__truncated,
+ LIBC_NAMESPACE::internal::RoundDirection::Nearest);
+ expanded_float = temp.num;
+ if (temp.error == ERANGE) {
+ status = errc::result_out_of_range;
+ }
+ }
+
+ auto result = LIBC_NAMESPACE::fputil::FPBits<_Fp>();
+ result.set_mantissa(expanded_float.mantissa);
+ result.set_biased_exponent(expanded_float.exponent);
+
+ // C17 7.12.1/6
+ // The result underflows if the magnitude of the mathematical result is so
+ // small that the mathematical re- sult cannot be represented, without
+ // extraordinary roundoff error, in an object of the specified type.237) If
+ // the result underflows, the function returns an implementation-defined
+ // value whose magnitude is no greater than the smallest normalized positive
+ // number in the specified type; if the integer expression math_errhandling
+ // & MATH_ERRNO is nonzero, whether errno acquires the value ERANGE is
+ // implementation-defined; if the integer expression math_errhandling &
+ // MATH_ERREXCEPT is nonzero, whether the "underflow" floating-point
+ // exception is raised is implementation-defined.
+ //
+ // LLVM-LIBC sets ERAGNE for subnormal values
+ //
+ // [charconv.from.chars]/1
+ // ... If the parsed value is not in the range representable by the type of
+ // value, value is unmodified and the member ec of the return value is
+ // equal to errc::result_out_of_range. ...
+ //
+ // Undo the ERANGE for subnormal values.
+ if (status == errc::result_out_of_range && result.is_subnormal() && !result.is_zero())
+ status = errc{};
+
+ if (__negative)
+ __value = -result.get_val();
+ else
+ __value = result.get_val();
+
+ return {__first + __offset, status};
+}
+
template <floating_point _Fp>
from_chars_result __from_chars_floating_point_decimal(
const char* const __first,
@@ -321,12 +480,8 @@ __from_chars_floating_point_impl(const char* const __first, const char* __last,
return {__first, errc::invalid_argument};
}
-#if 1
- _LIBCPP_ASSERT_INTERNAL(__fmt != std::chars_format::hex, "");
-#else
if (__fmt == chars_format::hex)
- return std::__from_chars_floating_point_hex(__first, __last, __value);
-#endif
+ return std::__from_chars_floating_point_hex(__first, __last, __value, __ptr, __negative);
return std::__from_chars_floating_point_decimal(__first, __last, __value, __fmt, __ptr, __negative);
}
diff --git a/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp b/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
index f70e52a95cdf17..3b2d47c1aa7834 100644
--- a/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
@@ -445,12 +445,56 @@ struct test_fixed {
assert(x == F(1.5));
}
{ // Exponent sign no number
- const char* s = "1.5e+";
+ {
+ const char* s = "1.5e+";
- r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
- assert(r.ec == std::errc{});
- assert(r.ptr == s + 3);
- assert(x == F(1.5));
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
+ {
+ const char* s = "1.5e-";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
+ }
+ { // Exponent with space
+ {
+ const char* s = "1.5e +1";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
+ {
+ const char* s = "1.5e+ 1";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
+ {
+ const char* s = "1.5e -1";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
+ {
+ const char* s = "1.5e- 1";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
}
{ // double exponent
const char* s = "1.25e0e12";
@@ -460,8 +504,42 @@ struct test_fixed {
assert(r.ptr == s + 4);
assert(x == F(1.25));
}
- { // exponent double sign
- const char* s = "1.25e++12";
+ { // Exponent double sign
+ {
+ const char* s = "1.25e++12";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(1.25));
+ }
+ {
+ const char* s = "1.25e+-12";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(1.25));
+ }
+ {
+ const char* s = "1.25e-+12";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(1.25));
+ }
+ {
+ const char* s = "1.25e--12";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(1.25));
+ }
+ }
+ { // exponent hex prefix
+ const char* s = "1.25e0x12";
r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::fixed);
assert(r.ec == std::errc{});
@@ -559,20 +637,90 @@ struct test_scientific {
assert(x == F(0.25));
}
{ // Exponent sign no number
- const char* s = "1.5e+";
+ {
+ const char* s = "1.5e+";
- r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
- assert(r.ec == std::errc::invalid_argument);
- assert(r.ptr == s);
- assert(x == F(0.25));
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
+ {
+ const char* s = "1.5e-";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
+ }
+ { // Exponent with whitespace
+ {
+ const char* s = "1.5e +1";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
+ {
+ const char* s = "1.5e+ 1";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
+ {
+ const char* s = "1.5e -1";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
+ {
+ const char* s = "1.5e- 1";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
}
{ // exponent double sign
- const char* s = "1.25e++12";
+ {
+ const char* s = "1.25e++12";
- r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
- assert(r.ec == std::errc::invalid_argument);
- assert(r.ptr == s);
- assert(x == F(0.25));
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
+ {
+ const char* s = "1.25e+-12";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
+ {
+ const char* s = "1.25e-+12";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
+ {
+ const char* s = "1.25e--12";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s);
+ assert(x == F(0.25));
+ }
}
// *** Success
@@ -632,6 +780,14 @@ struct test_scientific {
assert(r.ptr == s + 7);
assert(x == F(1.5e-10));
}
+ { // exponent hex prefix -> e0
+ const char* s = "1.25e0x12";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 6);
+ assert(x == F(1.25));
+ }
{ // double exponent
const char* s = "1.25e0e12";
@@ -760,7 +916,7 @@ struct test_general {
// decimal digits optionally containing a decimal-point character, then
// an optional exponent part as defined in 6.4.4.3, excluding any digit
// separators (6.4.4.2); (C23 7.24.1.5)
- r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ r = std::from_chars(s, s + std::strlen(s), x);
assert(r.ec == std::errc{});
assert(r.ptr == s + 4);
assert(x == F(0.5));
@@ -772,7 +928,7 @@ struct test_general {
// decimal digits optionally containing a decimal-point character, then
// an optional exponent part as defined in 6.4.4.3, excluding any digit
// separators (6.4.4.2); (C23 7.24.1.5)
- r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::scientific);
+ r = std::from_chars(s, s + std::strlen(s), x);
assert(r.ec == std::errc{});
assert(r.ptr == s + 5);
assert(x == F(-0.5));
@@ -851,19 +1007,97 @@ struct test_general {
assert(x == F(1.5));
}
{ // Exponent sign no number
- const char* s = "1.5e+";
+ {
+ const char* s = "1.5e+";
- r = std::from_chars(s, s + std::strlen(s), x);
- assert(r.ec == std::errc{});
- assert(r.ptr == s + 3);
- assert(x == F(1.5));
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
+ {
+ const char* s = "1.5e-";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
+ }
+ { // Exponent hwith witespace
+ {
+ const char* s = "1.5e +1";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
+ {
+ const char* s = "1.5e+ 1";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
+ {
+ const char* s = "1.5e -1";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
+ {
+ const char* s = "1.5e- 1";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.5));
+ }
}
{ // exponent double sign
- const char* s = "1.25e++12";
+ {
+ const char* s = "1.25e++12";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(1.25));
+ }
+ {
+ const char* s = "1.25e+-12";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(1.25));
+ }
+ {
+ const char* s = "1.25e-+12";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(1.25));
+ }
+ {
+ const char* s = "1.25e--12";
+
+ r = std::from_chars(s, s + std::strlen(s), x);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(1.25));
+ }
+ }
+ { // exponent hex prefix -> e0
+ const char* s = "1.25e0x12";
r = std::from_chars(s, s + std::strlen(s), x);
assert(r.ec == std::errc{});
- assert(r.ptr == s + 4);
+ assert(r.ptr == s + 6);
assert(x == F(1.25));
}
{ // double exponent
@@ -959,6 +1193,292 @@ struct test_general {
}
};
+template <class F>
+struct test_hex {
+ void operator()() {
+ std::from_chars_result r;
+ F x = 0.25;
+
+ // *** Failures
+
+ { // Starts with invalid character
+ std::array s = {' ', '1', 'e', '0'};
+ for (auto c : "ghijklmnopqrstuvwxyz"
+ "GHIJKLMNOPQRSTUVWXYZ"
+ "`~!@#$%^&*()_=[]{}\\|;:'\",/<>? \t\v\r\n") {
+ s[0] = c;
+ r = std::from_chars(s.data(), s.data() + s.size(), x, std::chars_format::hex);
+
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s.data());
+ assert(x == F(0.25));
+ }
+ }
+
+ // *** Success
+
+ { // number followed by non-numeric values
+ const char* s = "001x";
+
+ // the expected form of the subject sequence is a nonempty sequence of
+ // decimal digits optionally containing a decimal-point character, then
+ // an optional exponent part as defined in 6.4.4.3, excluding any digit
+ // separators (6.4.4.2); (C23 7.24.1.5)
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(1.0));
+ }
+ { // no leading digit
+ const char* s = ".5p0";
+
+ // the expected form of the subject sequence is a nonempty sequence of
+ // decimal digits optionally containing a decimal-point character, then
+ // an optional exponent part as defined in 6.4.4.3, excluding any digit
+ // separators (6.4.4.2); (C23 7.24.1.5)
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(0x0.5p0));
+ }
+ { // negative sign and no leading digit
+ const char* s = "-.5p0";
+
+ // the expected form of the subject sequence is a nonempty sequence of
+ // decimal digits optionally containing a decimal-point character, then
+ // an optional exponent part as defined in 6.4.4.3, excluding any digit
+ // separators (6.4.4.2); (C23 7.24.1.5)
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 5);
+ assert(x == F(-0x0.5p0));
+ }
+ { // no leading digit
+ const char* s = ".5";
+
+ // the expected form of the subject sequence is a nonempty sequence of
+ // decimal digits optionally containing a decimal-point character, then
+ // an optional exponent part as defined in 6.4.4.3, excluding any digit
+ // separators (6.4.4.2); (C23 7.24.1.5)
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 2);
+ assert(x == F(0x0.5p0));
+ }
+ { // negative sign and no leading digit
+ const char* s = "-.5";
+
+ // the expected form of the subject sequence is a nonempty sequence of
+ // decimal digits optionally containing a decimal-point character, then
+ // an optional exponent part as defined in 6.4.4.3, excluding any digit
+ // separators (6.4.4.2); (C23 7.24.1.5)
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(-0x0.5p0));
+ }
+ { // double deciamal point
+ const char* s = "1.25.78";
+
+ // This number is halfway between two float values.
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(0x1.25p0));
+ }
+ { // exponenent no sign
+ const char* s = "1.5p10";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 6);
+ assert(x == F(0x1.5p10));
+ }
+ { // exponenent capitalized no sign
+ const char* s = "1.5P10";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 6);
+ assert(x == F(0x1.5p10));
+ }
+ { // exponenent + sign
+ const char* s = "1.5p+10";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 7);
+ assert(x == F(0x1.5p10));
+ }
+ { // exponenent - sign
+ const char* s = "1.5p-10";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 7);
+ assert(x == F(0x1.5p-10));
+ }
+ { // Exponent no number
+ const char* s = "1.5p";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(0x1.5p0));
+ }
+ { // Exponent sign no number
+ {
+ const char* s = "1.5p+";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(0x1.5p0));
+ }
+ {
+ const char* s = "1.5p-";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(0x1.5p0));
+ }
+ }
+ { // Exponent double sign
+ {
+ const char* s = "1.25p++12";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(0x1.25p0));
+ }
+ {
+ const char* s = "1.25p+-12";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(0x1.25p0));
+ }
+ {
+ const char* s = "1.25p-+12";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(0x1.25p0));
+ }
+ {
+ const char* s = "1.25p--12";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 4);
+ assert(x == F(0x1.25p0));
+ }
+ }
+ { // exponent hex prefix -> p0
+ const char* s = "1.25p0x12";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 6);
+ assert(x == F(0x1.25p0));
+ }
+ { // double exponent
+ const char* s = "1.25p0p12";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 6);
+ assert(x == F(0x1.25p0));
+ }
+ { // This number is halfway between two float values.
+ const char* s = "131CA25";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 7);
+ assert(x == F(0x131CA25p0));
+ }
+ { // Shifting mantissa exponent and no exponent
+ const char* s = "123.456";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 7);
+ assert(x == F(0x123.456p0));
+ }
+ { // Shifting mantissa exponent and an exponent
+ const char* s = "123.456p3";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 9);
+ assert(x == F(0x123.456p3));
+ }
+ { // Mantissa overflow
+ {
+ const char* s = "0.111111111111111111111111111111111111111111";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + std::strlen(s));
+ assert(x == F(0x0.111111111111111111111111111111111111111111p0));
+ }
+ {
+ const char* s = "111111111111.111111111111111111111111111111111111111111";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + std::strlen(s));
+ assert(x == F(0x111111111111.111111111111111111111111111111111111111111p0));
+ }
+ }
+ { // Negative value
+ const char* s = "-0.25";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + std::strlen(s));
+ assert(x == F(-0x0.25p0));
+ }
+ { // value is too big -> +inf
+ const char* s = "1p9999999999999999999999999999999999999999";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc::result_out_of_range);
+ assert(r.ptr == s + strlen(s));
+ assert(x == std::numeric_limits<F>::infinity());
+ }
+ { // negative value is too big -> -inf
+ const char* s = "-1p9999999999999999999999999999999999999999";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc::result_out_of_range);
+ assert(r.ptr == s + strlen(s));
+ assert(x == -std::numeric_limits<F>::infinity());
+ }
+ { // value is too small -> 0
+ const char* s = "1p-9999999999999999999999999999999999999999";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc::result_out_of_range);
+ assert(r.ptr == s + strlen(s));
+ assert(x == F(0.0));
+ }
+ { // negative value is too small -> -0
+ const char* s = "-1p-9999999999999999999999999999999999999999";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc::result_out_of_range);
+ assert(r.ptr == s + strlen(s));
+ assert(x == F(-0.0));
+ }
+ }
+};
+
// The test
// test/std/utilities/charconv/charconv.msvc/test.cpp
// uses random values. This tests contains errors found by this test.
@@ -997,6 +1517,8 @@ int main(int, char**) {
run<test_fixed>(all_floats);
run<test_general>(all_floats);
+ run<test_hex>(all_floats);
+
test_random_errors();
return 0;
diff --git a/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp b/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp
index 19cdbf098156da..45680fd71e69f9 100644
--- a/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp
@@ -390,13 +390,7 @@ void test_from_chars(const string_view input, const BaseOrFmt base_or_fmt, const
if constexpr (is_integral_v<T>) {
assert(mode == TestFromCharsMode::Normal);
}
-#if 1
- else {
- // Not implemented yet.
- if (base_or_fmt == std::chars_format::hex)
- return;
- }
-#endif
+
constexpr T unmodified = 111;
T dest = unmodified;
@@ -662,10 +656,7 @@ void test_floating_hex_prefix(const conditional_t<IsDouble, std::uint64_t, std::
// "-1.fffffffffffffp+1023" or "-1.fffffep+127"
constexpr std::size_t buffer_size = IsDouble ? 22 : 14;
char buffer[buffer_size];
-// TODO Enable once std::from_chars has floating point support.
-#if 0
FloatingType val;
-#endif
for (std::uint32_t frac = 0; frac < Fractions; ++frac) {
const UIntType bits = prefix + frac;
@@ -673,8 +664,7 @@ void test_floating_hex_prefix(const conditional_t<IsDouble, std::uint64_t, std::
const auto to_result = to_chars(buffer, end(buffer), input, chars_format::hex);
assert_message_bits(to_result.ec == errc{}, "(hex) to_result.ec", bits);
-// TODO Enable once std::from_chars has floating point support.
-#if 0
+
const char* const last = to_result.ptr;
const auto from_result = from_chars(buffer, last, val, chars_format::hex);
@@ -682,7 +672,6 @@ void test_floating_hex_prefix(const conditional_t<IsDouble, std::uint64_t, std::
assert_message_bits(from_result.ptr == last, "(hex) from_result.ptr", bits);
assert_message_bits(from_result.ec == errc{}, "(hex) from_result.ec", bits);
assert_message_bits(_Bit_cast<UIntType>(val) == bits, "(hex) round-trip", bits);
-#endif
}
}
>From d51ccbef441c7a2a6dbe179eb11997812f6c5e3a Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Sun, 18 Aug 2024 16:43:07 +0200
Subject: [PATCH 11/14] CI fixes.
---
.../utilities/charconv/charconv.from.chars/float.pass.cpp | 2 ++
libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp | 5 +++++
libcxx/utils/libcxx/test/features.py | 8 ++++++++
3 files changed, 15 insertions(+)
diff --git a/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp b/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
index 3b2d47c1aa7834..abbaf0264fd338 100644
--- a/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
@@ -8,6 +8,8 @@
// UNSUPPORTED: c++03, c++11, c++14
+// XFAIL: availability-fp_from_chars-missing
+
// ADDITIONAL_COMPILE_FLAGS: -O0 -g
// <charconv>
diff --git a/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp b/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp
index 45680fd71e69f9..282f31da689111 100644
--- a/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp
@@ -656,7 +656,10 @@ void test_floating_hex_prefix(const conditional_t<IsDouble, std::uint64_t, std::
// "-1.fffffffffffffp+1023" or "-1.fffffep+127"
constexpr std::size_t buffer_size = IsDouble ? 22 : 14;
char buffer[buffer_size];
+
+#ifdef TEST_HAS_FROM_CHARS_FLOATING_POINT
FloatingType val;
+#endif
for (std::uint32_t frac = 0; frac < Fractions; ++frac) {
const UIntType bits = prefix + frac;
@@ -665,6 +668,7 @@ void test_floating_hex_prefix(const conditional_t<IsDouble, std::uint64_t, std::
const auto to_result = to_chars(buffer, end(buffer), input, chars_format::hex);
assert_message_bits(to_result.ec == errc{}, "(hex) to_result.ec", bits);
+#ifdef TEST_HAS_FROM_CHARS_FLOATING_POINT
const char* const last = to_result.ptr;
const auto from_result = from_chars(buffer, last, val, chars_format::hex);
@@ -672,6 +676,7 @@ void test_floating_hex_prefix(const conditional_t<IsDouble, std::uint64_t, std::
assert_message_bits(from_result.ptr == last, "(hex) from_result.ptr", bits);
assert_message_bits(from_result.ec == errc{}, "(hex) from_result.ec", bits);
assert_message_bits(_Bit_cast<UIntType>(val) == bits, "(hex) round-trip", bits);
+#endif // TEST_HAS_FROM_CHARS_FLOATING_POINT
}
}
diff --git a/libcxx/utils/libcxx/test/features.py b/libcxx/utils/libcxx/test/features.py
index 97cdb0349885d6..56e7cc45d2fada 100644
--- a/libcxx/utils/libcxx/test/features.py
+++ b/libcxx/utils/libcxx/test/features.py
@@ -784,4 +784,12 @@ def check_gdb(cfg):
cfg.available_features,
),
),
+ # Tests that require std::from_chars(floating-point) in the built library
+ Feature(
+ name="availability-fp_from_chars-missing",
+ when=lambda cfg: BooleanExpression.evaluate(
+ "!libcpp-has-no-availability-markup && (stdlib=apple-libc++ && !_target-has-llvm-20)",
+ cfg.available_features,
+ ),
+ ),
]
>From 52697934d392780d1ae2827d9512d60e97290405 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Tue, 3 Sep 2024 13:05:52 -0700
Subject: [PATCH 12/14] Clean up libc shared dependencies.
Moves everything used in libc++ into the `shared` namespace.
---
libc/shared/fp_bits.h | 22 +++++++++++++
libc/shared/str_to_float.h | 23 ++++---------
libc/shared/str_to_integer.h | 24 ++++++++++++++
.../src/include/from_chars_floating_point.h | 32 +++++++++----------
4 files changed, 69 insertions(+), 32 deletions(-)
create mode 100644 libc/shared/fp_bits.h
create mode 100644 libc/shared/str_to_integer.h
diff --git a/libc/shared/fp_bits.h b/libc/shared/fp_bits.h
new file mode 100644
index 00000000000000..2898c508b77727
--- /dev/null
+++ b/libc/shared/fp_bits.h
@@ -0,0 +1,22 @@
+//===-- Floating point number utils -----------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SHARED_FP_BITS_H
+#define LLVM_LIBC_SHARED_FP_BITS_H
+
+#include "src/__support/FPUtil/FPBits.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace shared {
+
+using fputil::FPBits;
+
+} // namespace shared
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SHARED_FP_BITS_H
diff --git a/libc/shared/str_to_float.h b/libc/shared/str_to_float.h
index d4e9f54aabb485..b133a28e26efcd 100644
--- a/libc/shared/str_to_float.h
+++ b/libc/shared/str_to_float.h
@@ -11,26 +11,17 @@
#include "src/__support/str_to_float.h"
-namespace LIBC_NAMESPACE::shared {
+namespace LIBC_NAMESPACE_DECL {
+namespace shared {
using internal::ExpandedFloat;
+using internal::FloatConvertReturn;
using internal::RoundDirection;
-template <class T>
-inline internal::FloatConvertReturn<T>
-binary_exp_to_float(internal::ExpandedFloat<T> init_num, bool truncated,
- internal::RoundDirection round) {
- return internal::binary_exp_to_float(init_num, truncated, round);
-}
+using internal::binary_exp_to_float;
+using internal::decimal_exp_to_float;
-template <class T>
-inline internal::FloatConvertReturn<T> decimal_exp_to_float(
- internal::ExpandedFloat<T> init_num, bool truncated,
- internal::RoundDirection round, const char *__restrict num_start,
- const size_t num_len = cpp::numeric_limits<size_t>::max()) {
- return internal::decimal_exp_to_float(init_num, truncated, round, num_start,
- num_len);
-}
-} // namespace LIBC_NAMESPACE::shared
+} // namespace shared
+} // namespace LIBC_NAMESPACE_DECL
#endif // LLVM_LIBC_SHARED_STR_TO_FLOAT_H
diff --git a/libc/shared/str_to_integer.h b/libc/shared/str_to_integer.h
new file mode 100644
index 00000000000000..15bee698d5a6b2
--- /dev/null
+++ b/libc/shared/str_to_integer.h
@@ -0,0 +1,24 @@
+//===-- String to int conversion utils --------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SHARED_STR_TO_INTEGER_H
+#define LLVM_LIBC_SHARED_STR_TO_INTEGER_H
+
+#include "src/__support/str_to_integer.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace shared {
+
+using LIBC_NAMESPACE::StrToNumResult;
+
+using internal::strtointeger;
+
+} // namespace shared
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SHARED_STR_TO_INTEGER_H
diff --git a/libcxx/src/include/from_chars_floating_point.h b/libcxx/src/include/from_chars_floating_point.h
index 371af1cbb0a78b..fef4101defbace 100644
--- a/libcxx/src/include/from_chars_floating_point.h
+++ b/libcxx/src/include/from_chars_floating_point.h
@@ -9,8 +9,10 @@
#ifndef _LIBCPP_SRC_INCLUDE_FROM_CHARS_FLOATING_POINT_H
#define _LIBCPP_SRC_INCLUDE_FROM_CHARS_FLOATING_POINT_H
-// This header is in the shared LLVM-libc header library.
+// These headers are in the shared LLVM-libc header library.
+#include "shared/fp_bits.h"
#include "shared/str_to_float.h"
+#include "shared/str_to_integer.h"
#include <__assert>
#include <__config>
@@ -212,36 +214,36 @@ from_chars_result __from_chars_floating_point_hex(
if (__offset + 1 < __n && // an exponent always needs at least one digit.
std::tolower(__first[__offset]) == 'p') {
++__offset; // assumes a valid exponent.
- LIBC_NAMESPACE ::StrToNumResult<int32_t> __e =
- LIBC_NAMESPACE::internal::strtointeger<int32_t>(__first + __offset, 10, __n - __offset);
+ LIBC_NAMESPACE::shared::StrToNumResult<int32_t> __e =
+ LIBC_NAMESPACE::shared::strtointeger<int32_t>(__first + __offset, 10, __n - __offset);
// __result.error contains the errno value, 0 or ERANGE these are not interesting.
// If the number of characters parsed is 0 it means there was no number.
if (__e.parsed_len != 0) {
__offset += __e.parsed_len;
__exponent = __merge_exponents(
- __fractional.__exponent, __e.value, LIBC_NAMESPACE::fputil::FPBits<_Fp>::MAX_BIASED_EXPONENT);
+ __fractional.__exponent, __e.value, LIBC_NAMESPACE::shared::FPBits<_Fp>::MAX_BIASED_EXPONENT);
} else
--__offset; // the assumption of a valid exponent was not true, undo eating the exponent character.
}
if (!__exponent)
__exponent =
- __merge_exponents(__fractional.__exponent, 0, LIBC_NAMESPACE::fputil::FPBits<_Fp>::MAX_BIASED_EXPONENT);
+ __merge_exponents(__fractional.__exponent, 0, LIBC_NAMESPACE::shared::FPBits<_Fp>::MAX_BIASED_EXPONENT);
- LIBC_NAMESPACE::internal::ExpandedFloat<_Fp> expanded_float = {0, 0};
+ LIBC_NAMESPACE::shared::ExpandedFloat<_Fp> expanded_float = {0, 0};
errc status{};
if (__fractional.__mantissa != 0) {
auto temp = LIBC_NAMESPACE::shared::binary_exp_to_float<_Fp>(
{__fractional.__mantissa, *__exponent},
__fractional.__truncated,
- LIBC_NAMESPACE::internal::RoundDirection::Nearest);
+ LIBC_NAMESPACE::shared::RoundDirection::Nearest);
expanded_float = temp.num;
if (temp.error == ERANGE) {
status = errc::result_out_of_range;
}
}
- auto result = LIBC_NAMESPACE::fputil::FPBits<_Fp>();
+ auto result = LIBC_NAMESPACE::shared::FPBits<_Fp>();
result.set_mantissa(expanded_float.mantissa);
result.set_biased_exponent(expanded_float.exponent);
@@ -345,7 +347,6 @@ from_chars_result __from_chars_floating_point_decimal(
// existance an error.
if (__fmt != chars_format::fixed && index < static_cast<size_t>(length) &&
std::tolower(src[index]) == EXPONENT_MARKER) {
-
bool has_sign = false;
if (index + 1 < static_cast<size_t>(length) && (src[index + 1] == '+' || src[index + 1] == '-')) {
has_sign = true;
@@ -354,8 +355,7 @@ from_chars_result __from_chars_floating_point_decimal(
std::isdigit(src[index + 1 + static_cast<size_t>(has_sign)])) {
has_valid_exponent = true;
++index;
- auto result =
- LIBC_NAMESPACE::internal::strtointeger<int32_t>(src + index, 10, static_cast<size_t>(length) - index);
+ auto result = LIBC_NAMESPACE::shared::strtointeger<int32_t>(src + index, 10, static_cast<size_t>(length) - index);
// if (result.has_error())
// output.error = result.error;
int32_t add_to_exponent = result.value;
@@ -366,10 +366,10 @@ from_chars_result __from_chars_floating_point_decimal(
// If the result is in the valid range, then we use it. The valid range is
// also within the int32 range, so this prevents overflow issues.
- if (temp_exponent > LIBC_NAMESPACE::fputil::FPBits<_Fp>::MAX_BIASED_EXPONENT) {
- exponent = LIBC_NAMESPACE::fputil::FPBits<_Fp>::MAX_BIASED_EXPONENT;
- } else if (temp_exponent < -LIBC_NAMESPACE::fputil::FPBits<_Fp>::MAX_BIASED_EXPONENT) {
- exponent = -LIBC_NAMESPACE::fputil::FPBits<_Fp>::MAX_BIASED_EXPONENT;
+ if (temp_exponent > LIBC_NAMESPACE::shared::FPBits<_Fp>::MAX_BIASED_EXPONENT) {
+ exponent = LIBC_NAMESPACE::shared::FPBits<_Fp>::MAX_BIASED_EXPONENT;
+ } else if (temp_exponent < -LIBC_NAMESPACE::shared::FPBits<_Fp>::MAX_BIASED_EXPONENT) {
+ exponent = -LIBC_NAMESPACE::shared::FPBits<_Fp>::MAX_BIASED_EXPONENT;
} else {
exponent = static_cast<int32_t>(temp_exponent);
}
@@ -406,7 +406,7 @@ from_chars_result __from_chars_floating_point_decimal(
}
}
- auto result = LIBC_NAMESPACE::fputil::FPBits<_Fp>();
+ auto result = LIBC_NAMESPACE::shared::FPBits<_Fp>();
result.set_mantissa(expanded_float.mantissa);
result.set_biased_exponent(expanded_float.exponent);
>From b2d4f105464136c767053b664aaa3a0ecd4f6c7d Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Tue, 3 Sep 2024 13:42:04 -0700
Subject: [PATCH 13/14] add libc bazel target for /shared
Will be necessary for downstream libcxx build
---
utils/bazel/llvm-project-overlay/libc/BUILD.bazel | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
index bedd363edd1bde..db91e59b19efdd 100644
--- a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
@@ -150,7 +150,6 @@ libc_support_library(
hdrs = ["hdr/stdio_macros.h"],
)
-
libc_support_library(
name = "hdr_limits_macros",
hdrs = ["hdr/limits_macros.h"],
@@ -1224,6 +1223,19 @@ libc_support_library(
],
)
+########################## externally shared targets ###########################
+
+libc_support_library(
+ name = "libc_external_common",
+ hdrs = glob(["shared/*.h"]),
+ deps = [
+ ":__support_common",
+ ":__support_fputil_fp_bits",
+ ":__support_str_to_float",
+ ":__support_str_to_integer",
+ ],
+)
+
############################### errno targets ################################
libc_function(
>From fe23413537f1d69edcd40db3cfac82765fd42fc0 Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Wed, 25 Sep 2024 17:08:49 +0200
Subject: [PATCH 14/14] Polishing the patch.
- Refactors duplicated code to functions.
- Fixes a bug where 12e 12 was parsed as 12e12 instead of on 12
(the libc function used eats leading whitespace.)
- Several minor cleanups.
---
.../__charconv/from_chars_floating_point.h | 5 -
libcxx/include/charconv | 4 +-
libcxx/src/charconv.cpp | 4 +-
.../src/include/from_chars_floating_point.h | 373 ++++++++----------
.../charconv.from.chars/float.pass.cpp | 42 +-
.../utilities/charconv/charconv.msvc/test.cpp | 11 +-
.../charconv/charconv.msvc/test.pass.cpp | 2 -
.../cmake/Modules/FindLibcCommonUtils.cmake | 2 +-
8 files changed, 217 insertions(+), 226 deletions(-)
diff --git a/libcxx/include/__charconv/from_chars_floating_point.h b/libcxx/include/__charconv/from_chars_floating_point.h
index bd92b0a5e981fb..336c5afae98ea8 100644
--- a/libcxx/include/__charconv/from_chars_floating_point.h
+++ b/libcxx/include/__charconv/from_chars_floating_point.h
@@ -13,12 +13,7 @@
#include <__assert>
#include <__charconv/chars_format.h>
#include <__charconv/from_chars_result.h>
-#include <__charconv/traits.h>
#include <__config>
-#include <__system_error/errc.h>
-#include <__type_traits/enable_if.h>
-#include <__type_traits/integral_constant.h>
-#include <__type_traits/is_floating_point.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
diff --git a/libcxx/include/charconv b/libcxx/include/charconv
index dfef8194a2a67b..29c6875008abb4 100644
--- a/libcxx/include/charconv
+++ b/libcxx/include/charconv
@@ -65,10 +65,10 @@ namespace std {
constexpr from_chars_result from_chars(const char* first, const char* last,
see below& value, int base = 10); // constexpr since C++23
- constexpr from_chars_result from_chars(const char* first, const char* last,
+ from_chars_result from_chars(const char* first, const char* last,
float& value, chars_format fmt);
- constexpr from_chars_result from_chars(const char* first, const char* last,
+ from_chars_result from_chars(const char* first, const char* last,
double& value, chars_format fmt);
} // namespace std
diff --git a/libcxx/src/charconv.cpp b/libcxx/src/charconv.cpp
index a9a73065c64ebc..b22ed410e2d311 100644
--- a/libcxx/src/charconv.cpp
+++ b/libcxx/src/charconv.cpp
@@ -75,12 +75,12 @@ to_chars_result to_chars(char* __first, char* __last, long double __value, chars
__first, __last, static_cast<double>(__value), __fmt, __precision);
}
-from_chars_result
+_LIBCPP_EXPORTED_FROM_ABI from_chars_result
__from_chars_floating_point(const char* __first, const char* __last, float& __value, chars_format __fmt) {
return std::__from_chars_floating_point_impl<float>(__first, __last, __value, __fmt);
}
-from_chars_result
+_LIBCPP_EXPORTED_FROM_ABI from_chars_result
__from_chars_floating_point(const char* __first, const char* __last, double& __value, chars_format __fmt) {
return std::__from_chars_floating_point_impl<double>(__first, __last, __value, __fmt);
}
diff --git a/libcxx/src/include/from_chars_floating_point.h b/libcxx/src/include/from_chars_floating_point.h
index fef4101defbace..95f17607e3adb9 100644
--- a/libcxx/src/include/from_chars_floating_point.h
+++ b/libcxx/src/include/from_chars_floating_point.h
@@ -20,8 +20,6 @@
#include <charconv>
#include <concepts>
#include <limits>
-#include <optional>
-#include <type_traits>
// Included for the _Floating_type_traits class
#include "to_chars_floating_point.h"
@@ -29,7 +27,7 @@
_LIBCPP_BEGIN_NAMESPACE_STD
// Parses an infinity string.
-// Valid strings are case insentitive and contain INF or INFINITY.
+// Valid strings are case insensitive and contain INF or INFINITY.
//
// - __first is the first argument to std::from_chars. When the string is invalid
// this value is returned as ptr in the result.
@@ -75,7 +73,7 @@ from_chars_result __from_chars_floating_point_inf(
}
// Parses a nan string.
-// Valid strings are case insentitive and contain INF or INFINITY.
+// Valid strings are case insensitive and contain INF or INFINITY.
//
// - __first is the first argument to std::from_chars. When the string is invalid
// this value is returned as ptr in the result.
@@ -129,6 +127,11 @@ struct __fractional_constant_result {
bool __valid{false};
};
+// Parses the hex constant part of the hexadecimal floating-point value.
+// - input start of buffer given to from_chars
+// - __n the number of elements in the buffer
+// - __offset where to start parsing. The input can have an optional sign, the
+// offset starts after this sign.
template <class _Tp>
__fractional_constant_result<_Tp> __parse_fractional_hex_constant(const char* __input, size_t __n, size_t __offset) {
__fractional_constant_result<_Tp> __result;
@@ -184,6 +187,35 @@ __fractional_constant_result<_Tp> __parse_fractional_hex_constant(const char* __
return __result;
}
+struct __exponent_result {
+ size_t __offset{size_t(-1)};
+ int __value{0};
+ bool __present{false};
+};
+
+// When the exponent is not present the result of the struct contains
+// __offset, 0, false. This allows using the results unconditionally, the
+// __present is important for the scientific notation, where the value is
+// mandatory.
+__exponent_result __parse_exponent(const char* __input, size_t __n, size_t __offset, char __marker) {
+ if (__offset + 1 < __n && // an exponent always needs at least one digit.
+ std::tolower(__input[__offset]) == __marker && //
+ !std::isspace(__input[__offset + 1]) // leading whitespace is not allowed.
+ ) {
+ ++__offset;
+ LIBC_NAMESPACE::shared::StrToNumResult<int32_t> __e =
+ LIBC_NAMESPACE::shared::strtointeger<int32_t>(__input + __offset, 10, __n - __offset);
+ // __result.error contains the errno value, 0 or ERANGE these are not interesting.
+ // If the number of characters parsed is 0 it means there was no number.
+ if (__e.parsed_len != 0)
+ return {__offset + __e.parsed_len, __e.value, true};
+ else
+ --__offset; // the assumption of a valid exponent was not true, undo eating the exponent character.
+ }
+
+ return {__offset, 0, false};
+}
+
// Here we do this operation as int64 to avoid overflow.
int32_t __merge_exponents(int64_t __fractional, int64_t __exponent, int __max_biased_exponent) {
int64_t __sum = __fractional + __exponent;
@@ -197,59 +229,16 @@ int32_t __merge_exponents(int64_t __fractional, int64_t __exponent, int __max_bi
return __sum;
}
-template <floating_point _Fp>
-from_chars_result __from_chars_floating_point_hex(
- const char* const __first, const char* __last, _Fp& __value, const char* __ptr, bool __negative) {
- size_t __n = __last - __first;
- size_t __offset = __ptr - __first;
-
- auto __fractional =
- __parse_fractional_hex_constant<typename _Floating_type_traits<_Fp>::_Uint_type>(__first, __n, __offset);
- if (!__fractional.__valid)
- return {__first, errc::invalid_argument};
-
- __offset = __fractional.__offset;
-
- optional<int> __exponent;
- if (__offset + 1 < __n && // an exponent always needs at least one digit.
- std::tolower(__first[__offset]) == 'p') {
- ++__offset; // assumes a valid exponent.
- LIBC_NAMESPACE::shared::StrToNumResult<int32_t> __e =
- LIBC_NAMESPACE::shared::strtointeger<int32_t>(__first + __offset, 10, __n - __offset);
- // __result.error contains the errno value, 0 or ERANGE these are not interesting.
- // If the number of characters parsed is 0 it means there was no number.
- if (__e.parsed_len != 0) {
- __offset += __e.parsed_len;
- __exponent = __merge_exponents(
- __fractional.__exponent, __e.value, LIBC_NAMESPACE::shared::FPBits<_Fp>::MAX_BIASED_EXPONENT);
- } else
- --__offset; // the assumption of a valid exponent was not true, undo eating the exponent character.
- }
-
- if (!__exponent)
- __exponent =
- __merge_exponents(__fractional.__exponent, 0, LIBC_NAMESPACE::shared::FPBits<_Fp>::MAX_BIASED_EXPONENT);
-
- LIBC_NAMESPACE::shared::ExpandedFloat<_Fp> expanded_float = {0, 0};
- errc status{};
- if (__fractional.__mantissa != 0) {
- auto temp = LIBC_NAMESPACE::shared::binary_exp_to_float<_Fp>(
- {__fractional.__mantissa, *__exponent},
- __fractional.__truncated,
- LIBC_NAMESPACE::shared::RoundDirection::Nearest);
- expanded_float = temp.num;
- if (temp.error == ERANGE) {
- status = errc::result_out_of_range;
- }
- }
-
- auto result = LIBC_NAMESPACE::shared::FPBits<_Fp>();
- result.set_mantissa(expanded_float.mantissa);
- result.set_biased_exponent(expanded_float.exponent);
+template <class _Fp, class _Tp>
+from_chars_result
+__calculate_result(_Tp __mantissa, int __exponent, _Fp& __value, bool __negative, from_chars_result __result) {
+ auto __r = LIBC_NAMESPACE::shared::FPBits<_Fp>();
+ __r.set_mantissa(__mantissa);
+ __r.set_biased_exponent(__exponent);
// C17 7.12.1/6
// The result underflows if the magnitude of the mathematical result is so
- // small that the mathematical re- sult cannot be represented, without
+ // small that the mathematical result cannot be represented, without
// extraordinary roundoff error, in an object of the specified type.237) If
// the result underflows, the function returns an implementation-defined
// value whose magnitude is no greater than the smallest normalized positive
@@ -267,17 +256,107 @@ from_chars_result __from_chars_floating_point_hex(
// equal to errc::result_out_of_range. ...
//
// Undo the ERANGE for subnormal values.
- if (status == errc::result_out_of_range && result.is_subnormal() && !result.is_zero())
- status = errc{};
+ if (__result.ec == errc::result_out_of_range && __r.is_subnormal() && !__r.is_zero())
+ __result.ec = errc{};
if (__negative)
- __value = -result.get_val();
+ __value = -__r.get_val();
else
- __value = result.get_val();
+ __value = __r.get_val();
- return {__first + __offset, status};
+ return __result;
}
+// Implements from_chars for decimal floating-point values.
+// __first forwarded from from_chars
+// __last forwarded from from_chars
+// __value forwarded from from_chars
+// __fmt forwarded from from_chars
+// __ptr the start of the buffer to parse. This is after the optional sign character.
+// __negative should __value be set to a negative value?
+//
+// This function and __from_chars_floating_point_decimal are similar. However
+// the similar parts are all in helper functions. So the amount of code
+// duplication is minimal.
+template <floating_point _Fp>
+from_chars_result __from_chars_floating_point_hex(
+ const char* const __first, const char* __last, _Fp& __value, const char* __ptr, bool __negative) {
+ size_t __n = __last - __first;
+ size_t __offset = __ptr - __first;
+
+ auto __fractional =
+ std::__parse_fractional_hex_constant<typename _Floating_type_traits<_Fp>::_Uint_type>(__first, __n, __offset);
+ if (!__fractional.__valid)
+ return {__first, errc::invalid_argument};
+
+ auto __parsed_exponent = std::__parse_exponent(__first, __n, __fractional.__offset, 'p');
+ __offset = __parsed_exponent.__offset;
+ int __exponent = std::__merge_exponents(
+ __fractional.__exponent, __parsed_exponent.__value, LIBC_NAMESPACE::shared::FPBits<_Fp>::MAX_BIASED_EXPONENT);
+
+ from_chars_result __result{__first + __offset, {}};
+ LIBC_NAMESPACE::shared::ExpandedFloat<_Fp> __expanded_float = {0, 0};
+ if (__fractional.__mantissa != 0) {
+ auto __temp = LIBC_NAMESPACE::shared::binary_exp_to_float<_Fp>(
+ {__fractional.__mantissa, __exponent},
+ __fractional.__truncated,
+ LIBC_NAMESPACE::shared::RoundDirection::Nearest);
+ __expanded_float = __temp.num;
+ if (__temp.error == ERANGE) {
+ __result.ec = errc::result_out_of_range;
+ }
+ }
+
+ return std::__calculate_result(__expanded_float.mantissa, __expanded_float.exponent, __value, __negative, __result);
+}
+
+// Parses the hex constant part of the decimal float value.
+// - input start of buffer given to from_chars
+// - __n the number of elements in the buffer
+// - __offset where to start parsing. The input can have an optional sign, the
+// offset starts after this sign.
+template <class _Tp>
+__fractional_constant_result<_Tp>
+__parse_fractional_decimal_constant(const char* __input, size_t __n, size_t __offset) {
+ __fractional_constant_result<_Tp> __result;
+
+ const _Tp __mantissa_truncate_threshold = numeric_limits<_Tp>::max() / 10;
+ bool __fraction = false;
+ for (; __offset < __n; ++__offset) {
+ if (std::isdigit(__input[__offset])) {
+ __result.__valid = true;
+
+ uint32_t __digit = __input[__offset] - '0';
+ if (__result.__mantissa < __mantissa_truncate_threshold) {
+ __result.__mantissa = (__result.__mantissa * 10) + __digit;
+ if (__fraction)
+ --__result.__exponent;
+ } else {
+ if (__digit > 0)
+ __result.__truncated = true;
+ if (!__fraction)
+ ++__result.__exponent;
+ }
+ } else if (__input[__offset] == '.') {
+ if (__fraction)
+ break; // this means that __input[__offset] points to a second decimal point, ending the number.
+
+ __fraction = true;
+ } else
+ break;
+ }
+
+ __result.__offset = __offset;
+ return __result;
+}
+
+// Implements from_chars for decimal floating-point values.
+// __first forwarded from from_chars
+// __last forwarded from from_chars
+// __value forwarded from from_chars
+// __fmt forwarded from from_chars
+// __ptr the start of the buffer to parse. This is after the optional sign character.
+// __negative should __value be set to a negative value?
template <floating_point _Fp>
from_chars_result __from_chars_floating_point_decimal(
const char* const __first,
@@ -286,159 +365,55 @@ from_chars_result __from_chars_floating_point_decimal(
chars_format __fmt,
const char* __ptr,
bool __negative) {
- using _Traits = _Floating_type_traits<_Fp>;
- using _Uint_type = typename _Traits::_Uint_type;
-
- const char* src = __ptr; // rename to match the libc code copied for this section.
- ptrdiff_t length = __last - src;
- _LIBCPP_ASSERT_INTERNAL(length > 0, "Last must be after start");
-
- _Uint_type mantissa = 0;
- int exponent = 0;
- bool truncated = false;
- bool seen_digit = false;
- bool has_valid_exponent = false;
- bool after_decimal = false;
- size_t index = 0;
- const size_t BASE = 10;
- constexpr char EXPONENT_MARKER = 'e';
- constexpr char DECIMAL_POINT = '.';
-
- // The loop fills the mantissa with as many digits as it can hold
- const _Uint_type bitstype_max_div_by_base = numeric_limits<_Uint_type>::max() / BASE;
-
- while (index < static_cast<size_t>(length)) {
- if (std::isdigit(src[index])) {
- uint32_t digit = src[index] - '0';
- seen_digit = true;
-
- if (mantissa < bitstype_max_div_by_base) {
- mantissa = (mantissa * BASE) + digit;
- if (after_decimal) {
- --exponent;
- }
- } else {
- if (digit > 0)
- truncated = true;
- if (!after_decimal)
- ++exponent;
- }
-
- ++index;
- continue;
- }
- if (src[index] == DECIMAL_POINT) {
- if (after_decimal) {
- break; // this means that src[index] points to a second decimal point, ending the number.
- }
- after_decimal = true;
- ++index;
- continue;
- }
- // The character is neither a digit nor a decimal point.
- break;
- }
+ size_t __n = __last - __first;
+ size_t __offset = __ptr - __first;
- if (!seen_digit)
+ auto __fractional =
+ std::__parse_fractional_decimal_constant<typename _Floating_type_traits<_Fp>::_Uint_type>(__first, __n, __offset);
+ if (!__fractional.__valid)
return {__first, errc::invalid_argument};
+ __offset = __fractional.__offset;
+
// LWG3456 Pattern used by std::from_chars is underspecified
// This changes fixed to ignore a possible exponent instead of making its
// existance an error.
- if (__fmt != chars_format::fixed && index < static_cast<size_t>(length) &&
- std::tolower(src[index]) == EXPONENT_MARKER) {
- bool has_sign = false;
- if (index + 1 < static_cast<size_t>(length) && (src[index + 1] == '+' || src[index + 1] == '-')) {
- has_sign = true;
- }
- if (index + 1 + static_cast<size_t>(has_sign) < static_cast<size_t>(length) &&
- std::isdigit(src[index + 1 + static_cast<size_t>(has_sign)])) {
- has_valid_exponent = true;
- ++index;
- auto result = LIBC_NAMESPACE::shared::strtointeger<int32_t>(src + index, 10, static_cast<size_t>(length) - index);
- // if (result.has_error())
- // output.error = result.error;
- int32_t add_to_exponent = result.value;
- index += result.parsed_len;
-
- // Here we do this operation as int64 to avoid overflow.
- int64_t temp_exponent = static_cast<int64_t>(exponent) + static_cast<int64_t>(add_to_exponent);
-
- // If the result is in the valid range, then we use it. The valid range is
- // also within the int32 range, so this prevents overflow issues.
- if (temp_exponent > LIBC_NAMESPACE::shared::FPBits<_Fp>::MAX_BIASED_EXPONENT) {
- exponent = LIBC_NAMESPACE::shared::FPBits<_Fp>::MAX_BIASED_EXPONENT;
- } else if (temp_exponent < -LIBC_NAMESPACE::shared::FPBits<_Fp>::MAX_BIASED_EXPONENT) {
- exponent = -LIBC_NAMESPACE::shared::FPBits<_Fp>::MAX_BIASED_EXPONENT;
- } else {
- exponent = static_cast<int32_t>(temp_exponent);
- }
+ int __exponent;
+ if (__fmt == chars_format::fixed) {
+ __exponent =
+ std::__merge_exponents(__fractional.__exponent, 0, LIBC_NAMESPACE::shared::FPBits<_Fp>::MAX_BIASED_EXPONENT);
+ } else {
+ auto __parsed_exponent = std::__parse_exponent(__first, __n, __offset, 'e');
+ if (__fmt == chars_format::scientific && !__parsed_exponent.__present) {
+ // [charconv.from.chars]/6.2 if fmt has chars_format::scientific set but not chars_format::fixed,
+ // the otherwise optional exponent part shall appear;
+ return {__first, errc::invalid_argument};
}
- }
- // [charconv.from.chars]
- switch (__fmt) {
- case chars_format::scientific:
- // 6.2 if fmt has chars_format::scientific set but not chars_format::fixed,
- // the otherwise optional exponent part shall appear;
- if (!has_valid_exponent)
- return {__first, errc::invalid_argument};
- break;
- case chars_format::fixed:
- // 6.3 if fmt has chars_format::fixed set but not chars_format::scientific,
- // the optional exponent part shall not appear;
- if (has_valid_exponent)
- return {__first, errc::invalid_argument};
- break;
- case chars_format::general:
- case chars_format::hex: // impossible but it silences the compiler
- break;
+ __offset = __parsed_exponent.__offset;
+ __exponent = std::__merge_exponents(
+ __fractional.__exponent, __parsed_exponent.__value, LIBC_NAMESPACE::shared::FPBits<_Fp>::MAX_BIASED_EXPONENT);
}
- LIBC_NAMESPACE::shared::ExpandedFloat<_Fp> expanded_float = {0, 0};
- errc status{};
- if (mantissa != 0) {
- auto temp = LIBC_NAMESPACE::shared::decimal_exp_to_float<_Fp>(
- {mantissa, exponent}, truncated, LIBC_NAMESPACE::shared::RoundDirection::Nearest, src, length);
- expanded_float = temp.num;
- if (temp.error == ERANGE) {
- status = errc::result_out_of_range;
+ from_chars_result __result{__first + __offset, {}};
+ LIBC_NAMESPACE::shared::ExpandedFloat<_Fp> __expanded_float = {0, 0};
+ if (__fractional.__mantissa != 0) {
+ // This function expects to parse a positive value. This means it does not
+ // take a __first, __n as arguments, since __first points to '-' for
+ // negative values.
+ auto __temp = LIBC_NAMESPACE::shared::decimal_exp_to_float<_Fp>(
+ {__fractional.__mantissa, __exponent},
+ __fractional.__truncated,
+ LIBC_NAMESPACE::shared::RoundDirection::Nearest,
+ __ptr,
+ __last - __ptr);
+ __expanded_float = __temp.num;
+ if (__temp.error == ERANGE) {
+ __result.ec = errc::result_out_of_range;
}
}
- auto result = LIBC_NAMESPACE::shared::FPBits<_Fp>();
- result.set_mantissa(expanded_float.mantissa);
- result.set_biased_exponent(expanded_float.exponent);
-
- // C17 7.12.1/6
- // The result underflows if the magnitude of the mathematical result is so
- // small that the mathematical result cannot be represented, without
- // extraordinary roundoff error, in an object of the specified type.237) If
- // the result underflows, the function returns an implementation-defined
- // value whose magnitude is no greater than the smallest normalized positive
- // number in the specified type; if the integer expression math_errhandling
- // & MATH_ERRNO is nonzero, whether errno acquires the value ERANGE is
- // implementation-defined; if the integer expression math_errhandling &
- // MATH_ERREXCEPT is nonzero, whether the "underflow" floating-point
- // exception is raised is implementation-defined.
- //
- // LLVM-LIBC sets ERANGE for subnormal values
- //
- // [charconv.from.chars]/1
- // ... If the parsed value is not in the range representable by the type of
- // value, value is unmodified and the member ec of the return value is
- // equal to errc::result_out_of_range. ...
- //
- // Undo the ERANGE for subnormal values.
- if (status == errc::result_out_of_range && result.is_subnormal() && !result.is_zero())
- status = errc{};
-
- if (__negative)
- __value = -result.get_val();
- else
- __value = result.get_val();
-
- return {src + index, status};
+ return std::__calculate_result(__expanded_float.mantissa, __expanded_float.exponent, __value, __negative, __result);
}
template <floating_point _Fp>
diff --git a/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp b/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
index abbaf0264fd338..ad3669635524d7 100644
--- a/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.from.chars/float.pass.cpp
@@ -10,10 +10,6 @@
// XFAIL: availability-fp_from_chars-missing
-// ADDITIONAL_COMPILE_FLAGS: -O0 -g
-
-// <charconv>
-
// from_chars_result from_chars(const char* first, const char* last,
// Float& value, chars_format fmt = chars_format::general)
@@ -464,7 +460,7 @@ struct test_fixed {
assert(x == F(1.5));
}
}
- { // Exponent with space
+ { // Exponent with whitespace
{
const char* s = "1.5e +1";
@@ -1026,7 +1022,7 @@ struct test_general {
assert(x == F(1.5));
}
}
- { // Exponent hwith witespace
+ { // Exponent with whitespace
{
const char* s = "1.5e +1";
@@ -1346,6 +1342,40 @@ struct test_hex {
assert(x == F(0x1.5p0));
}
}
+ { // Exponent with whitespace
+ {
+ const char* s = "1.5p +1";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(0x1.5p0));
+ }
+ {
+ const char* s = "1.5p+ 1";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(0x1.5p0));
+ }
+ {
+ const char* s = "1.5p -1";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(0x1.5p0));
+ }
+ {
+ const char* s = "1.5p- 1";
+
+ r = std::from_chars(s, s + std::strlen(s), x, std::chars_format::hex);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + 3);
+ assert(x == F(0x1.5p0));
+ }
+ }
{ // Exponent double sign
{
const char* s = "1.25p++12";
diff --git a/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp b/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp
index 282f31da689111..ace6d46b879b01 100644
--- a/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp
@@ -854,20 +854,13 @@ void test_floating_from_chars(const chars_format fmt) {
// The UCRT considers indeterminate NaN to be negative quiet NaN with no payload bits set.
// It parses "nan(ind)" and "-nan(ind)" identically.
- //
- //
- // TODO Evaluate this code, probably needs to be commented out.
- //
- //
-#if 0
+# ifdef _MSC_VER
test_from_chars<T>("nan(InD)", fmt, 8, errc{}, -qnan);
-#endif
test_from_chars<T>("-nan(InD)", fmt, 9, errc{}, -qnan);
-#if 0
test_from_chars<T>("nan(SnAn)", fmt, 9, errc{}, nullopt, TestFromCharsMode::SignalingNaN);
test_from_chars<T>("-nan(SnAn)", fmt, 10, errc{}, nullopt, TestFromCharsMode::SignalingNaN);
-#endif
+# endif
switch (fmt) {
case chars_format::general:
diff --git a/libcxx/test/std/utilities/charconv/charconv.msvc/test.pass.cpp b/libcxx/test/std/utilities/charconv/charconv.msvc/test.pass.cpp
index 190805687beb8c..0031a12f7d25ba 100644
--- a/libcxx/test/std/utilities/charconv/charconv.msvc/test.pass.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.msvc/test.pass.cpp
@@ -8,8 +8,6 @@
// UNSUPPORTED: c++03, c++11, c++14
-// ADDITIONAL_COMPILE_FLAGS: -O0 -g
-
// to_chars requires functions in the dylib that have not been introduced in older
// versions of the dylib on macOS.
// XFAIL: availability-fp_to_chars-missing
diff --git a/runtimes/cmake/Modules/FindLibcCommonUtils.cmake b/runtimes/cmake/Modules/FindLibcCommonUtils.cmake
index d4fc949c3de7ae..4d62619ae0af6b 100644
--- a/runtimes/cmake/Modules/FindLibcCommonUtils.cmake
+++ b/runtimes/cmake/Modules/FindLibcCommonUtils.cmake
@@ -9,5 +9,5 @@ add_library(llvm-libc-common-utilities INTERFACE)
# TODO: Reorganize the libc shared section so that it can be included without
# adding the root "libc" directory to the include path.
target_include_directories(llvm-libc-common-utilities INTERFACE ${CMAKE_CURRENT_LIST_DIR}/../../../libc)
-target_compile_definitions(llvm-libc-common-utilities INTERFACE LIBC_NAMESPACE=__llvm_libc_common_utils)
+target_compile_definitions(llvm-libc-common-utilities INTERFACE LIBC_NAMESPACE=__llvm_libc_common_utils)
target_compile_features(llvm-libc-common-utilities INTERFACE cxx_std_17)
More information about the libcxx-commits
mailing list