From 6c4267fb1779bc5550bb413f33250f9365acfbc6 Mon Sep 17 00:00:00 2001 From: Michael Jones Date: Mon, 21 Oct 2024 15:04:06 -0700 Subject: [PATCH] [libcxx][libc] Hand in Hand PoC with from_chars (#91651) Implements std::from_chars for float and double. The implementation uses LLVM-libc to do the real parsing. Since this is the first time libc++ uses LLVM-libc there is a bit of additional infrastructure code. The patch is based on the [RFC] Project Hand In Hand (LLVM-libc/libc++ code sharing) https://discourse.llvm.org/t/rfc-project-hand-in-hand-llvm-libc-libc-code-sharing/77701 --- libc/shared/fp_bits.h | 22 + libc/shared/str_to_float.h | 27 + libc/shared/str_to_integer.h | 24 + libc/src/__support/FPUtil/FPBits.h | 12 + libc/src/__support/high_precision_decimal.h | 11 + libc/src/__support/str_to_float.h | 26 + libc/src/__support/str_to_integer.h | 11 + libc/src/__support/str_to_num_result.h | 11 + libcxx/docs/Status/Cxx17Papers.csv | 2 +- libcxx/docs/Status/Cxx2cIssues.csv | 1 + libcxx/include/CMakeLists.txt | 1 + .../__charconv/from_chars_floating_point.h | 73 + libcxx/include/__configuration/availability.h | 13 + libcxx/include/charconv | 7 + libcxx/include/module.modulemap | 1 + libcxx/lib/abi/CHANGELOG.TXT | 7 + ...bcxxabi.v1.stable.exceptions.nonew.abilist | 2 + ...bcxxabi.v1.stable.exceptions.nonew.abilist | 2 + ...bcxxabi.v1.stable.exceptions.nonew.abilist | 2 + ...bcxxabi.v1.stable.exceptions.nonew.abilist | 2 + ...bcxxabi.v1.stable.exceptions.nonew.abilist | 2 + ...bcxxabi.v1.stable.exceptions.nonew.abilist | 2 + ...bcxxabi.v1.stable.exceptions.nonew.abilist | 2 + ...bcxxabi.v1.stable.exceptions.nonew.abilist | 2 + ...xxabi.v1.stable.noexceptions.nonew.abilist | 4 +- libcxx/src/CMakeLists.txt | 9 +- libcxx/src/charconv.cpp | 12 + .../src/include/from_chars_floating_point.h | 457 +++++ .../floating_point.pass.cpp | 1560 +++++++++++++++++ .../utilities/charconv/charconv.msvc/test.cpp | 34 +- .../charconv/charconv.msvc/test.pass.cpp | 4 + libcxx/test/support/charconv_test_helpers.h | 2 + libcxx/test/support/test_macros.h | 4 + libcxx/utils/libcxx/test/features.py | 8 + .../cmake/Modules/FindLibcCommonUtils.cmake | 14 + .../llvm-project-overlay/libc/BUILD.bazel | 13 + 36 files changed, 2365 insertions(+), 21 deletions(-) create mode 100644 libc/shared/fp_bits.h create mode 100644 libc/shared/str_to_float.h create mode 100644 libc/shared/str_to_integer.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/floating_point.pass.cpp create mode 100644 runtimes/cmake/Modules/FindLibcCommonUtils.cmake 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 new file mode 100644 index 00000000000000..b133a28e26efcd --- /dev/null +++ b/libc/shared/str_to_float.h @@ -0,0 +1,27 @@ +//===-- 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_DECL { +namespace shared { + +using internal::ExpandedFloat; +using internal::FloatConvertReturn; +using internal::RoundDirection; + +using internal::binary_exp_to_float; +using internal::decimal_exp_to_float; + +} // 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/libc/src/__support/FPUtil/FPBits.h b/libc/src/__support/FPUtil/FPBits.h index 5d1f633bb56ee4..6da89091a8ced9 100644 --- a/libc/src/__support/FPUtil/FPBits.h +++ b/libc/src/__support/FPUtil/FPBits.h @@ -6,6 +6,12 @@ // //===----------------------------------------------------------------------===// +// ----------------------------------------------------------------------------- +// **** WARNING **** +// This file is shared with libc++. You should also be careful when adding +// dependencies to this file, since it needs to build for all libc++ targets. +// ----------------------------------------------------------------------------- + #ifndef LLVM_LIBC_SRC___SUPPORT_FPUTIL_FPBITS_H #define LLVM_LIBC_SRC___SUPPORT_FPUTIL_FPBITS_H @@ -795,6 +801,12 @@ template LIBC_INLINE static constexpr FPType get_fp_type() { static_assert(cpp::always_false, "Unsupported type"); } +// ----------------------------------------------------------------------------- +// **** WARNING **** +// This interface is shared with libc++, if you change this interface you need +// to update it in both libc and libc++. You should also be careful when adding +// dependencies to this file, since it needs to build for all libc++ targets. +// ----------------------------------------------------------------------------- // A generic class to manipulate C++ floating point formats. // It derives its functionality to FPRepImpl above. template diff --git a/libc/src/__support/high_precision_decimal.h b/libc/src/__support/high_precision_decimal.h index 3e397574d4cbb7..ac11649d1d1686 100644 --- a/libc/src/__support/high_precision_decimal.h +++ b/libc/src/__support/high_precision_decimal.h @@ -6,6 +6,12 @@ // //===----------------------------------------------------------------------===// +// ----------------------------------------------------------------------------- +// **** WARNING **** +// This file is shared with libc++. You should also be careful when adding +// dependencies to this file, since it needs to build for all libc++ targets. +// ----------------------------------------------------------------------------- + #ifndef LLVM_LIBC_SRC___SUPPORT_HIGH_PRECISION_DECIMAL_H #define LLVM_LIBC_SRC___SUPPORT_HIGH_PRECISION_DECIMAL_H @@ -23,6 +29,11 @@ struct LShiftTableEntry { char const *power_of_five; }; +// ----------------------------------------------------------------------------- +// **** WARNING **** +// This interface is shared with libc++, if you change this interface you need +// to update it in both libc and libc++. +// ----------------------------------------------------------------------------- // This is used in both this file and in the main str_to_float.h. // TODO: Figure out where to put this. enum class RoundDirection { Up, Down, Nearest }; diff --git a/libc/src/__support/str_to_float.h b/libc/src/__support/str_to_float.h index a452b3a55fdeb4..91569af5cb7679 100644 --- a/libc/src/__support/str_to_float.h +++ b/libc/src/__support/str_to_float.h @@ -6,6 +6,12 @@ // //===----------------------------------------------------------------------===// +// ----------------------------------------------------------------------------- +// **** WARNING **** +// This file is shared with libc++. You should also be careful when adding +// dependencies to this file, since it needs to build for all libc++ targets. +// ----------------------------------------------------------------------------- + #ifndef LLVM_LIBC_SRC___SUPPORT_STR_TO_FLOAT_H #define LLVM_LIBC_SRC___SUPPORT_STR_TO_FLOAT_H @@ -32,11 +38,21 @@ namespace LIBC_NAMESPACE_DECL { namespace internal { +// ----------------------------------------------------------------------------- +// **** WARNING **** +// This interface is shared with libc++, if you change this interface you need +// to update it in both libc and libc++. +// ----------------------------------------------------------------------------- template struct ExpandedFloat { typename fputil::FPBits::StorageType mantissa; int32_t exponent; }; +// ----------------------------------------------------------------------------- +// **** WARNING **** +// This interface is shared with libc++, if you change this interface you need +// to update it in both libc and libc++. +// ----------------------------------------------------------------------------- template struct FloatConvertReturn { ExpandedFloat num = {0, 0}; int error = 0; @@ -637,6 +653,11 @@ template <> LIBC_INLINE constexpr int32_t get_lower_bound() { return -(309 + 15 + 20); } +// ----------------------------------------------------------------------------- +// **** WARNING **** +// This interface is shared with libc++, if you change this interface you need +// to update it in both libc and libc++. +// ----------------------------------------------------------------------------- // Takes a mantissa and base 10 exponent and converts it into its closest // floating point type T equivalient. First we try the Eisel-Lemire algorithm, // then if that fails then we fall back to a more accurate algorithm for @@ -716,6 +737,11 @@ LIBC_INLINE FloatConvertReturn decimal_exp_to_float( return output; } +// ----------------------------------------------------------------------------- +// **** WARNING **** +// This interface is shared with libc++, if you change this interface you need +// to update it in both libc and libc++. +// ----------------------------------------------------------------------------- // Takes a mantissa and base 2 exponent and converts it into its closest // floating point type T equivalient. Since the exponent is already in the right // form, this is mostly just shifting and rounding. This is used for hexadecimal diff --git a/libc/src/__support/str_to_integer.h b/libc/src/__support/str_to_integer.h index c8d02434c89ce2..86611f9a6902da 100644 --- a/libc/src/__support/str_to_integer.h +++ b/libc/src/__support/str_to_integer.h @@ -6,6 +6,12 @@ // //===----------------------------------------------------------------------===// +// ----------------------------------------------------------------------------- +// **** WARNING **** +// This file is shared with libc++. You should also be careful when adding +// dependencies to this file, since it needs to build for all libc++ targets. +// ----------------------------------------------------------------------------- + #ifndef LLVM_LIBC_SRC___SUPPORT_STR_TO_INTEGER_H #define LLVM_LIBC_SRC___SUPPORT_STR_TO_INTEGER_H @@ -73,6 +79,11 @@ LIBC_INLINE int infer_base(const char *__restrict src, size_t src_len) { return 10; } +// ----------------------------------------------------------------------------- +// **** WARNING **** +// This interface is shared with libc++, if you change this interface you need +// to update it in both libc and libc++. +// ----------------------------------------------------------------------------- // Takes a pointer to a string and the base to convert to. This function is used // as the backend for all of the string to int functions. template diff --git a/libc/src/__support/str_to_num_result.h b/libc/src/__support/str_to_num_result.h index 6d361357cac2a9..48c363c88ff419 100644 --- a/libc/src/__support/str_to_num_result.h +++ b/libc/src/__support/str_to_num_result.h @@ -6,6 +6,12 @@ // //===----------------------------------------------------------------------===// +// ----------------------------------------------------------------------------- +// **** WARNING **** +// This file is shared with libc++. You should also be careful when adding +// dependencies to this file, since it needs to build for all libc++ targets. +// ----------------------------------------------------------------------------- + #ifndef LLVM_LIBC_SRC___SUPPORT_STR_TO_NUM_RESULT_H #define LLVM_LIBC_SRC___SUPPORT_STR_TO_NUM_RESULT_H @@ -16,6 +22,11 @@ namespace LIBC_NAMESPACE_DECL { +// ----------------------------------------------------------------------------- +// **** WARNING **** +// This interface is shared with libc++, if you change this interface you need +// to update it in both libc and libc++. +// ----------------------------------------------------------------------------- template struct StrToNumResult { T value; int error; diff --git a/libcxx/docs/Status/Cxx17Papers.csv b/libcxx/docs/Status/Cxx17Papers.csv index 3b56807312d556..7714f41ca19e04 100644 --- a/libcxx/docs/Status/Cxx17Papers.csv +++ b/libcxx/docs/Status/Cxx17Papers.csv @@ -71,7 +71,7 @@ "`P0394R4 `__","Hotel Parallelifornia: terminate() for Parallel Algorithms Exception Handling","2016-06 (Oulu)","|Complete|","17.0","" "","","","","","" "`P0003R5 `__","Removing Deprecated Exception Specifications from C++17","2016-11 (Issaquah)","|Complete|","5.0","" -"`P0067R5 `__","Elementary string conversions, revision 5","2016-11 (Issaquah)","|Partial|","","``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``." +"`P0067R5 `__","Elementary string conversions, revision 5","2016-11 (Issaquah)","|Partial|","","``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." "`P0403R1 `__","Literal suffixes for ``basic_string_view``\ ","2016-11 (Issaquah)","|Complete|","4.0","" "`P0414R2 `__","Merging shared_ptr changes from Library Fundamentals to C++17","2016-11 (Issaquah)","|Complete|","11.0","" "`P0418R2 `__","Fail or succeed: there is no atomic lattice","2016-11 (Issaquah)","","","" diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv index a62c4992020a0f..19572c655ecd2e 100644 --- a/libcxx/docs/Status/Cxx2cIssues.csv +++ b/libcxx/docs/Status/Cxx2cIssues.csv @@ -78,4 +78,5 @@ "","","","","","" "`LWG3343 `__","Ordering of calls to ``unlock()`` and ``notify_all()`` in Effects element of ``notify_all_at_thread_exit()`` should be reversed","Not Adopted Yet","|Complete|","16.0","" "`LWG4139 `__","ยง[time.zone.leap] recursive constraint in <=>","Not Adopted Yet","|Complete|","20.0","" +"`LWG3456 `__","Pattern used by std::from_chars is underspecified (option B)",,"Not Yet Adopted","|Complete|","20.0","" "","","","","","" diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index a107314518b1fa..86d2fc2c2c679d 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..2860b0e8da83af --- /dev/null +++ b/libcxx/include/__charconv/from_chars_floating_point.h @@ -0,0 +1,73 @@ +// -*- 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 <__config> +#include <__system_error/errc.h> +#include + +#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 + +template +struct __from_chars_result { + _Fp __value; + ptrdiff_t __n; + errc __ec; +}; + +template +_LIBCPP_EXPORTED_FROM_ABI __from_chars_result<_Fp> __from_chars_floating_point( + [[clang::noescape]] const char* __first, [[clang::noescape]] const char* __last, chars_format __fmt); + +extern template __from_chars_result __from_chars_floating_point( + [[clang::noescape]] const char* __first, [[clang::noescape]] const char* __last, chars_format __fmt); + +extern template __from_chars_result __from_chars_floating_point( + [[clang::noescape]] const char* __first, [[clang::noescape]] const char* __last, chars_format __fmt); + +template +_LIBCPP_HIDE_FROM_ABI from_chars_result +__from_chars(const char* __first, const char* __last, _Fp& __value, chars_format __fmt) { + __from_chars_result<_Fp> __r = std::__from_chars_floating_point<_Fp>(__first, __last, __fmt); + if (__r.__ec != errc::invalid_argument) + __value = __r.__value; + return {__first + __r.__n, __r.__ec}; +} + +_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(__first, __last, __value, __fmt); +} + +_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(__first, __last, __value, __fmt); +} + +#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/__configuration/availability.h b/libcxx/include/__configuration/availability.h index f42ff460db4544..173999c46807c3 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 @@ -409,6 +417,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_EXCEPTIONS. // Those are defined in terms of the availability attributes above, and // should not be vendor-specific. diff --git a/libcxx/include/charconv b/libcxx/include/charconv index a2e270e9316dc7..29c6875008abb4 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 + from_chars_result from_chars(const char* first, const char* last, + float& value, chars_format fmt); + + from_chars_result from_chars(const char* first, const char* last, + double& value, chars_format fmt); + } // namespace std */ @@ -73,6 +79,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/include/module.modulemap b/libcxx/include/module.modulemap index 06e93d2452904d..d775da489e35e5 100644 --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -898,6 +898,7 @@ module std [system] { module charconv { module chars_format { header "__charconv/chars_format.h" } + module from_chars_floating_point { header "__charconv/from_chars_floating_point.h" } module from_chars_integral { header "__charconv/from_chars_integral.h" } module from_chars_result { header "__charconv/from_chars_result.h" } module tables { header "__charconv/tables.h" } diff --git a/libcxx/lib/abi/CHANGELOG.TXT b/libcxx/lib/abi/CHANGELOG.TXT index 6911694b75d8a5..e27eb5fa046ff2 100644 --- a/libcxx/lib/abi/CHANGELOG.TXT +++ b/libcxx/lib/abi/CHANGELOG.TXT @@ -16,6 +16,13 @@ New entries should be added directly below the "Version" header. Version 20.0 ------------ +* [libcxx][libc] Implements from_chars floating-point + + All platforms + ------------- + Symbol added: _ZNSt3__127__from_chars_floating_pointIdEENS_19__from_chars_resultIT_EEPKcS5_NS_12chars_formatE + Symbol added: _ZNSt3__127__from_chars_floating_pointIfEENS_19__from_chars_resultIT_EEPKcS5_NS_12chars_formatE + * [libc++] Stop trying to avoid exporting some typeinfo names This patch removes the explicit list of symbols to avoid exporting 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 db77e1d0ac30b6..79f999b3e02bb1 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 @@ -1584,6 +1584,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_pointIdEENS_19__from_chars_resultIT_EEPKcS5_NS_12chars_formatE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__127__from_chars_floating_pointIfEENS_19__from_chars_resultIT_EEPKcS5_NS_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/i686-linux-android21.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/i686-linux-android21.libcxxabi.v1.stable.exceptions.nonew.abilist index 8af5db472f7c3a..9efdf11940a77f 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_pointIdEENS_19__from_chars_resultIT_EEPKcS5_NS_12chars_formatE', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt6__ndk127__from_chars_floating_pointIfEENS_19__from_chars_resultIT_EEPKcS5_NS_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/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..7fde4b905fc503 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 @@ -1715,6 +1715,8 @@ {'import_export': 'wEXP', 'is_defined': True, 'name': '_ZNSt3__119basic_istringstreamIcNS_11char_traitsIcEENS_9allocatorIcEEEaSEOS5_', 'storage_mapping_class': 'DS', 'type': 'FUNC'} {'import_export': 'wEXP', 'is_defined': True, 'name': '_ZNSt3__119basic_ostringstreamIcNS_11char_traitsIcEENS_9allocatorIcEEEaSEOS5_', 'storage_mapping_class': 'DS', 'type': 'FUNC'} {'import_export': 'wEXP', 'is_defined': True, 'name': '_ZNSt3__122__libcpp_verbose_abortEPKcz', 'storage_mapping_class': 'DS', 'type': 'FUNC'} +{'import_export': 'wEXP', 'is_defined': True, 'name': '_ZNSt3__127__from_chars_floating_pointIdEENS_19__from_chars_resultIT_EEPKcS5_NS_12chars_formatE', 'storage_mapping_class': 'DS', 'type': 'FUNC'} +{'import_export': 'wEXP', 'is_defined': True, 'name': '_ZNSt3__127__from_chars_floating_pointIfEENS_19__from_chars_resultIT_EEPKcS5_NS_12chars_formatE', 'storage_mapping_class': 'DS', 'type': 'FUNC'} {'import_export': 'wEXP', 'is_defined': True, 'name': '_ZNSt3__14__fs10filesystem16_FilesystemClock9is_steadyE', 'storage_mapping_class': 'RO', 'type': 'OBJECT'} {'import_export': 'wEXP', 'is_defined': True, 'name': '_ZNSt3__14__fs10filesystem4path19preferred_separatorE', 'storage_mapping_class': 'RO', 'type': 'OBJECT'} {'import_export': 'wEXP', 'is_defined': True, 'name': '_ZNSt3__16__sortIRNS_6__lessIaaEEPaEEvT0_S5_T_', '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..da30346257f957 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 @@ -1715,6 +1715,8 @@ {'import_export': 'wEXP', 'is_defined': True, 'name': '_ZNSt3__119basic_istringstreamIcNS_11char_traitsIcEENS_9allocatorIcEEEaSEOS5_', 'storage_mapping_class': 'DS', 'type': 'FUNC'} {'import_export': 'wEXP', 'is_defined': True, 'name': '_ZNSt3__119basic_ostringstreamIcNS_11char_traitsIcEENS_9allocatorIcEEEaSEOS5_', 'storage_mapping_class': 'DS', 'type': 'FUNC'} {'import_export': 'wEXP', 'is_defined': True, 'name': '_ZNSt3__122__libcpp_verbose_abortEPKcz', 'storage_mapping_class': 'DS', 'type': 'FUNC'} +{'import_export': 'wEXP', 'is_defined': True, 'name': '_ZNSt3__127__from_chars_floating_pointIdEENS_19__from_chars_resultIT_EEPKcS5_NS_12chars_formatE', 'storage_mapping_class': 'DS', 'type': 'FUNC'} +{'import_export': 'wEXP', 'is_defined': True, 'name': '_ZNSt3__127__from_chars_floating_pointIfEENS_19__from_chars_resultIT_EEPKcS5_NS_12chars_formatE', 'storage_mapping_class': 'DS', 'type': 'FUNC'} {'import_export': 'wEXP', 'is_defined': True, 'name': '_ZNSt3__14__fs10filesystem16_FilesystemClock9is_steadyE', 'storage_mapping_class': 'RO', 'type': 'OBJECT'} {'import_export': 'wEXP', 'is_defined': True, 'name': '_ZNSt3__14__fs10filesystem4path19preferred_separatorE', 'storage_mapping_class': 'RO', 'type': 'OBJECT'} {'import_export': 'wEXP', 'is_defined': True, 'name': '_ZNSt3__16__sortIRNS_6__lessIaaEEPaEEvT0_S5_T_', '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 62716f5c415f00..e1dc6e778b57c3 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 @@ -1584,6 +1584,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..ceeeffe4d97925 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_pointIdEENS_19__from_chars_resultIT_EEPKcS5_NS_12chars_formatE', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt6__ndk127__from_chars_floating_pointIfEENS_19__from_chars_resultIT_EEPKcS5_NS_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..d3670d237b239f 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_pointIdEENS_19__from_chars_resultIT_EEPKcS5_NS_12chars_formatE', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__127__from_chars_floating_pointIfEENS_19__from_chars_resultIT_EEPKcS5_NS_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..2c21a03d41a0d7 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_pointIdEENS_19__from_chars_resultIT_EEPKcS5_NS_12chars_formatE', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__127__from_chars_floating_pointIfEENS_19__from_chars_resultIT_EEPKcS5_NS_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.noexceptions.nonew.abilist b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.noexceptions.nonew.abilist index ac3cc129c04b51..0d4c5095090878 100644 --- a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.noexceptions.nonew.abilist +++ b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.noexceptions.nonew.abilist @@ -1204,6 +1204,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_pointIdEENS_19__from_chars_resultIT_EEPKcS5_NS_12chars_formatE', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__127__from_chars_floating_pointIfEENS_19__from_chars_resultIT_EEPKcS5_NS_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'} @@ -2006,4 +2008,4 @@ {'is_defined': True, 'name': '_ZTv0_n24_NSt3__114basic_iostreamIcNS_11char_traitsIcEEED0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZTv0_n24_NSt3__114basic_iostreamIcNS_11char_traitsIcEEED1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZTv0_n24_NSt3__19strstreamD0Ev', 'type': 'FUNC'} -{'is_defined': True, 'name': '_ZTv0_n24_NSt3__19strstreamD1Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZTv0_n24_NSt3__19strstreamD1Ev', 'type': 'FUNC'} \ No newline at end of file diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt index 4af04f202db1f7..cce8b8976f73c0 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 @@ -172,11 +173,14 @@ endif() split_list(LIBCXX_COMPILE_FLAGS) split_list(LIBCXX_LINK_FLAGS) +include(FindLibcCommonUtils) + # Build the shared library. add_library(cxx_shared SHARED ${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 - PRIVATE ${LIBCXX_LIBRARIES}) + PRIVATE ${LIBCXX_LIBRARIES} + PRIVATE llvm-libc-common-utilities) set_target_properties(cxx_shared PROPERTIES EXCLUDE_FROM_ALL "$,FALSE,TRUE>" @@ -267,7 +271,8 @@ add_library(cxx_static STATIC ${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 PRIVATE ${LIBCXX_LIBRARIES} - PRIVATE libcxx-abi-static) + PRIVATE libcxx-abi-static + PRIVATE llvm-libc-common-utilities) set_target_properties(cxx_static PROPERTIES EXCLUDE_FROM_ALL "$,FALSE,TRUE>" diff --git a/libcxx/src/charconv.cpp b/libcxx/src/charconv.cpp index 4fd7a2c2c0f038..3fe0afec0e283c 100644 --- a/libcxx/src/charconv.cpp +++ b/libcxx/src/charconv.cpp @@ -9,6 +9,7 @@ #include #include +#include "include/from_chars_floating_point.h" #include "include/to_chars_floating_point.h" _LIBCPP_BEGIN_NAMESPACE_STD @@ -74,4 +75,15 @@ to_chars_result to_chars(char* __first, char* __last, long double __value, chars __first, __last, static_cast(__value), __fmt, __precision); } +template +__from_chars_result<_Fp> __from_chars_floating_point( + [[clang::noescape]] const char* __first, [[clang::noescape]] const char* __last, chars_format __fmt) { + return std::__from_chars_floating_point_impl<_Fp>(__first, __last, __fmt); +} + +template __from_chars_result __from_chars_floating_point( + [[clang::noescape]] const char* __first, [[clang::noescape]] const char* __last, chars_format __fmt); + +template __from_chars_result __from_chars_floating_point( + [[clang::noescape]] const char* __first, [[clang::noescape]] const char* __last, chars_format __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..19eeeb28fb08d2 --- /dev/null +++ b/libcxx/src/include/from_chars_floating_point.h @@ -0,0 +1,457 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// 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> +#include +#include +#include +#include + +// Included for the _Floating_type_traits class +#include "to_chars_floating_point.h" + +_LIBCPP_BEGIN_NAMESPACE_STD + +// Parses an infinity string. +// 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. +// - __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 +__from_chars_result<_Fp> +__from_chars_floating_point_inf(const char* const __first, const char* __last, const char* __ptr, bool __negative) { + if (__last - __ptr < 2) [[unlikely]] + return {_Fp{0}, 0, errc::invalid_argument}; + + if (std::tolower(__ptr[0]) != 'n' || std::tolower(__ptr[1]) != 'f') [[unlikely]] + return {_Fp{0}, 0, 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<_Fp>::has_infinity) { + if (__negative) + return {-std::numeric_limits<_Fp>::infinity(), __ptr - __first, std::errc{}}; + + return {std::numeric_limits<_Fp>::infinity(), __ptr - __first, std::errc{}}; + } else { + return {_Fp{0}, __ptr - __first, errc::result_out_of_range}; + } +} + +// Parses a nan string. +// 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. +// - __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 +__from_chars_result<_Fp> +__from_chars_floating_point_nan(const char* const __first, const char* __last, const char* __ptr, bool __negative) { + if (__last - __ptr < 2) [[unlikely]] + return {_Fp{0}, 0, errc::invalid_argument}; + + if (std::tolower(__ptr[0]) != 'a' || std::tolower(__ptr[1]) != 'n') [[unlikely]] + return {_Fp{0}, 0, 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) + return {-std::numeric_limits<_Fp>::quiet_NaN(), __ptr - __first, std::errc{}}; + + return {std::numeric_limits<_Fp>::quiet_NaN(), __ptr - __first, std::errc{}}; +} + +template +struct __fractional_constant_result { + size_t __offset{size_t(-1)}; + _Tp __mantissa{0}; + int __exponent{0}; + bool __truncated{false}; + bool __is_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 +__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.__is_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; +} + +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 __e = + LIBC_NAMESPACE::shared::strtointeger(__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; + + if (__sum > __max_biased_exponent) + return __max_biased_exponent; + + if (__sum < -__max_biased_exponent) + return -__max_biased_exponent; + + return __sum; +} + +template +__from_chars_result<_Fp> +__calculate_result(_Tp __mantissa, int __exponent, bool __negative, __from_chars_result<_Fp> __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 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 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 (__result.__ec == errc::result_out_of_range && __r.is_subnormal() && !__r.is_zero()) + __result.__ec = errc{}; + + if (__negative) + __result.__value = -__r.get_val(); + else + __result.__value = __r.get_val(); + + 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 +__from_chars_result<_Fp> +__from_chars_floating_point_hex(const char* const __first, const char* __last, const char* __ptr, bool __negative) { + size_t __n = __last - __first; + ptrdiff_t __offset = __ptr - __first; + + auto __fractional = + std::__parse_fractional_hex_constant::_Uint_type>(__first, __n, __offset); + if (!__fractional.__is_valid) + return {_Fp{0}, 0, 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<_Fp> __result{_Fp{0}, __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<_Fp>(__expanded_float.mantissa, __expanded_float.exponent, __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 +__fractional_constant_result<_Tp> +__parse_fractional_decimal_constant(const char* __input, ptrdiff_t __n, ptrdiff_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.__is_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 +__from_chars_result<_Fp> __from_chars_floating_point_decimal( + const char* const __first, const char* __last, chars_format __fmt, const char* __ptr, bool __negative) { + ptrdiff_t __n = __last - __first; + ptrdiff_t __offset = __ptr - __first; + + auto __fractional = + std::__parse_fractional_decimal_constant::_Uint_type>(__first, __n, __offset); + if (!__fractional.__is_valid) + return {_Fp{0}, 0, 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. + 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 {_Fp{0}, 0, errc::invalid_argument}; + } + + __offset = __parsed_exponent.__offset; + __exponent = std::__merge_exponents( + __fractional.__exponent, __parsed_exponent.__value, LIBC_NAMESPACE::shared::FPBits<_Fp>::MAX_BIASED_EXPONENT); + } + + __from_chars_result<_Fp> __result{_Fp{0}, __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; + } + } + + return std::__calculate_result(__expanded_float.mantissa, __expanded_float.exponent, __negative, __result); +} + +template +__from_chars_result<_Fp> +__from_chars_floating_point_impl(const char* const __first, const char* __last, chars_format __fmt) { + if (__first == __last) [[unlikely]] + return {_Fp{0}, 0, errc::invalid_argument}; + + const char* __ptr = __first; + bool __negative = *__ptr == '-'; + if (__negative) { + ++__ptr; + if (__ptr == __last) [[unlikely]] + return {_Fp{0}, 0, errc::invalid_argument}; + } + + // [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 + + // [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': + return std::__from_chars_floating_point_inf<_Fp>(__first, __last, __ptr + 1, __negative); + case 'n': + 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. + return std::__from_chars_floating_point_nan<_Fp>(__first, __last, __ptr + 1, __negative); + return {_Fp{0}, 0, errc::invalid_argument}; + } + + if (__fmt == chars_format::hex) + return std::__from_chars_floating_point_hex<_Fp>(__first, __last, __ptr, __negative); + + return std::__from_chars_floating_point_decimal<_Fp>(__first, __last, __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/floating_point.pass.cpp b/libcxx/test/std/utilities/charconv/charconv.from.chars/floating_point.pass.cpp new file mode 100644 index 00000000000000..6faf0499c4c9bb --- /dev/null +++ b/libcxx/test/std/utilities/charconv/charconv.from.chars/floating_point.pass.cpp @@ -0,0 +1,1560 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// XFAIL: availability-fp_from_chars-missing + +// 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) + +#include +#include +#include +#include +#include +#include +#include + +#include "charconv_test_helpers.h" +#include "test_macros.h" + +template +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::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::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::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::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::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::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::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::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::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::infinity()); + } +} + +template +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 +void test_fmt_independent(std::chars_format fmt) { + test_infinity(fmt); + test_nan(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)); + } + { // 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); + + assert(result.ec == std::errc::invalid_argument); + assert(result.ptr == s); + assert(value == F(0.25)); + } + { // 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); + + assert(result.ec == std::errc::invalid_argument); + assert(result.ptr == s); + assert(value == F(0.25)); + } +} + +template +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(fmt); + } +}; + +template +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)); + } + } + + // *** 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)); + } + { // 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"; + + 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)); + } + { + 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 whitespace + { + 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"; + 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"; + + 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{}); + 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)); + } + { // 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"; + + 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 +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)); + } + { + 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"; + + 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 + + { // 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)); + } + { // 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"; + + 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::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::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 +struct test_general { + 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); + + 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); + assert(r.ec == std::errc{}); + 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); + 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); + 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"; + + // 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)); + } + { // 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 == 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"; + + 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"; + + 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 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)); + } + { + 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 with whitespace + { + 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"; + + 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 + 6); + 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 == 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)); + } + } + { // 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)); + } + { // 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::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::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)); + } + } +}; + +template +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 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"; + + 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::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::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. +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(all_floats); + run(all_floats); + run(all_floats); + run(all_floats); + + run(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 30ee9adcd74bf0..ace6d46b879b01 100644 --- a/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp +++ b/libcxx/test/std/utilities/charconv/charconv.msvc/test.cpp @@ -45,6 +45,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; @@ -589,8 +590,8 @@ void test_floating_prefix(const conditional_t(val) == bits, "round-trip", bits); -#endif +#endif // TEST_HAS_FROM_CHARS_FLOATING_POINT } { @@ -656,8 +656,8 @@ void test_floating_hex_prefix(const conditional_t(val) == bits, "(hex) round-trip", bits); -#endif +#endif // TEST_HAS_FROM_CHARS_FLOATING_POINT } } @@ -786,8 +786,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 void test_floating_from_chars(const chars_format fmt) { test_from_chars("", fmt, 0, inv_arg); // no characters @@ -855,11 +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. +# ifdef _MSC_VER test_from_chars("nan(InD)", fmt, 8, errc{}, -qnan); test_from_chars("-nan(InD)", fmt, 9, errc{}, -qnan); test_from_chars("nan(SnAn)", fmt, 9, errc{}, nullopt, TestFromCharsMode::SignalingNaN); test_from_chars("-nan(SnAn)", fmt, 10, errc{}, nullopt, TestFromCharsMode::SignalingNaN); +# endif switch (fmt) { case chars_format::general: @@ -941,7 +942,7 @@ void test_floating_from_chars(const chars_format fmt) { break; } } -#endif +#endif // TEST_HAS_FROM_CHARS_FLOATING_POINT template void test_floating_to_chars( @@ -953,13 +954,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(fmt); test_floating_from_chars(fmt); } - // Test rounding. // See float_from_chars_test_cases.hpp in this directory. @@ -993,7 +992,8 @@ void all_floating_tests(mt19937_64& mt64) { for (const auto& p : floating_point_test_cases_double) { test_from_chars(p.first, chars_format::general, strlen(p.first), errc{}, _Bit_cast(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..c294a40ce71ce5 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,9 @@ // UNSUPPORTED: c++03, c++11, c++14 +// TODO Investigate why this fails +// UNSUPPORTED: windows + // 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 +25,7 @@ // #include +#include "test_macros.h" // Work-around for sprintf_s's usage in the Microsoft tests. #ifndef _WIN32 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