On Wed, 2 Nov 2022 at 09:26, Jakub Jelinek <ja...@redhat.com> wrote: > > Hi! > > The following patch adds std::{to,from}_chars support for std::float128_t > on glibc 2.26+ for {i?86,x86_64,ia64,powerpc64le}-linux. > When long double is already IEEE quad, previous changes already handle > it by using long double overloads in _Float128 overloads. > The powerpc64le case (with explicit or implicit -mabi=ibmlongdouble) > is handled by using the __float128/__ieee128 entrypoints which are > already in the library and used for -mabi=ieeelongdouble. > For i?86, x86_64 and ia64 this patch adds new library entrypoints, > mostly by enabling the code that was already there for powerpc64le-linux. > Those use __float128 or __ieee128, the patch uses _Float128 for the > exported overloads and internally as template parameter. While > powerpc64le-linux uses __sprintfieee128 and __strtoieee128, > for _Float128 the patch uses the glibc 2.26 strfromf128 and strtof128 > APIs. So that one can build gcc against older glibc and then compile > user programs on newer glibc, the patch uses weak references unless > gcc is compiled against glibc 2.26+. strfromf128 unfortunately can't > handle %.0Lf and %.*Le, %.*Lf, %.*Lg format strings sprintf/__sprintfieee128 > use, we need to remove the L from those and replace * with actually > directly printing the precision into the format string (i.e. it can > handle %.0f and %.27f (floating point type is implied from the function > name)).
Wow, nice work juggling all the pieces here. > Unlike the std::{,b}float16_t support, this one actually exports APIs > with std::float128_t aka _Float128 in the mangled name, because no > standard format is superset of it. On the other side, e.g. on i?86/x86_64 > it doesn't have restrictions like for _Float16/__bf16 which ISAs need > to be enabled in order to use it. > > The denorm_min case in the testcase is temporarily commented out because > of the ERANGE subnormal issue Patrick posted patch for. > > Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk? OK, thanks! > > 2022-11-02 Jakub Jelinek <ja...@redhat.com> > > * include/std/charconv (from_chars, to_chars): Add _Float128 > overfloads if _GLIBCXX_HAVE_FLOAT128_MATH is defined. > * config/abi/pre/gnu.ver (GLIBCXX_3.4.31): Export > _ZSt8to_charsPcS_DF128_, _ZSt8to_charsPcS_DF128_St12chars_format, > _ZSt8to_charsPcS_DF128_St12chars_formati and > _ZSt10from_charsPKcS0_RDF128_St12chars_format. > * src/c++17/floating_from_chars.cc (USE_STRTOF128_FOR_FROM_CHARS): > Define if needed. > (__strtof128): Declare. > (from_chars_impl): Handle _Float128. > (from_chars): New _Float128 overload if USE_STRTOF128_FOR_FROM_CHARS > is define. > * src/c++17/floating_to_chars.cc (__strfromf128): Declare. > (FLOAT128_TO_CHARS): Define even when _Float128 is supported and > wider than long double. > (F128_type): Use _Float128 for that case. > (floating_type_traits): Specialize for F128_type rather than > __float128. > (sprintf_ld): Add length argument. Handle _Float128. > (__floating_to_chars_shortest, __floating_to_chars_precision): > Pass length to sprintf_ld. > (to_chars): Add _Float128 overloads for the F128_type being > _Float128 cases. > * testsuite/20_util/to_chars/float128_c++23.cc: New test. > > --- libstdc++-v3/include/std/charconv.jj 2022-10-31 22:20:39.475072806 > +0100 > +++ libstdc++-v3/include/std/charconv 2022-11-01 16:48:50.693196228 +0100 > @@ -736,6 +736,27 @@ namespace __detail > __value = __val; > return __res; > } > +#elif defined(__STDCPP_FLOAT128_T__) && defined(_GLIBCXX_HAVE_FLOAT128_MATH) > +#ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT > + __extension__ from_chars_result > + from_chars(const char* __first, const char* __last, __ieee128& __value, > + chars_format __fmt = chars_format::general) noexcept; > + > + inline from_chars_result > + from_chars(const char* __first, const char* __last, _Float128& __value, > + chars_format __fmt = chars_format::general) noexcept > + { > + __extension__ __ieee128 __val; > + from_chars_result __res = from_chars(__first, __last, __val, __fmt); > + if (__res.ec == errc{}) > + __value = __val; > + return __res; > + } > +#else > + from_chars_result > + from_chars(const char* __first, const char* __last, _Float128& __value, > + chars_format __fmt = chars_format::general) noexcept; > +#endif > #endif > > #if defined(__STDCPP_BFLOAT16_T__) && > defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) \ > @@ -851,6 +872,46 @@ namespace __detail > return to_chars(__first, __last, static_cast<long double>(__value), > __fmt, > __precision); > } > +#elif defined(__STDCPP_FLOAT128_T__) && defined(_GLIBCXX_HAVE_FLOAT128_MATH) > +#ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT > + __extension__ to_chars_result > + to_chars(char* __first, char* __last, __float128 __value) noexcept; > + __extension__ to_chars_result > + to_chars(char* __first, char* __last, __float128 __value, > + chars_format __fmt) noexcept; > + __extension__ to_chars_result > + to_chars(char* __first, char* __last, __float128 __value, > + chars_format __fmt, int __precision) noexcept; > + > + inline to_chars_result > + to_chars(char* __first, char* __last, _Float128 __value) noexcept > + { > + __extension__ > + return to_chars(__first, __last, static_cast<__float128>(__value)); > + } > + inline to_chars_result > + to_chars(char* __first, char* __last, _Float128 __value, > + chars_format __fmt) noexcept > + { > + __extension__ > + return to_chars(__first, __last, static_cast<__float128>(__value), > __fmt); > + } > + inline to_chars_result > + to_chars(char* __first, char* __last, _Float128 __value, > + chars_format __fmt, int __precision) noexcept > + { > + __extension__ > + return to_chars(__first, __last, static_cast<__float128>(__value), __fmt, > + __precision); > + } > +#else > + to_chars_result to_chars(char* __first, char* __last, _Float128 __value) > + noexcept; > + to_chars_result to_chars(char* __first, char* __last, _Float128 __value, > + chars_format __fmt) noexcept; > + to_chars_result to_chars(char* __first, char* __last, _Float128 __value, > + chars_format __fmt, int __precision) noexcept; > +#endif > #endif > > #if defined(__STDCPP_BFLOAT16_T__) && > defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) > --- libstdc++-v3/config/abi/pre/gnu.ver.jj 2022-10-31 22:20:39.475072806 > +0100 > +++ libstdc++-v3/config/abi/pre/gnu.ver 2022-11-01 17:00:57.682346445 +0100 > @@ -2450,6 +2450,10 @@ GLIBCXX_3.4.31 { > _ZSt21__to_chars_bfloat16_tPcS_fSt12chars_format; > _ZSt22__from_chars_float16_tPKcS0_RfSt12chars_format; > _ZSt23__from_chars_bfloat16_tPKcS0_RfSt12chars_format; > + _ZSt8to_charsPcS_DF128_; > + _ZSt8to_charsPcS_DF128_St12chars_format; > + _ZSt8to_charsPcS_DF128_St12chars_formati; > + _ZSt10from_charsPKcS0_RDF128_St12chars_format; > } GLIBCXX_3.4.30; > > # Symbols in the support library (libsupc++) have their own tag. > --- libstdc++-v3/src/c++17/floating_from_chars.cc.jj 2022-10-31 > 22:20:39.476072793 +0100 > +++ libstdc++-v3/src/c++17/floating_from_chars.cc 2022-11-01 > 16:59:23.999615740 +0100 > @@ -59,6 +59,14 @@ > #endif > // strtold for __ieee128 > extern "C" __ieee128 __strtoieee128(const char*, char**); > +#elif __FLT128_MANT_DIG__ == 113 && __LDBL_MANT_DIG__ != 113 \ > + && defined(__GLIBC_PREREQ) > +#define USE_STRTOF128_FOR_FROM_CHARS 1 > +extern "C" _Float128 __strtof128(const char*, char**) > +#ifndef _GLIBCXX_HAVE_FLOAT128_MATH > + __attribute__((__weak__)) > +#endif > + __asm ("strtof128"); > #endif > > #if _GLIBCXX_FLOAT_IS_IEEE_BINARY32 && _GLIBCXX_DOUBLE_IS_IEEE_BINARY64 \ > @@ -618,6 +626,16 @@ namespace > # ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT > else if constexpr (is_same_v<T, __ieee128>) > tmpval = __strtoieee128(str, &endptr); > +# elif defined(USE_STRTOF128_FOR_FROM_CHARS) > + else if constexpr (is_same_v<T, _Float128>) > + { > +#ifndef _GLIBCXX_HAVE_FLOAT128_MATH > + if (&__strtof128 == nullptr) > + tmpval = _Float128(std::strtold(str, &endptr); > + else > +#endif > + tmpval = __strtof128(str, &endptr); > + } > # endif > #else > tmpval = std::strtod(str, &endptr); > @@ -1232,6 +1250,14 @@ from_chars(const char* first, const char > chars_format fmt) noexcept > { > // fast_float doesn't support IEEE binary128 format, but we can use > strtold. > + return from_chars_strtod(first, last, value, fmt); > +} > +#elif defined(USE_STRTOF128_FOR_FROM_CHARS) > +from_chars_result > +from_chars(const char* first, const char* last, _Float128& value, > + chars_format fmt) noexcept > +{ > + // fast_float doesn't support IEEE binary128 format, but we can use > strtold. > return from_chars_strtod(first, last, value, fmt); > } > #endif > --- libstdc++-v3/src/c++17/floating_to_chars.cc.jj 2022-11-01 > 12:16:14.352652455 +0100 > +++ libstdc++-v3/src/c++17/floating_to_chars.cc 2022-11-01 16:25:48.330968909 > +0100 > @@ -43,6 +43,13 @@ > #endif > // sprintf for __ieee128 > extern "C" int __sprintfieee128(char*, const char*, ...); > +#elif __FLT128_MANT_DIG__ == 113 && __LDBL_MANT_DIG__ != 113 \ > + && defined(__GLIBC_PREREQ) > +extern "C" int __strfromf128(char*, size_t, const char*, _Float128) > +#ifndef _GLIBCXX_HAVE_FLOAT128_MATH > + __attribute__((__weak__)) > +#endif > + __asm ("strfromf128"); > #endif > > // This implementation crucially assumes float/double have the > @@ -77,10 +84,11 @@ extern "C" int __sprintfieee128(char*, c > #if defined _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT && __FLT128_MANT_DIG__ == 113 > // Define overloads of std::to_chars for __float128. > # define FLOAT128_TO_CHARS 1 > -#endif > - > -#ifdef FLOAT128_TO_CHARS > using F128_type = __float128; > +#elif __FLT128_MANT_DIG__ == 113 && __LDBL_MANT_DIG__ != 113 \ > + && defined(__GLIBC_PREREQ) > +# define FLOAT128_TO_CHARS 1 > +using F128_type = _Float128; > #else > using F128_type = void; > #endif > @@ -252,7 +260,7 @@ namespace > > # ifdef FLOAT128_TO_CHARS > template<> > - struct floating_type_traits<__float128> : floating_type_traits_binary128 > + struct floating_type_traits<F128_type> : floating_type_traits_binary128 > { }; > # endif > #endif > @@ -1035,7 +1043,8 @@ namespace > #pragma GCC diagnostic ignored "-Wabi" > template<typename T, typename... Extra> > inline int > - sprintf_ld(char* buffer, const char* format_string, T value, Extra... args) > + sprintf_ld(char* buffer, size_t length __attribute__((unused)), > + const char* format_string, T value, Extra... args) > { > int len; > > @@ -1045,10 +1054,31 @@ namespace > fesetround(FE_TONEAREST); // We want round-to-nearest behavior. > #endif > > +#ifdef FLOAT128_TO_CHARS > #ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT > if constexpr (is_same_v<T, __ieee128>) > len = __sprintfieee128(buffer, format_string, args..., value); > else > +#else > + if constexpr (is_same_v<T, _Float128>) > + { > +#ifndef _GLIBCXX_HAVE_FLOAT128_MATH > + if (&__strfromf128 == nullptr) > + len = sprintf(buffer, format_string, args..., (long double)value); > + else > +#endif > + if constexpr (sizeof...(args) == 0) > + len = __strfromf128(buffer, length, "%.0f", value); > + else > + { > + // strfromf128 unfortunately doesn't allow .* > + char fmt[3 * sizeof(int) + 6]; > + sprintf(fmt, "%%.%d%c", args..., int(format_string[4])); > + len = __strfromf128(buffer, length, fmt, value); > + } > + } > + else > +#endif > #endif > len = sprintf(buffer, format_string, args..., value); > > @@ -1206,8 +1236,10 @@ template<typename T> > // can avoid this if we use sprintf to write all but the last > // digit, and carefully compute and write the last digit > // ourselves. > - char buffer[expected_output_length+1]; > - const int output_length = sprintf_ld(buffer, "%.0Lf", value); > + char buffer[expected_output_length + 1]; > + const int output_length = sprintf_ld(buffer, > + expected_output_length + 1, > + "%.0Lf", value); > __glibcxx_assert(output_length == expected_output_length); > memcpy(first, buffer, output_length); > return {first + output_length, errc{}}; > @@ -1397,9 +1429,10 @@ template<typename T> > __builtin_unreachable(); > > // Do the sprintf into the local buffer. > - char buffer[output_length_upper_bound+1]; > + char buffer[output_length_upper_bound + 1]; > int output_length > - = sprintf_ld(buffer, output_specifier, value, effective_precision); > + = sprintf_ld(buffer, output_length_upper_bound + 1, > output_specifier, > + value, effective_precision); > __glibcxx_assert(output_length <= output_length_upper_bound); > > if (effective_precision > 0) > @@ -1799,6 +1832,7 @@ to_chars(char* first, char* last, long d > } > > #ifdef FLOAT128_TO_CHARS > +#ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT > to_chars_result > to_chars(char* first, char* last, __float128 value) noexcept > { > @@ -1817,6 +1851,26 @@ to_chars(char* first, char* last, __floa > { > return __floating_to_chars_precision(first, last, value, fmt, precision); > } > +#else > +to_chars_result > +to_chars(char* first, char* last, _Float128 value) noexcept > +{ > + return __floating_to_chars_shortest(first, last, value, chars_format{}); > +} > + > +to_chars_result > +to_chars(char* first, char* last, _Float128 value, chars_format fmt) noexcept > +{ > + return __floating_to_chars_shortest(first, last, value, fmt); > +} > + > +to_chars_result > +to_chars(char* first, char* last, _Float128 value, chars_format fmt, > + int precision) noexcept > +{ > + return __floating_to_chars_precision(first, last, value, fmt, precision); > +} > +#endif > #endif > > // Entrypoints for 16-bit floats. > --- libstdc++-v3/testsuite/20_util/to_chars/float128_c++23.cc.jj > 2022-11-01 17:04:19.895606130 +0100 > +++ libstdc++-v3/testsuite/20_util/to_chars/float128_c++23.cc 2022-11-01 > 20:47:15.254989646 +0100 > @@ -0,0 +1,105 @@ > +// Copyright (C) 2022 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/>. > + > +// { dg-options "-std=gnu++2b" } > +// { dg-do run { target c++23 } } > +// { dg-require-effective-target ieee_floats } > +// { dg-require-effective-target size32plus } > +// { dg-add-options ieee } > + > +#include <charconv> > +#include <stdfloat> > +#include <limits> > +#include <numbers> > +#include <testsuite_hooks.h> > + > +#if defined(__STDCPP_FLOAT128_T__) \ > + && (defined(_GLIBCXX_LDOUBLE_IS_IEEE_BINARY128) \ > + || defined(_GLIBCXX_HAVE_FLOAT128_MATH)) > +void > +test(std::chars_format fmt = std::chars_format{}) > +{ > + std::float128_t tests[] = { > +// std::numeric_limits<std::float128_t>::denorm_min(), > + std::numeric_limits<std::float128_t>::min(), > + 0.0f128, > + -42.0f128, > + 1234.5678912345f128, > + std::numbers::e_v<std::float128_t>, > + std::numbers::log2e_v<std::float128_t>, > + std::numbers::log10e_v<std::float128_t>, > + std::numbers::pi_v<std::float128_t>, > + std::numbers::inv_pi_v<std::float128_t>, > + std::numbers::inv_sqrtpi_v<std::float128_t>, > + std::numbers::ln2_v<std::float128_t>, > + std::numbers::ln10_v<std::float128_t>, > + std::numbers::sqrt2_v<std::float128_t>, > + std::numbers::sqrt3_v<std::float128_t>, > + std::numbers::inv_sqrt3_v<std::float128_t>, > + std::numbers::egamma_v<std::float128_t>, > + std::numbers::phi_v<std::float128_t>, > + std::numeric_limits<std::float128_t>::max() > + }; > + char str1[10000], str2[10000]; > + for (auto u : tests) > + { > + auto [ptr1, ec1] = std::to_chars(str1, str1 + sizeof(str1), u, fmt); > + VERIFY( ec1 == std::errc() ); > +// std::cout << i << ' ' << std::string_view (str1, ptr1) << '\n'; > + if (fmt == std::chars_format::fixed) > + { > + auto [ptr2, ec2] = std::to_chars(str2, str2 + (ptr1 - str1), u, > fmt); > + VERIFY( ec2 == std::errc() && ptr2 - str2 == ptr1 - str1 ); > + auto [ptr3, ec3] = std::to_chars(str2, str2 + (ptr1 - str1 - 1), u, > fmt); > + VERIFY( ec3 != std::errc() ); > + } > + std::float128_t v; > + auto [ptr4, ec4] = std::from_chars(str1, ptr1, v, > + fmt == std::chars_format{} > + ? std::chars_format::general : fmt); > + VERIFY( ec4 == std::errc() && ptr4 == ptr1 ); > + VERIFY( u == v ); > + > + auto [ptr5, ec5] = std::to_chars(str1, str1 + sizeof(str1), u, fmt, > 90); > + VERIFY( ec5 == std::errc() ); > +// std::cout << i << ' ' << std::string_view (str1, ptr5) << '\n'; > + v = 4.0f128; > + auto [ptr6, ec6] = std::from_chars(str1, ptr5, v, > + fmt == std::chars_format{} > + ? std::chars_format::general : fmt); > + VERIFY( ec6 == std::errc() && ptr6 == ptr5 ); > + if (fmt == std::chars_format::fixed && u > 0.0f128 && u < 0.000001f128) > + VERIFY( v == 0.0 ); > + else > + VERIFY( u == v ); > + } > +} > +#endif > + > +int > +main() > +{ > +#if defined(__STDCPP_FLOAT128_T__) \ > + && (defined(_GLIBCXX_LDOUBLE_IS_IEEE_BINARY128) \ > + || defined(_GLIBCXX_HAVE_FLOAT128_MATH)) > + test(); > + test(std::chars_format::fixed); > + test(std::chars_format::scientific); > + test(std::chars_format::general); > + test(std::chars_format::hex); > +#endif > +} > > Jakub >