From: Jonathan Wakely Date: Mon, 20 Jul 2020 22:49:27 +0000 (+0100) Subject: libstdc++: Add std::from_chars for floating-point types X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=932fbc868ad429167a3d4d5625aa9d6dc0b4506b;p=gcc.git libstdc++: Add std::from_chars for floating-point types This adds the missing std::from_chars overloads for floating-point types, as required for C++17 conformance. The implementation is a hack and not intended to be used in the long term. Rather than parsing the string directly, this determines the initial portion of the string that matches the pattern determined by the chars_format parameter, then creates a NTBS to be parsed by strtod (or strtold or strtof). Because creating a NTBS requires allocating memory, but std::from_chars is noexcept, we need to be careful to minimise allocation. Even after being careful, allocation failure is still possible, and so a non-conforming std::no_more_memory error code might be returned. Because strtod et al depend on the current locale, but std::from_chars does not, we change the current thread's locale to "C" using newlocale and uselocale before calling strtod, and restore it afterwards. Because strtod doesn't have the equivalent of a std::chars_format parameter, it has to examine the input to determine the format in use, even though the std::from_chars code has already parsed it once (or twice for large input strings!) By replacing the use of strtod we could avoid allocation, avoid changing locale, and use optimised code paths specific to each std::chars_format case. We would also get more portable behaviour, rather than depending on the presence of uselocale, and on any bugs or quirks of the target libc's strtod. Replacing strtod is a project for a later date. libstdc++-v3/ChangeLog: * acinclude.m4 (libtool_VERSION): Bump version. * config.h.in: Regenerate. * config/abi/pre/gnu.ver: Add GLIBCXX_3.4.29 version and new exports. * config/os/gnu-linux/ldbl-extra.ver: Add _GLIBCXX_LDBL_3.4.29 version and new export. * configure: Regenerate. * configure.ac: Check for and uselocale. * crossconfig.m4: Add macro or checks for uselocale. * include/std/charconv (from_chars): Declare overloads for float, double, and long double. * src/c++17/Makefile.am: Add new file. * src/c++17/Makefile.in: Regenerate. * src/c++17/floating_from_chars.cc: New file. (from_chars): Define for float, double, and long double. * testsuite/20_util/from_chars/1_c++20_neg.cc: Prune extra diagnostics caused by new overloads. * testsuite/20_util/from_chars/1_neg.cc: Likewise. * testsuite/20_util/from_chars/2.cc: Check leading '+'. * testsuite/20_util/from_chars/4.cc: New test. * testsuite/20_util/from_chars/5.cc: New test. * testsuite/util/testsuite_abi.cc: Add new symbol versions. --- diff --git a/libstdc++-v3/acinclude.m4 b/libstdc++-v3/acinclude.m4 index ee5e0336f2c..e3926e1c9c2 100644 --- a/libstdc++-v3/acinclude.m4 +++ b/libstdc++-v3/acinclude.m4 @@ -3846,7 +3846,7 @@ changequote([,])dnl fi # For libtool versioning info, format is CURRENT:REVISION:AGE -libtool_VERSION=6:28:0 +libtool_VERSION=6:29:0 # Everything parsed; figure out what files and settings to use. case $enable_symvers in diff --git a/libstdc++-v3/config.h.in b/libstdc++-v3/config.h.in index 8940e0c7acd..8ae3e0fc4bf 100644 --- a/libstdc++-v3/config.h.in +++ b/libstdc++-v3/config.h.in @@ -526,6 +526,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_UNISTD_H +/* Define to 1 if you have the `uselocale' function. */ +#undef HAVE_USELOCALE + /* Defined if usleep exists. */ #undef HAVE_USLEEP @@ -556,6 +559,9 @@ /* Define if writev is available in . */ #undef HAVE_WRITEV +/* Define to 1 if you have the header file. */ +#undef HAVE_XLOCALE_H + /* Define to 1 if you have the `_acosf' function. */ #undef HAVE__ACOSF diff --git a/libstdc++-v3/config/abi/pre/gnu.ver b/libstdc++-v3/config/abi/pre/gnu.ver index edf4485e607..17aff5d907b 100644 --- a/libstdc++-v3/config/abi/pre/gnu.ver +++ b/libstdc++-v3/config/abi/pre/gnu.ver @@ -2299,6 +2299,13 @@ GLIBCXX_3.4.28 { } GLIBCXX_3.4.27; +GLIBCXX_3.4.29 { + + # std::from_chars + _ZSt10from_charsPKcS0_R[def]St12chars_format; + +} GLIBCXX_3.4.28; + # Symbols in the support library (libsupc++) have their own tag. CXXABI_1.3 { diff --git a/libstdc++-v3/config/os/gnu-linux/ldbl-extra.ver b/libstdc++-v3/config/os/gnu-linux/ldbl-extra.ver index 5ef4a6cb6e1..b4f3af0f9d9 100644 --- a/libstdc++-v3/config/os/gnu-linux/ldbl-extra.ver +++ b/libstdc++-v3/config/os/gnu-linux/ldbl-extra.ver @@ -40,6 +40,10 @@ GLIBCXX_LDBL_3.4.21 { __gnu_cxx_ldbl1287num_getI[cw]*16_M_extract_floatB5cxx11*; } GLIBCXX_LDBL_3.4.10; +GLIBCXX_LDBL_3.4.29 { + _ZSt10from_charsPKcS0_RgSt12chars_format; +} GLIBCXX_LDBL_3.4.21; + CXXABI_LDBL_1.3 { _ZT[IS]g; _ZT[IS]Pg; diff --git a/libstdc++-v3/configure b/libstdc++-v3/configure index dd54bd406a9..8d16bf3ffd6 100755 --- a/libstdc++-v3/configure +++ b/libstdc++-v3/configure @@ -22661,6 +22661,19 @@ fi done +for ac_header in xlocale.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "xlocale.h" "ac_cv_header_xlocale_h" "$ac_includes_default" +if test "x$ac_cv_header_xlocale_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_XLOCALE_H 1 +_ACEOF + +fi + +done + + # Only do link tests if native. Else, hardcode. if $GLIBCXX_IS_NATIVE; then @@ -28986,6 +28999,19 @@ if test "x$ac_cv_func_sockatmark" = xyes; then : #define HAVE_SOCKATMARK 1 _ACEOF +fi +done + + + # Non-standard functions used by C++17 std::from_chars + for ac_func in uselocale +do : + ac_fn_c_check_func "$LINENO" "uselocale" "ac_cv_func_uselocale" +if test "x$ac_cv_func_uselocale" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_USELOCALE 1 +_ACEOF + fi done @@ -41997,6 +42023,9 @@ _ACEOF fi + + $as_echo "#define HAVE_USELOCALE 1" >>confdefs.h + ;; *-darwin*) @@ -47770,6 +47799,18 @@ done CXXFLAGS="$ac_save_CXXFLAGS" + + for ac_func in uselocale +do : + ac_fn_c_check_func "$LINENO" "uselocale" "ac_cv_func_uselocale" +if test "x$ac_cv_func_uselocale" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_USELOCALE 1 +_ACEOF + +fi +done + ;; *djgpp) @@ -48041,6 +48082,17 @@ if test "x$ac_cv_func_sockatmark" = xyes; then : #define HAVE_SOCKATMARK 1 _ACEOF +fi +done + + for ac_func in uselocale +do : + ac_fn_c_check_func "$LINENO" "uselocale" "ac_cv_func_uselocale" +if test "x$ac_cv_func_uselocale" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_USELOCALE 1 +_ACEOF + fi done @@ -54682,6 +54734,17 @@ _ACEOF fi done + for ac_func in uselocale +do : + ac_fn_c_check_func "$LINENO" "uselocale" "ac_cv_func_uselocale" +if test "x$ac_cv_func_uselocale" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_USELOCALE 1 +_ACEOF + +fi +done + @@ -75231,7 +75294,7 @@ $as_echo "$as_me: WARNING: === Symbol versioning will be disabled." >&2;} fi # For libtool versioning info, format is CURRENT:REVISION:AGE -libtool_VERSION=6:28:0 +libtool_VERSION=6:29:0 # Everything parsed; figure out what files and settings to use. case $enable_symvers in diff --git a/libstdc++-v3/configure.ac b/libstdc++-v3/configure.ac index ffd0079613f..cbfdf4c6bad 100644 --- a/libstdc++-v3/configure.ac +++ b/libstdc++-v3/configure.ac @@ -256,6 +256,8 @@ AC_CHECK_HEADERS([linux/random.h], [], [], #endif ]]) +AC_CHECK_HEADERS([xlocale.h]) + # Only do link tests if native. Else, hardcode. if $GLIBCXX_IS_NATIVE; then @@ -282,6 +284,9 @@ if $GLIBCXX_IS_NATIVE; then # For Networking TS. AC_CHECK_FUNCS(sockatmark) + # Non-standard functions used by C++17 std::from_chars + AC_CHECK_FUNCS(uselocale) + # For iconv support. AM_ICONV diff --git a/libstdc++-v3/crossconfig.m4 b/libstdc++-v3/crossconfig.m4 index 313f84d05f4..9f2589b739e 100644 --- a/libstdc++-v3/crossconfig.m4 +++ b/libstdc++-v3/crossconfig.m4 @@ -63,6 +63,8 @@ case "${host}" in # We don't yet support AIX's TLS ABI. #GCC_CHECK_TLS AM_ICONV + + AC_DEFINE(HAVE_USELOCALE) ;; *-darwin*) @@ -73,6 +75,8 @@ case "${host}" in # Don't call GLIBCXX_CHECK_LINKER_FEATURES, Darwin doesn't have a GNU ld GLIBCXX_CHECK_MATH_SUPPORT GLIBCXX_CHECK_STDLIB_SUPPORT + + AC_CHECK_FUNCS(uselocale) ;; *djgpp) @@ -129,6 +133,7 @@ case "${host}" in AC_CHECK_FUNCS(aligned_alloc posix_memalign memalign _aligned_malloc) AC_CHECK_FUNCS(timespec_get) AC_CHECK_FUNCS(sockatmark) + AC_CHECK_FUNCS(uselocale) ;; *-fuchsia*) @@ -190,6 +195,7 @@ case "${host}" in AC_CHECK_FUNCS(aligned_alloc posix_memalign memalign _aligned_malloc) AC_CHECK_FUNCS(timespec_get) AC_CHECK_FUNCS(sockatmark) + AC_CHECK_FUNCS(uselocale) AM_ICONV ;; *-mingw32*) diff --git a/libstdc++-v3/include/std/charconv b/libstdc++-v3/include/std/charconv index cc7dd0e3758..be668c1939e 100644 --- a/libstdc++-v3/include/std/charconv +++ b/libstdc++-v3/include/std/charconv @@ -688,6 +688,20 @@ namespace __detail operator^=(chars_format& __lhs, chars_format __rhs) noexcept { return __lhs = __lhs ^ __rhs; } +#if _GLIBCXX_HAVE_USELOCALE + 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); + + from_chars_result + from_chars(const char* __first, const char* __last, long double& __value, + chars_format __fmt = chars_format::general); +#endif + _GLIBCXX_END_NAMESPACE_VERSION } // namespace std #endif // C++14 diff --git a/libstdc++-v3/src/c++17/Makefile.am b/libstdc++-v3/src/c++17/Makefile.am index 85e31fbd91f..642efb976ac 100644 --- a/libstdc++-v3/src/c++17/Makefile.am +++ b/libstdc++-v3/src/c++17/Makefile.am @@ -50,6 +50,7 @@ inst_sources = endif sources = \ + floating_from_chars.cc \ fs_dir.cc \ fs_ops.cc \ fs_path.cc \ diff --git a/libstdc++-v3/src/c++17/Makefile.in b/libstdc++-v3/src/c++17/Makefile.in index de605a3f6a6..ce08eb3ff11 100644 --- a/libstdc++-v3/src/c++17/Makefile.in +++ b/libstdc++-v3/src/c++17/Makefile.in @@ -124,8 +124,8 @@ LTLIBRARIES = $(noinst_LTLIBRARIES) libc__17convenience_la_LIBADD = @ENABLE_DUAL_ABI_TRUE@am__objects_1 = cow-fs_dir.lo cow-fs_ops.lo \ @ENABLE_DUAL_ABI_TRUE@ cow-fs_path.lo -am__objects_2 = fs_dir.lo fs_ops.lo fs_path.lo memory_resource.lo \ - $(am__objects_1) +am__objects_2 = floating_from_chars.lo fs_dir.lo fs_ops.lo fs_path.lo \ + memory_resource.lo $(am__objects_1) @ENABLE_DUAL_ABI_TRUE@am__objects_3 = cow-string-inst.lo @ENABLE_EXTERN_TEMPLATE_TRUE@am__objects_4 = ostream-inst.lo \ @ENABLE_EXTERN_TEMPLATE_TRUE@ string-inst.lo $(am__objects_3) @@ -435,6 +435,7 @@ headers = @ENABLE_EXTERN_TEMPLATE_TRUE@ $(extra_string_inst_sources) sources = \ + floating_from_chars.cc \ fs_dir.cc \ fs_ops.cc \ fs_path.cc \ diff --git a/libstdc++-v3/src/c++17/floating_from_chars.cc b/libstdc++-v3/src/c++17/floating_from_chars.cc new file mode 100644 index 00000000000..45de2be283d --- /dev/null +++ b/libstdc++-v3/src/c++17/floating_from_chars.cc @@ -0,0 +1,422 @@ +// std::from_chars implementation for floating-point types -*- C++ -*- + +// Copyright (C) 2020 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// Under Section 7 of GPL version 3, you are granted additional +// permissions described in the GCC Runtime Library Exception, version +// 3.1, as published by the Free Software Foundation. + +// You should have received a copy of the GNU General Public License and +// a copy of the GCC Runtime Library Exception along with this program; +// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +// . + +// +// ISO C++ 14882:2017 +// 23.2.9 Primitive numeric input conversion [utility.from.chars] +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if _GLIBCXX_HAVE_XLOCALE_H +# include +#endif + +#if _GLIBCXX_HAVE_USELOCALE +namespace std _GLIBCXX_VISIBILITY(default) +{ +_GLIBCXX_BEGIN_NAMESPACE_VERSION + +namespace +{ + // A memory resource with a static buffer that can be used for small + // allocations. At most one allocation using the freestore can be done + // if the static buffer is insufficient. The callers below only require + // a single allocation, so there's no need for anything more complex. + struct buffer_resource : pmr::memory_resource + { + ~buffer_resource() { if (m_ptr) operator delete(m_ptr, m_bytes); } + + void* + do_allocate(size_t bytes, size_t alignment [[maybe_unused]]) override + { + // Allocate from the buffer if it will fit. + if (m_bytes < sizeof(m_buf) && (m_bytes + bytes) <= sizeof(m_buf)) + return m_buf + std::__exchange(m_bytes, m_bytes + bytes); + + __glibcxx_assert(m_ptr == nullptr); + __glibcxx_assert(alignment != 1); + + m_ptr = operator new(bytes); + m_bytes = bytes; + return m_ptr; + } + + void + do_deallocate(void*, size_t, size_t) noexcept override + { /* like pmr::monotonic_buffer_resource, do nothing here */ } + + bool + do_is_equal(const pmr::memory_resource& other) const noexcept override + { return &other == this; } + + static constexpr int guaranteed_capacity() { return sizeof(m_buf); } + + private: + char m_buf[512]; + size_t m_bytes = 0; + void* m_ptr = nullptr; + }; + + inline bool valid_fmt(chars_format fmt) + { + return fmt != chars_format{} + && ((fmt & chars_format::general) == fmt + || (fmt & chars_format::hex) == fmt); + } + + constexpr char hex_digits[] = "abcdefABCDEF0123456789"; + constexpr auto dec_digits = hex_digits + 12; + + // Find initial portion of [first, last) containing a floating-point number. + // The string `digits` is either `dec_digits` or `hex_digits` + // and `exp` is 'e' or 'p' or '\0'. + const char* + find_end_of_float(const char* first, const char* last, const char* digits, + char exp) + { + while (first < last && strchr(digits, *first) != nullptr) + ++first; + if (first < last && *first == '.') + { + ++first; + while (first < last && strchr(digits, *first)) + ++first; + } + if (first < last && exp != 0 && std::tolower((unsigned char)*first) == exp) + { + ++first; + if (first < last && (*first == '-' || *first == '+')) + ++first; + while (first < last && strchr(dec_digits, *first) != nullptr) + ++first; + } + return first; + } + + // Determine the prefix of [first, last) that matches the pattern + // corresponding to `fmt`. + // Returns a NTBS containing the pattern, using `buf` to allocate + // additional storage if needed. + // Returns a nullptr if a valid pattern is not present. + const char* + pattern(const char* const first, const char* last, + chars_format& fmt, pmr::string& buf) + { + // fmt has the value of one of the enumerators of chars_format. + __glibcxx_assert(valid_fmt(fmt)); + + string_view res; + + if (first == last || *first == '+') [[unlikely]] + return nullptr; + + const int neg = (*first == '-'); + + if (std::memchr("iInN", (unsigned char)first[neg], 4)) + { + ptrdiff_t len = last - first; + if (len < (3 + neg)) + return nullptr; + + // possible infinity or NaN, let strtod decide + if (first[neg] == 'i' || first[neg] == 'I') + { + // Need at most 9 chars for "-INFINITY", ignore anything after it. + len = std::min(len, ptrdiff_t(neg + 8)); + } + else if (len > (neg + 3) && first[neg + 3] == '(') + { + // Look for end of "NAN(n-char-sequence)" + if (void* p = std::memchr(const_cast(first)+4, ')', len-4)) + len = static_cast(p) + 1 - first; +#ifndef __cpp_exceptions + if (len > buffer_resource::guaranteed_capacity()) + { + // The character sequence is too large for the buffer. + // Allocation failure could terminate the process, + // so just return an error via the fmt parameter. + fmt = chars_format{}; + return nullptr; + } +#endif + } + else // Only need 4 chars for "-NAN" + len = neg + 3; + + buf.assign(first, 0, len); + // prevent make_result correcting for "0x" + fmt = chars_format::general; + return buf.c_str(); + } + + const char* digits; + char* ptr; + + // Assign [first,last) to a std::string to get a NTBS that can be used + // with strspn, strtod etc. + // If the string would be longer than the fixed buffer inside the + // buffer_resource type use find_end_of_float to try to reduce how + // much memory is needed, to reduce the chance of std::bad_alloc. + + if (fmt == chars_format::hex) + { + digits = hex_digits; + + if ((last - first + 2) > buffer_resource::guaranteed_capacity()) + { + last = find_end_of_float(first + neg, last, digits, 'p'); +#ifndef __cpp_exceptions + if ((last - first + 2) > buffer_resource::guaranteed_capacity()) + { + // The character sequence is still too large for the buffer. + // Allocation failure could terminate the process, + // so just return an error via the fmt parameter. + fmt = chars_format{}; + return nullptr; + } +#endif + } + + buf = "-0x" + !neg; + buf.append(first + neg, last); + ptr = buf.data() + neg + 2; + } + else + { + digits = dec_digits; + + if ((last - first) > buffer_resource::guaranteed_capacity()) + { + last = find_end_of_float(first + neg, last, digits, + "e"[fmt == chars_format::fixed]); +#ifndef __cpp_exceptions + if ((last - first) > buffer_resource::guaranteed_capacity()) + { + // The character sequence is still too large for the buffer. + // Allocation failure could terminate the process, + // so just return an error via the fmt parameter. + fmt = chars_format{}; + return nullptr; + } +#endif + } + buf.assign(first, last); + ptr = buf.data() + neg; + } + + // "A non-empty sequence of decimal digits" or + // "A non-empty sequence of hexadecimal digits" + size_t len = std::strspn(ptr, digits); + // "possibly containing a radix character," + if (ptr[len] == '.') + { + const size_t len2 = std::strspn(ptr + len + 1, digits); + if (len + len2) + ptr += len + 1 + len2; + else + return nullptr; + } + else if (len == 0) [[unlikely]] + return nullptr; + else + ptr += len; + + if (fmt == chars_format::fixed) + { + // Truncate the string to stop strtod parsing past this point. + *ptr = '\0'; + } + else if (fmt == chars_format::scientific) + { + // Check for required exponent part which starts with 'e' or 'E' + if (*ptr != 'e' && *ptr != 'E') + return nullptr; + // then an optional plus or minus sign + const int sign = (ptr[1] == '-' || ptr[1] == '+'); + // then a nonempty sequence of decimal digits + if (!std::memchr(dec_digits, (unsigned char)ptr[1+sign], 10)) + return nullptr; + } + else if (fmt == chars_format::general) + { + if (*ptr == 'x' || *ptr == 'X') + *ptr = '\0'; + } + + return buf.c_str(); + } + + // Convert the NTBS `str` to a floating-point value of type `T`. + // If `str` cannot be converted, `value` is unchanged and `0` is returned. + // Otherwise, let N be the number of characters consumed from `str`. + // On success `value` is set to the converted value and N is returned. + // If the converted value is out of range, `value` is unchanged and + // -N is returned. + template + ptrdiff_t + from_chars_impl(const char* str, T& value, errc& ec) noexcept + { + if (locale_t loc = ::newlocale(LC_ALL, "C", (locale_t)0)) [[likely]] + { + locale_t orig = ::uselocale(loc); + + const int save_errno = errno; + errno = 0; + char* endptr; + T tmpval; + if constexpr (is_same_v) + tmpval = std::strtof(str, &endptr); + if constexpr (is_same_v) + tmpval = std::strtod(str, &endptr); + else if constexpr (is_same_v) + tmpval = std::strtold(str, &endptr); + const int conv_errno = std::__exchange(errno, save_errno); + + ::uselocale(orig); + ::freelocale(loc); + + const ptrdiff_t n = endptr - str; + if (conv_errno == ERANGE) [[unlikely]] + { + if (std::isinf(tmpval)) // overflow + ec = errc::result_out_of_range; + else // underflow (LWG 3081 wants to set value = tmpval here) + ec = errc::result_out_of_range; + } + else if (n) + { + value = tmpval; + ec = errc(); + } + return n; + } + else if (errno == ENOMEM) + ec = errc::not_enough_memory; + + return 0; + } + + inline from_chars_result + make_result(const char* str, ptrdiff_t n, chars_format fmt, errc ec) noexcept + { + from_chars_result result = { str, ec }; + if (n != 0) + { + if (fmt == chars_format::hex) + n -= 2; // correct for the "0x" inserted into the pattern + result.ptr += n; + } + else if (fmt == chars_format{}) [[unlikely]] + { + // FIXME: the standard does not allow this result. + ec = errc::not_enough_memory; + } + return result; + } + +} // namespace + +// FIXME: This should be reimplemented so it doesn't use strtod and newlocale. +// That will avoid the need for any memory allocation, meaning that the +// non-conforming errc::not_enough_memory result cannot happen. + +from_chars_result +from_chars(const char* first, const char* last, float& value, + chars_format fmt) noexcept +{ + buffer_resource mr; + pmr::string buf(&mr); + size_t len = 0; + errc ec = errc::invalid_argument; + __try + { + if (const char* pat = pattern(first, last, fmt, buf)) [[likely]] + len = from_chars_impl(pat, value, ec); + } + __catch (const std::bad_alloc&) + { + fmt = chars_format{}; + } + return make_result(first, len, fmt, ec); +} + +from_chars_result +from_chars(const char* first, const char* last, double& value, + chars_format fmt) noexcept +{ + buffer_resource mr; + pmr::string buf(&mr); + size_t len = 0; + errc ec = errc::invalid_argument; + __try + { + if (const char* pat = pattern(first, last, fmt, buf)) [[likely]] + len = from_chars_impl(pat, value, ec); + } + __catch (const std::bad_alloc&) + { + fmt = chars_format{}; + } + return make_result(first, len, fmt, ec); +} + +from_chars_result +from_chars(const char* first, const char* last, long double& value, + chars_format fmt) noexcept +{ + buffer_resource mr; + pmr::string buf(&mr); + size_t len = 0; + errc ec = errc::invalid_argument; + __try + { + if (const char* pat = pattern(first, last, fmt, buf)) [[likely]] + len = from_chars_impl(pat, value, ec); + } + __catch (const std::bad_alloc&) + { + fmt = chars_format{}; + } + return make_result(first, len, fmt, ec); +} + +#ifdef _GLIBCXX_LONG_DOUBLE_COMPAT +extern "C" from_chars_result +_ZSt10from_charsPKcS0_ReSt12chars_format(const char* first, const char* last, + long double& value, + chars_format fmt) noexcept +__attribute__((alias ("_ZSt10from_charsPKcS0_RdSt12chars_format"))); +#endif + +_GLIBCXX_END_NAMESPACE_VERSION +} // namespace std +#endif // _GLIBCXX_HAVE_USELOCALE diff --git a/libstdc++-v3/testsuite/20_util/from_chars/1_c++20_neg.cc b/libstdc++-v3/testsuite/20_util/from_chars/1_c++20_neg.cc index 8454b304d13..b3ca6525681 100644 --- a/libstdc++-v3/testsuite/20_util/from_chars/1_c++20_neg.cc +++ b/libstdc++-v3/testsuite/20_util/from_chars/1_c++20_neg.cc @@ -41,3 +41,4 @@ test01(const char* first, const char* last) } // { dg-prune-output "enable_if" } +// { dg-prune-output "cannot bind non-const lvalue reference" } diff --git a/libstdc++-v3/testsuite/20_util/from_chars/1_neg.cc b/libstdc++-v3/testsuite/20_util/from_chars/1_neg.cc index 12b5e597b9f..0d2fe2b3e65 100644 --- a/libstdc++-v3/testsuite/20_util/from_chars/1_neg.cc +++ b/libstdc++-v3/testsuite/20_util/from_chars/1_neg.cc @@ -44,3 +44,4 @@ test01(const char* first, const char* last) } // { dg-prune-output "enable_if" } +// { dg-prune-output "cannot bind non-const lvalue reference" } diff --git a/libstdc++-v3/testsuite/20_util/from_chars/2.cc b/libstdc++-v3/testsuite/20_util/from_chars/2.cc index 902092fd423..e5e2db82c3f 100644 --- a/libstdc++-v3/testsuite/20_util/from_chars/2.cc +++ b/libstdc++-v3/testsuite/20_util/from_chars/2.cc @@ -55,6 +55,12 @@ test01() VERIFY( r.ptr == s.data() ); VERIFY( i == 999 ); + s = "+1"; + r = std::from_chars(s.data(), s.data() + s.length(), i); + VERIFY( r.ec == std::errc::invalid_argument ); + VERIFY( r.ptr == s.data() ); + VERIFY( i == 999 ); + unsigned u = 888; s = "-1"; r = std::from_chars(s.data(), s.data() + s.length(), u); @@ -69,6 +75,11 @@ test01() VERIFY( r.ec == std::errc::invalid_argument ); VERIFY( r.ptr == s.data() ); VERIFY( u == 888 ); + s = "+1"; + r = std::from_chars(s.data(), s.data() + s.length(), u); + VERIFY( r.ec == std::errc::invalid_argument ); + VERIFY( r.ptr == s.data() ); + VERIFY( u == 888 ); for (int base = 2; base <= 36; ++base) { diff --git a/libstdc++-v3/testsuite/20_util/from_chars/4.cc b/libstdc++-v3/testsuite/20_util/from_chars/4.cc new file mode 100644 index 00000000000..6d692592e95 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/from_chars/4.cc @@ -0,0 +1,368 @@ +// Copyright (C) 2020 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// . + +// is supported in C++14 as a GNU extension +// { dg-do run { target c++14 } } + +#include +#include +#include +#include +#include +#include + +// Test std::from_chars floating-point conversions. + +void +test01() +{ + std::string s; + double d; + std::from_chars_result res; + + for (auto fmt : { std::chars_format::fixed, std::chars_format::scientific, + std::chars_format::general, std::chars_format::hex }) + { + s = "Info"; + res = std::from_chars(s.data(), s.data() + s.length(), d, fmt); + VERIFY( std::isinf(d) ); + VERIFY( res.ptr == s.data() + 3 ); + VERIFY( res.ec == std::errc{} ); + + s = "-INFIN"; + res = std::from_chars(s.data(), s.data() + s.length(), d, fmt); + VERIFY( std::isinf(d) ); + VERIFY( d < 0 ); + VERIFY( res.ptr == s.data() + 4 ); + VERIFY( res.ec == std::errc{} ); + + s = "InFiNiTy aNd BeYoNd"; + res = std::from_chars(s.data(), s.data() + s.length(), d, fmt); + VERIFY( std::isinf(d) ); + VERIFY( res.ptr == s.data() + 8 ); + VERIFY( res.ec == std::errc{} ); + + s = "nAn"; + res = std::from_chars(s.data(), s.data() + s.length(), d, fmt); + VERIFY( std::isnan(d) ); + VERIFY( res.ptr == s.data() + 3 ); + VERIFY( res.ec == std::errc{} ); + + s = "-NAN()"; + res = std::from_chars(s.data(), s.data() + s.length(), d, fmt); + VERIFY( std::isnan(d) ); + VERIFY( res.ptr == s.data() + s.length() ); + VERIFY( res.ec == std::errc{} ); + } +} + +void +test02() +{ + std::string s; + double d = 1.0; + std::from_chars_result res; + + s = "0x123"; + res = std::from_chars(s.data(), s.data() + s.length(), d); + VERIFY( d == 0.0 ); + VERIFY( res.ptr == s.data() + 1 ); + VERIFY( res.ec == std::errc{} ); + + d = 1.0; + res = std::from_chars(s.data(), s.data() + s.length(), d, + std::chars_format::fixed); + VERIFY( d == 0.0 ); + VERIFY( res.ptr == s.data() + 1 ); + VERIFY( res.ec == std::errc{} ); + + d = 1.0; + res = std::from_chars(s.data(), s.data() + s.length(), d, + std::chars_format::scientific); + VERIFY( d == 1.0 ); + VERIFY( res.ptr == s.data() ); + VERIFY( res.ec == std::errc::invalid_argument ); + + d = 1.0; + res = std::from_chars(s.data(), s.data() + s.length(), d, + std::chars_format::general); + VERIFY( d == 0.0 ); + VERIFY( res.ptr == s.data() + 1 ); + VERIFY( res.ec == std::errc{} ); + + d = 1.0; + res = std::from_chars(s.data(), s.data() + s.length(), d, + std::chars_format::hex); + VERIFY( d == 0.0 ); + VERIFY( res.ptr == s.data() + 1 ); + VERIFY( res.ec == std::errc{} ); +} + +void +test03() +{ + std::string s; + double d = 1.0; + std::from_chars_result res; + + s = "0.5e+2azzz"; + res = std::from_chars(s.data(), s.data() + s.length(), d); + VERIFY( d == 0.5e+2 ); + VERIFY( res.ptr == s.data() + s.length() - 1 - 3 ); + VERIFY( res.ec == std::errc{} ); + + res = std::from_chars(s.data(), s.data() + s.length(), d, + std::chars_format::fixed); + VERIFY( d == 0.5 ); + VERIFY( res.ptr == s.data() + 3 ); + VERIFY( res.ec == std::errc{} ); + + d = 1.0; + res = std::from_chars(s.data(), s.data() + s.length(), d, + std::chars_format::scientific); + VERIFY( d == 0.5e+2 ); + VERIFY( res.ptr == s.data() + s.length() - 1 - 3 ); + VERIFY( res.ec == std::errc{} ); + + d = 1.0; + res = std::from_chars(s.data(), s.data() + s.length(), d, + std::chars_format::general); + VERIFY( d == 0.5e+2 ); + VERIFY( res.ptr == s.data() + s.length() - 1 - 3 ); + VERIFY( res.ec == std::errc{} ); + + d = 1.0; + res = std::from_chars(s.data(), s.data() + s.length(), d, + std::chars_format::hex); + VERIFY( d == 0x0.5Ep0 ); + VERIFY( res.ptr == s.data() + 4 ); + VERIFY( res.ec == std::errc{} ); + + s = "1.Ap-2zzz"; + res = std::from_chars(s.data(), s.data() + s.length(), d, + std::chars_format::hex); + VERIFY( d == 0.40625 ); + VERIFY( res.ptr == s.data() + s.length() - 3 ); + VERIFY( res.ec == std::errc{} ); +} + +void +test04() +{ + // Huge input strings + std::string s(1000, '0'); + double d = 1.0; + std::from_chars_result res; + res = std::from_chars(s.data(), s.data() + s.length(), d); + VERIFY( res.ptr == s.data() + s.length() ); + VERIFY( res.ec == std::errc{} ); + VERIFY( d == 0.0 ); + + s += ".5"; + res = std::from_chars(s.data(), s.data() + s.length(), d); + VERIFY( res.ptr == s.data() + s.length() ); + VERIFY( res.ec == std::errc{} ); + VERIFY( d == 0.5 ); + + s += "e2"; + auto len = s.length(); + s += std::string(1000, 'a'); + res = std::from_chars(s.data(), s.data() + s.length(), d); + VERIFY( res.ptr == s.data() + len ); + VERIFY( res.ec == std::errc{} ); + VERIFY( d == 50 ); +} + +using std::to_string; + +#ifdef __GLIBCXX_TYPE_INT_N_0 +std::string +to_string(unsigned __GLIBCXX_TYPE_INT_N_0 val) +{ + using Limits = std::numeric_limits; + std::string s(Limits::digits10+2, '0'); + for (auto iter = s.end(); val != 0; val /= 10) + *--iter = '0' + (val % 10); + return s; +} +#endif + +void +test05() +{ + std::from_chars_result res; + float flt; + double dbl; + long double ldbl; + + // Small integer values that are exactly representable + + for (int i = 0; i < 100; ++i) + { + std::string s = to_string(i); + int len = s.length(); + s += "123"; + const char* s1 = s.c_str(); + const char* s1_end = s1 + len; + + for (auto fmt : { std::chars_format::fixed, + std::chars_format::general, + std::chars_format::hex }) + { + if (fmt == std::chars_format::hex && i > 9) + continue; + + res = std::from_chars(s1, s1_end, flt, fmt); + VERIFY( res.ec == std::errc{} ); + VERIFY( res.ptr == s1_end ); + VERIFY( flt == i ); + + res = std::from_chars(s1, s1_end, dbl, fmt); + VERIFY( res.ec == std::errc{} ); + VERIFY( res.ptr == s1_end ); + VERIFY( dbl == i ); + + res = std::from_chars(s1, s1_end, ldbl, fmt); + VERIFY( res.ec == std::errc{} ); + VERIFY( res.ptr == s1_end ); + VERIFY( ldbl == i ); + } + + if (i > 9) + continue; + + // Test single-digit integers with small exponents. + + const char s2[] = { '.', *s1, 'e', '0', '0', '0', '1' }; + const char* s2_end = s2 + sizeof(s2); + + const char s3[] = { *s1, '0', 'e', '-', '0', '0', '1' }; + const char* s3_end = s3 + sizeof(s3); + + for (auto fmt : { std::chars_format::scientific, + std::chars_format::general }) + { + res = std::from_chars(s2, s2_end, flt, fmt); + VERIFY( res.ec == std::errc{} ); + VERIFY( res.ptr == s2_end ); + VERIFY( flt == i ); + + res = std::from_chars(s3, s3_end, flt, fmt); + VERIFY( res.ec == std::errc{} ); + VERIFY( res.ptr == s3_end ); + VERIFY( flt == i ); + + res = std::from_chars(s2, s2_end, dbl, fmt); + VERIFY( res.ec == std::errc{} ); + VERIFY( res.ptr == s2_end ); + VERIFY( dbl == i ); + + res = std::from_chars(s3, s3_end, dbl, fmt); + VERIFY( res.ec == std::errc{} ); + VERIFY( res.ptr == s3_end ); + VERIFY( dbl == i ); + + res = std::from_chars(s2, s2_end, ldbl, fmt); + VERIFY( res.ec == std::errc{} ); + VERIFY( res.ptr == s2_end ); + VERIFY( ldbl == i ); + + res = std::from_chars(s3, s3_end, ldbl, fmt); + VERIFY( res.ec == std::errc{} ); + VERIFY( res.ptr == s3_end ); + VERIFY( ldbl == i ); + } + } +} + +template +void +test_max_mantissa() +{ + using Float_limits = std::numeric_limits; + using UInt_limits = std::numeric_limits; + + if constexpr (Float_limits::is_iec559 + && Float_limits::digits < UInt_limits::digits) + { + std::printf("Testing %d-bit float, using %zu-bit integer\n", + Float_limits::digits + (int)std::log2(Float_limits::max_exponent) + 1, + sizeof(UIntT) * __CHAR_BIT__); + + std::from_chars_result res; + FloatT flt; + + for (int i = 0; i < 10; ++i) + { + // (1 << digits) - 1 is the maximum value of the mantissa + const auto val = ((UIntT)1 << Float_limits::digits) - 1 - i; + std::string s = to_string(val); + auto len = s.length(); + s += "000"; // these should be ignored + for (auto fmt : { std::chars_format::fixed, + std::chars_format::general }) + { + res = std::from_chars(s.data(), s.data() + len, flt, fmt); + VERIFY( res.ec == std::errc{} ); + VERIFY( res.ptr == s.data() + len ); + VERIFY( flt == val ); + } + s.resize(len); + const auto orig_len = len; + s += "e+000"; + len = s.length(); + s += "111"; + for (auto fmt : { std::chars_format::scientific, + std::chars_format::general }) + { + res = std::from_chars(s.data(), s.data() + len, flt, fmt); + VERIFY( res.ec == std::errc{} ); + VERIFY( res.ptr == s.data() + len ); + VERIFY( flt == val ); + + std::string s2 = s.substr(0, len - 5); + s2.insert(s2.cbegin() + orig_len - 1, '.'); + s2 += "e000000000001"; + res = std::from_chars(s.data(), s.data() + len, flt, fmt); + VERIFY( res.ec == std::errc{} ); + VERIFY( res.ptr == s.data() + len ); + VERIFY( flt == val ); + } + } + } +} + +void +test06() +{ + test_max_mantissa(); + test_max_mantissa(); +#ifdef __GLIBCXX_TYPE_INT_N_0 + test_max_mantissa(); +#endif +} + +int +main() +{ + test01(); + test02(); + test03(); + test04(); + test05(); + test06(); +} diff --git a/libstdc++-v3/testsuite/20_util/from_chars/5.cc b/libstdc++-v3/testsuite/20_util/from_chars/5.cc new file mode 100644 index 00000000000..f8fc7f6cd12 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/from_chars/5.cc @@ -0,0 +1,163 @@ +// Copyright (C) 2020 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// . + +// is supported in C++14 as a GNU extension +// { dg-do run { target c++14 } } + +#include +#include +#include +#include + +// Test std::from_chars error handling. + +void +test01() +{ + std::from_chars_result r; + double d = 3.2; + std::string s; + + for (auto p : { "", "*", ".", "-", "-*", "-.", "+", "+.", "+-", "-+", "+1", + ".p1", "-.p1", + "in", "inch", "+inf", "na", "nam", "+nan" }) + { + s = p; + for (auto fmt : { std::chars_format::fixed, std::chars_format::scientific, + std::chars_format::general, std::chars_format::hex }) + { + r = std::from_chars(s.data(), s.data() + s.length(), d, fmt); + VERIFY( r.ec == std::errc::invalid_argument ); + VERIFY( r.ptr == s.data() ); + VERIFY( d == 3.2 ); + } + } + + for (auto p : { ".e1", "-.e1" }) // These are valid patterns for hex format + { + s = p; + for (auto fmt : { std::chars_format::fixed, std::chars_format::scientific, + std::chars_format::general }) + { + r = std::from_chars(s.data(), s.data() + s.length(), d, fmt); + VERIFY( r.ec == std::errc::invalid_argument ); + VERIFY( r.ptr == s.data() ); + VERIFY( d == 3.2 ); + } + } + + // scientific format requires an exponent + for (auto p : { "1.2", "-1.2", "1.2e", "-1.2e", "1.2e-", "-1.2e+" }) + { + s = p; + r = std::from_chars(s.data(), s.data() + s.length(), d, + std::chars_format::scientific); + VERIFY( r.ec == std::errc::invalid_argument ); + VERIFY( r.ptr == s.data() ); + VERIFY( d == 3.2 ); + } + + // patterns that are invalid without the final character + for (auto p : { "1", ".1", "-1", "-.1", + "inf", "-inf", "nan", "-nan" }) + { + s = p; + for (auto fmt : { std::chars_format::fixed, std::chars_format::scientific, + std::chars_format::general, std::chars_format::hex }) + { + r = std::from_chars(s.data(), s.data() + s.length() - 1, d, fmt); + VERIFY( r.ec == std::errc::invalid_argument ); + VERIFY( r.ptr == s.data() ); + VERIFY( d == 3.2 ); + } + } +} + +void +test02() +{ + std::from_chars_result r; + std::string s; + + float f = 0.5; + // Overflow + s = "99999999999999999e999999999999999999"; + r = std::from_chars(s.data(), s.data() + s.length(), f); + VERIFY( r.ec == std::errc::result_out_of_range ); + VERIFY( r.ptr == s.data() + s.length() ); + VERIFY( f == 0.5 ); + + s += '*'; + r = std::from_chars(s.data(), s.data() + s.length(), f); + VERIFY( r.ec == std::errc::result_out_of_range ); + VERIFY( r.ptr == s.data() + s.length() - 1 ); + VERIFY( f == 0.5 ); + + s.insert(s.begin(), '-'); + r = std::from_chars(s.data(), s.data() + s.length(), f); + VERIFY( r.ec == std::errc::result_out_of_range ); + VERIFY( r.ptr == s.data() + s.length() - 1 ); + VERIFY( f == 0.5 ); +} + +void +test03() +{ + double d = 0.5; + // Underflow + std::string s("-1.2345e-9999zzz"); + std::from_chars_result res; + res = std::from_chars(s.data(), s.data() + s.length(), d); + VERIFY( res.ptr == s.data() + s.length() - 3 ); + VERIFY( res.ec == std::errc::result_out_of_range ); + VERIFY( d == 0.5 ); + + res = std::from_chars(s.data() + 1, s.data() + s.length(), d); + VERIFY( res.ptr == s.data() + s.length() - 3 ); + VERIFY( res.ec == std::errc::result_out_of_range ); + VERIFY( d == 0.5 ); +} + +void +test04() +{ + std::from_chars_result res; + std::string z(2000, '0'); + // Invalid inputs for scientific format + for (const char* s : { "", "1", ".", ".0", ".5", "1e+", "1e+-1" }) + { + for (auto len : { 0, 10, 100, 1000, 2000 }) + { + auto str = z.substr(len) + s; + double d = 99.0; + res = std::from_chars(str.data(), str.data() + str.length(), d, + std::chars_format::scientific); + VERIFY( res.ec == std::errc::invalid_argument ); + VERIFY( res.ptr == str.data() ); + VERIFY( d == 99.0 ); + } + } +} + +int +main() +{ + test01(); + test02(); + test03(); + test04(); +} diff --git a/libstdc++-v3/testsuite/util/testsuite_abi.cc b/libstdc++-v3/testsuite/util/testsuite_abi.cc index aedb6561ed4..fd8224b6789 100644 --- a/libstdc++-v3/testsuite/util/testsuite_abi.cc +++ b/libstdc++-v3/testsuite/util/testsuite_abi.cc @@ -209,6 +209,8 @@ check_version(symbol& test, bool added) known_versions.push_back("GLIBCXX_3.4.26"); known_versions.push_back("GLIBCXX_3.4.27"); known_versions.push_back("GLIBCXX_3.4.28"); + known_versions.push_back("GLIBCXX_3.4.29"); + known_versions.push_back("GLIBCXX_LDBL_3.4.29"); known_versions.push_back("CXXABI_1.3"); known_versions.push_back("CXXABI_LDBL_1.3"); known_versions.push_back("CXXABI_1.3.1"); @@ -240,7 +242,10 @@ check_version(symbol& test, bool added) test.version_status = symbol::incompatible; // Check that added symbols are added in the latest pre-release version. - bool latestp = (test.version_name == "GLIBCXX_3.4.28" + bool latestp = (test.version_name == "GLIBCXX_3.4.29" + // XXX remove next line when GLIBCXX_3.4.30 is added and baselines + // have been regenerated to include GLIBCXX_LDBL_3.4.29 symbols: + || test.version_name == "GLIBCXX_LDBL_3.4.29" || test.version_name == "CXXABI_1.3.12" || test.version_name == "CXXABI_FLOAT128" || test.version_name == "CXXABI_TM_1");