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 <xlocale.h> 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.
This commit is contained in:
Jonathan Wakely 2020-07-20 23:49:27 +01:00
parent e443d82138
commit 932fbc868a
17 changed files with 1083 additions and 5 deletions

View File

@ -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

View File

@ -526,6 +526,9 @@
/* Define to 1 if you have the <unistd.h> 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 <sys/uio.h>. */
#undef HAVE_WRITEV
/* Define to 1 if you have the <xlocale.h> header file. */
#undef HAVE_XLOCALE_H
/* Define to 1 if you have the `_acosf' function. */
#undef HAVE__ACOSF

View File

@ -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 {

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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*)

View File

@ -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

View File

@ -50,6 +50,7 @@ inst_sources =
endif
sources = \
floating_from_chars.cc \
fs_dir.cc \
fs_ops.cc \
fs_path.cc \

View File

@ -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 \

View File

@ -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
// <http://www.gnu.org/licenses/>.
//
// ISO C++ 14882:2017
// 23.2.9 Primitive numeric input conversion [utility.from.chars]
//
#include <charconv>
#include <string>
#include <memory_resource>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <locale.h>
#include <bits/functexcept.h>
#if _GLIBCXX_HAVE_XLOCALE_H
# include <xlocale.h>
#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<char*>(first)+4, ')', len-4))
len = static_cast<char*>(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<typename T>
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<T, float>)
tmpval = std::strtof(str, &endptr);
if constexpr (is_same_v<T, double>)
tmpval = std::strtod(str, &endptr);
else if constexpr (is_same_v<T, long double>)
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

View File

@ -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" }

View File

@ -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" }

View File

@ -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)
{

View File

@ -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
// <http://www.gnu.org/licenses/>.
// <charconv> is supported in C++14 as a GNU extension
// { dg-do run { target c++14 } }
#include <charconv>
#include <string>
#include <limits>
#include <cmath>
#include <cstdlib>
#include <testsuite_hooks.h>
// 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<unsigned __GLIBCXX_TYPE_INT_N_0>;
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<typename FloatT, typename UIntT>
void
test_max_mantissa()
{
using Float_limits = std::numeric_limits<FloatT>;
using UInt_limits = std::numeric_limits<UIntT>;
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<float, unsigned long>();
test_max_mantissa<double, unsigned long long>();
#ifdef __GLIBCXX_TYPE_INT_N_0
test_max_mantissa<long double, unsigned __GLIBCXX_TYPE_INT_N_0>();
#endif
}
int
main()
{
test01();
test02();
test03();
test04();
test05();
test06();
}

View File

@ -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
// <http://www.gnu.org/licenses/>.
// <charconv> is supported in C++14 as a GNU extension
// { dg-do run { target c++14 } }
#include <charconv>
#include <string>
#include <cmath>
#include <testsuite_hooks.h>
// 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();
}

View File

@ -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");