libstdc++: std::from_chars std::{,b}float16_t support
Checks
Commit Message
Hi!
On top of the
https://gcc.gnu.org/pipermail/libstdc++/2022-October/054849.html
https://gcc.gnu.org/pipermail/libstdc++/2022-October/054886.html
the following patch adds std::from_chars support, similarly to the
previous std::to_chars patch through APIs that use float instead of
the 16-bit floating point formats as container.
The patch uses the fast_float library and doesn't need any changes
to it, like the previous patch it introduces wrapper classes around
float that represent the float holding float16_t or bfloat16_t value,
and specializes binary_format etc. from fast_float for these classes.
The new test verifies exhaustively to_chars and from_chars afterward
results in the original value (except for nans) in all the fmt cases.
Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
2022-11-01 Jakub Jelinek <jakub@redhat.com>
* include/std/charconv (__from_chars_float16_t,
__from_chars_bfloat16_t): Declare.
(from_chars): Add _Float16 and __gnu_cxx::__bfloat16_t overloads.
* config/abi/pre/gnu.ver (GLIBCXX_3.4.31): Export
_ZSt22__from_chars_float16_tPKcS0_RfSt12chars_format and
_ZSt23__from_chars_bfloat16_tPKcS0_RfSt12chars_format.
* src/c++17/floating_from_chars.cc
(fast_float::floating_type_float16_t,
fast_float::floating_type_bfloat16_t): New classes.
(fast_float::binary_format<floating_type_float16_t>,
fast_float::binary_format<floating_type_bfloat16_t>): New
specializations.
(fast_float::to_float<floating_type_float16_t>,
fast_float::to_float<floating_type_bfloat16_t>,
fast_float::to_extended<floating_type_float16_t>,
fast_float::to_extended<floating_type_bfloat16_t>): Likewise.
(fast_float::from_chars_16): New template function.
(__floating_from_chars_hex): Allow instantiation with
fast_float::floating_type_{,b}float16_t.
(from_chars): Formatting fixes for float/double/long double overloads.
(__from_chars_float16_t, __from_chars_bfloat16_t): New functions.
* testsuite/20_util/to_chars/float16_c++23.cc: New test.
Jakub
Comments
On Tue, 1 Nov 2022 at 09:36, Jakub Jelinek <jakub@redhat.com> wrote:
>
> Hi!
>
> On top of the
> https://gcc.gnu.org/pipermail/libstdc++/2022-October/054849.html
> https://gcc.gnu.org/pipermail/libstdc++/2022-October/054886.html
> the following patch adds std::from_chars support, similarly to the
> previous std::to_chars patch through APIs that use float instead of
> the 16-bit floating point formats as container.
> The patch uses the fast_float library and doesn't need any changes
> to it, like the previous patch it introduces wrapper classes around
> float that represent the float holding float16_t or bfloat16_t value,
> and specializes binary_format etc. from fast_float for these classes.
>
> The new test verifies exhaustively to_chars and from_chars afterward
> results in the original value (except for nans) in all the fmt cases.
>
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
OK, thanks.
>
> 2022-11-01 Jakub Jelinek <jakub@redhat.com>
>
> * include/std/charconv (__from_chars_float16_t,
> __from_chars_bfloat16_t): Declare.
> (from_chars): Add _Float16 and __gnu_cxx::__bfloat16_t overloads.
> * config/abi/pre/gnu.ver (GLIBCXX_3.4.31): Export
> _ZSt22__from_chars_float16_tPKcS0_RfSt12chars_format and
> _ZSt23__from_chars_bfloat16_tPKcS0_RfSt12chars_format.
> * src/c++17/floating_from_chars.cc
> (fast_float::floating_type_float16_t,
> fast_float::floating_type_bfloat16_t): New classes.
> (fast_float::binary_format<floating_type_float16_t>,
> fast_float::binary_format<floating_type_bfloat16_t>): New
> specializations.
> (fast_float::to_float<floating_type_float16_t>,
> fast_float::to_float<floating_type_bfloat16_t>,
> fast_float::to_extended<floating_type_float16_t>,
> fast_float::to_extended<floating_type_bfloat16_t>): Likewise.
> (fast_float::from_chars_16): New template function.
> (__floating_from_chars_hex): Allow instantiation with
> fast_float::floating_type_{,b}float16_t.
> (from_chars): Formatting fixes for float/double/long double overloads.
> (__from_chars_float16_t, __from_chars_bfloat16_t): New functions.
> * testsuite/20_util/to_chars/float16_c++23.cc: New test.
>
> --- libstdc++-v3/include/std/charconv.jj 2022-10-28 11:15:40.113959052 +0200
> +++ libstdc++-v3/include/std/charconv 2022-10-28 11:28:04.172657801 +0200
> @@ -673,6 +673,32 @@ namespace __detail
> from_chars(const char* __first, const char* __last, long double& __value,
> chars_format __fmt = chars_format::general) noexcept;
>
> + // Library routines for 16-bit extended floating point formats
> + // using float as interchange format.
> + from_chars_result
> + __from_chars_float16_t(const char* __first, const char* __last,
> + float& __value,
> + chars_format __fmt = chars_format::general) noexcept;
> + from_chars_result
> + __from_chars_bfloat16_t(const char* __first, const char* __last,
> + float& __value,
> + chars_format __fmt = chars_format::general) noexcept;
> +
> +#if defined(__STDCPP_FLOAT16_T__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) \
> + && defined(__cpp_lib_to_chars)
> + inline from_chars_result
> + from_chars(const char* __first, const char* __last, _Float16& __value,
> + chars_format __fmt = chars_format::general) noexcept
> + {
> + float __val;
> + from_chars_result __res
> + = __from_chars_float16_t(__first, __last, __val, __fmt);
> + if (__res.ec == errc{})
> + __value = __val;
> + return __res;
> + }
> +#endif
> +
> #if defined(__STDCPP_FLOAT32_T__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32)
> inline from_chars_result
> from_chars(const char* __first, const char* __last, _Float32& __value,
> @@ -709,6 +735,22 @@ namespace __detail
> if (__res.ec == errc{})
> __value = __val;
> return __res;
> + }
> +#endif
> +
> +#if defined(__STDCPP_BFLOAT16_T__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) \
> + && defined(__cpp_lib_to_chars)
> + inline from_chars_result
> + from_chars(const char* __first, const char* __last,
> + __gnu_cxx::__bfloat16_t & __value,
> + chars_format __fmt = chars_format::general) noexcept
> + {
> + float __val;
> + from_chars_result __res
> + = __from_chars_bfloat16_t(__first, __last, __val, __fmt);
> + if (__res.ec == errc{})
> + __value = __val;
> + return __res;
> }
> #endif
> #endif
> --- libstdc++-v3/config/abi/pre/gnu.ver.jj 2022-10-28 11:15:40.115959024 +0200
> +++ libstdc++-v3/config/abi/pre/gnu.ver 2022-10-28 16:55:55.274849390 +0200
> @@ -2448,6 +2448,8 @@ GLIBCXX_3.4.31 {
> _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE15_M_replace_cold*;
> _ZSt20__to_chars_float16_tPcS_fSt12chars_format;
> _ZSt21__to_chars_bfloat16_tPcS_fSt12chars_format;
> + _ZSt22__from_chars_float16_tPKcS0_RfSt12chars_format;
> + _ZSt23__from_chars_bfloat16_tPKcS0_RfSt12chars_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-05-23 21:44:49.107846783 +0200
> +++ libstdc++-v3/src/c++17/floating_from_chars.cc 2022-10-31 15:06:30.338480517 +0100
> @@ -75,6 +75,272 @@ extern "C" __ieee128 __strtoieee128(cons
> namespace
> {
> # include "fast_float/fast_float.h"
> +
> +namespace fast_float
> +{
> +
> + // Wrappers around float for std::{,b}float16_t promoted to float.
> + struct floating_type_float16_t
> + {
> + float* x;
> + uint16_t bits;
> + };
> + struct floating_type_bfloat16_t
> + {
> + float* x;
> + uint16_t bits;
> + };
> +
> + template<>
> + constexpr int
> + binary_format<floating_type_float16_t>::mantissa_explicit_bits()
> + { return 10; }
> +
> + template<>
> + constexpr int
> + binary_format<floating_type_bfloat16_t>::mantissa_explicit_bits()
> + { return 7; }
> +
> + // 10 bits of stored mantissa, pow(5,q) <= 0x4p+10 implies q <= 5
> + template<>
> + constexpr int
> + binary_format<floating_type_float16_t>::max_exponent_round_to_even()
> + { return 5; }
> +
> + // 7 bits of stored mantissa, pow(5,q) <= 0x4p+7 implies q <= 3
> + template<>
> + constexpr int
> + binary_format<floating_type_bfloat16_t>::max_exponent_round_to_even()
> + { return 3; }
> +
> + // 10 bits of stored mantissa, pow(5,-q) < 0x1p+64 / 0x1p+11 implies q >= -22
> + template<>
> + constexpr int
> + binary_format<floating_type_float16_t>::min_exponent_round_to_even()
> + { return -22; }
> +
> + // 7 bits of stored mantissa, pow(5,-q) < 0x1p+64 / 0x1p+8 implies q >= -24
> + template<>
> + constexpr int
> + binary_format<floating_type_bfloat16_t>::min_exponent_round_to_even()
> + { return -24; }
> +
> + template<>
> + constexpr int
> + binary_format<floating_type_float16_t>::minimum_exponent()
> + { return -15; }
> +
> + template<>
> + constexpr int
> + binary_format<floating_type_bfloat16_t>::minimum_exponent()
> + { return -127; }
> +
> + template<>
> + constexpr int
> + binary_format<floating_type_float16_t>::infinite_power()
> + { return 0x1F; }
> +
> + template<>
> + constexpr int
> + binary_format<floating_type_bfloat16_t>::infinite_power()
> + { return 0xFF; }
> +
> + template<>
> + constexpr int
> + binary_format<floating_type_float16_t>::sign_index()
> + { return 15; }
> +
> + template<>
> + constexpr int
> + binary_format<floating_type_bfloat16_t>::sign_index()
> + { return 15; }
> +
> + template<>
> + constexpr int
> + binary_format<floating_type_float16_t>::largest_power_of_ten()
> + { return 4; }
> +
> + template<>
> + constexpr int
> + binary_format<floating_type_bfloat16_t>::largest_power_of_ten()
> + { return 38; }
> +
> + template<>
> + constexpr int
> + binary_format<floating_type_float16_t>::smallest_power_of_ten()
> + { return -27; }
> +
> + template<>
> + constexpr int
> + binary_format<floating_type_bfloat16_t>::smallest_power_of_ten()
> + { return -60; }
> +
> + template<>
> + constexpr size_t
> + binary_format<floating_type_float16_t>::max_digits()
> + { return 22; }
> +
> + template<>
> + constexpr size_t
> + binary_format<floating_type_bfloat16_t>::max_digits()
> + { return 98; }
> +
> + // negative_digit_comp converts adjusted_mantissa to the (originally only)
> + // floating type and immediately back with slight tweaks (e.g. explicit
> + // leading bit instead of implicit for normals).
> + // Avoid going through the floating point type.
> + template<>
> + fastfloat_really_inline void
> + to_float<floating_type_float16_t>(bool negative, adjusted_mantissa am,
> + floating_type_float16_t &value)
> + {
> + constexpr int mantissa_bits
> + = binary_format<floating_type_float16_t>::mantissa_explicit_bits();
> + value.bits = (am.mantissa
> + | (uint16_t(am.power2) << mantissa_bits)
> + | (negative ? 0x8000 : 0));
> + }
> +
> + template<>
> + fastfloat_really_inline void
> + to_float<floating_type_bfloat16_t>(bool negative, adjusted_mantissa am,
> + floating_type_bfloat16_t &value)
> + {
> + constexpr int mantissa_bits
> + = binary_format<floating_type_bfloat16_t>::mantissa_explicit_bits();
> + value.bits = (am.mantissa
> + | (uint16_t(am.power2) << mantissa_bits)
> + | (negative ? 0x8000 : 0));
> + }
> +
> + template <>
> + fastfloat_really_inline adjusted_mantissa
> + to_extended<floating_type_float16_t>(floating_type_float16_t value) noexcept
> + {
> + adjusted_mantissa am;
> + constexpr int mantissa_bits
> + = binary_format<floating_type_float16_t>::mantissa_explicit_bits();
> + int32_t bias
> + = (mantissa_bits
> + - binary_format<floating_type_float16_t>::minimum_exponent());
> + constexpr uint16_t exponent_mask = 0x7C00;
> + constexpr uint16_t mantissa_mask = 0x03FF;
> + constexpr uint16_t hidden_bit_mask = 0x0400;
> + if ((value.bits & exponent_mask) == 0) {
> + // denormal
> + am.power2 = 1 - bias;
> + am.mantissa = value.bits & mantissa_mask;
> + } else {
> + // normal
> + am.power2 = int32_t((value.bits & exponent_mask) >> mantissa_bits);
> + am.power2 -= bias;
> + am.mantissa = (value.bits & mantissa_mask) | hidden_bit_mask;
> + }
> + return am;
> + }
> +
> + template <>
> + fastfloat_really_inline adjusted_mantissa
> + to_extended<floating_type_bfloat16_t>(floating_type_bfloat16_t value) noexcept
> + {
> + adjusted_mantissa am;
> + constexpr int mantissa_bits
> + = binary_format<floating_type_bfloat16_t>::mantissa_explicit_bits();
> + int32_t bias
> + = (mantissa_bits
> + - binary_format<floating_type_bfloat16_t>::minimum_exponent());
> + constexpr uint16_t exponent_mask = 0x7F80;
> + constexpr uint16_t mantissa_mask = 0x007F;
> + constexpr uint16_t hidden_bit_mask = 0x0080;
> + if ((value.bits & exponent_mask) == 0) {
> + // denormal
> + am.power2 = 1 - bias;
> + am.mantissa = value.bits & mantissa_mask;
> + } else {
> + // normal
> + am.power2 = int32_t((value.bits & exponent_mask) >> mantissa_bits);
> + am.power2 -= bias;
> + am.mantissa = (value.bits & mantissa_mask) | hidden_bit_mask;
> + }
> + return am;
> + }
> +
> + // Like fast_float.h from_chars_advanced, but for 16-bit float.
> + template<typename T>
> + from_chars_result
> + from_chars_16(const char* first, const char* last, T &value,
> + chars_format fmt) noexcept
> + {
> + parse_options options{fmt};
> +
> + from_chars_result answer;
> + if (first == last)
> + {
> + answer.ec = std::errc::invalid_argument;
> + answer.ptr = first;
> + return answer;
> + }
> +
> + parsed_number_string pns = parse_number_string(first, last, options);
> + if (!pns.valid)
> + return detail::parse_infnan(first, last, *value.x);
> +
> + answer.ec = std::errc();
> + answer.ptr = pns.lastmatch;
> +
> + adjusted_mantissa am
> + = compute_float<binary_format<T>>(pns.exponent, pns.mantissa);
> + if (pns.too_many_digits && am.power2 >= 0)
> + {
> + if (am != compute_float<binary_format<T>>(pns.exponent,
> + pns.mantissa + 1))
> + am = compute_error<binary_format<T>>(pns.exponent, pns.mantissa);
> + }
> +
> + // If we called compute_float<binary_format<T>>(pns.exponent, pns.mantissa)
> + // and we have an invalid power (am.power2 < 0),
> + // then we need to go the long way around again. This is very uncommon.
> + if (am.power2 < 0)
> + am = digit_comp<T>(pns, am);
> +
> + if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0)
> + || am.power2 == binary_format<T>::infinite_power())
> + {
> + // In case of over/underflow, return result_out_of_range and don't
> + // modify value, as per [charconv.from.chars]/1. Note that LWG 3081 wants
> + // to modify value in this case too.
> + answer.ec = std::errc::result_out_of_range;
> + return answer;
> + }
> +
> + // Transform the {,b}float16_t to float32_t before to_float.
> + if constexpr (std::is_same_v<T, floating_type_float16_t>)
> + {
> + if (am.power2 == 0)
> + {
> + if (am.mantissa)
> + {
> + int n = (std::numeric_limits<unsigned int>::digits
> + - __builtin_clz (am.mantissa)) - 1;
> + am.mantissa &= ~(static_cast<decltype(am.mantissa)>(1) << n);
> + am.mantissa <<= (binary_format<float>::mantissa_explicit_bits()
> + - n);
> + am.power2 = n + 0x67;
> + }
> + }
> + else
> + {
> + am.mantissa <<= 13;
> + am.power2 += 0x70;
> + }
> + }
> + else
> + am.mantissa <<= 16;
> + to_float(pns.negative, am, *value.x);
> + return answer;
> + }
> +} // fast_float
> +
> } // anon namespace
> #endif
>
> @@ -490,11 +756,14 @@ namespace
> from_chars_result
> __floating_from_chars_hex(const char* first, const char* last, T& value)
> {
> - static_assert(is_same_v<T, float> || is_same_v<T, double>);
> -
> - using uint_t = conditional_t<is_same_v<T, float>, uint32_t, uint64_t>;
> - constexpr int mantissa_bits = is_same_v<T, float> ? 23 : 52;
> - constexpr int exponent_bits = is_same_v<T, float> ? 8 : 11;
> + using uint_t = conditional_t<is_same_v<T, float>, uint32_t,
> + conditional_t<is_same_v<T, double>, uint64_t,
> + uint16_t>>;
> + constexpr int mantissa_bits
> + = fast_float::binary_format<T>::mantissa_explicit_bits();
> + constexpr int exponent_bits
> + = is_same_v<T, double> ? 11
> + : is_same_v<T, fast_float::floating_type_float16_t> ? 5 : 8;
> constexpr int exponent_bias = (1 << (exponent_bits - 1)) - 1;
>
> __glibcxx_requires_valid_range(first, last);
> @@ -520,12 +789,21 @@ namespace
> if (starts_with_ci(first, last, "inity"sv))
> first += strlen("inity");
>
> - uint_t result = 0;
> - result |= sign_bit;
> - result <<= exponent_bits;
> - result |= (1ull << exponent_bits) - 1;
> - result <<= mantissa_bits;
> - memcpy(&value, &result, sizeof(result));
> + if constexpr (is_same_v<T, float> || is_same_v<T, double>)
> + {
> + uint_t result = 0;
> + result |= sign_bit;
> + result <<= exponent_bits;
> + result |= (1ull << exponent_bits) - 1;
> + result <<= mantissa_bits;
> + memcpy(&value, &result, sizeof(result));
> + }
> + else
> + {
> + // float +/-Inf.
> + uint32_t result = 0x7F800000 | (sign_bit ? 0x80000000U : 0);
> + memcpy(value.x, &result, sizeof(result));
> + }
>
> return {first, errc{}};
> }
> @@ -566,12 +844,21 @@ namespace
>
> // We make the implementation-defined decision of ignoring the
> // sign bit and the n-char-sequence when assembling the NaN.
> - uint_t result = 0;
> - result <<= exponent_bits;
> - result |= (1ull << exponent_bits) - 1;
> - result <<= mantissa_bits;
> - result |= (1ull << (mantissa_bits - 1)) | 1;
> - memcpy(&value, &result, sizeof(result));
> + if constexpr (is_same_v<T, float> || is_same_v<T, double>)
> + {
> + uint_t result = 0;
> + result <<= exponent_bits;
> + result |= (1ull << exponent_bits) - 1;
> + result <<= mantissa_bits;
> + result |= (1ull << (mantissa_bits - 1)) | 1;
> + memcpy(&value, &result, sizeof(result));
> + }
> + else
> + {
> + // float qNaN.
> + uint32_t result = 0x7FC00001;
> + memcpy(value.x, &result, sizeof(result));
> + }
>
> return {first, errc{}};
> }
> @@ -633,18 +920,27 @@ namespace
> mantissa |= uint_t(hexit) << mantissa_idx;
> else if (mantissa_idx >= -4)
> {
> - if constexpr (is_same_v<T, float>)
> + if constexpr (is_same_v<T, float>
> + || is_same_v<T,
> + fast_float::floating_type_bfloat16_t>)
> {
> __glibcxx_assert(mantissa_idx == -1);
> mantissa |= hexit >> 1;
> midpoint_bit = (hexit & 0b0001) != 0;
> }
> - else
> + else if constexpr (is_same_v<T, double>)
> {
> __glibcxx_assert(mantissa_idx == -4);
> midpoint_bit = (hexit & 0b1000) != 0;
> nonzero_tail = (hexit & 0b0111) != 0;
> }
> + else
> + {
> + __glibcxx_assert(mantissa_idx == -2);
> + mantissa |= hexit >> 2;
> + midpoint_bit = (hexit & 0b0010) != 0;
> + nonzero_tail = (hexit & 0b0001) != 0;
> + }
> }
> else
> nonzero_tail |= (hexit != 0x0);
> @@ -808,7 +1104,34 @@ namespace
> __glibcxx_assert(((mantissa & (1ull << mantissa_bits)) != 0)
> == (biased_exponent != 0));
> }
> - memcpy(&value, &result, sizeof(result));
> + if constexpr (is_same_v<T, float> || is_same_v<T, double>)
> + memcpy(&value, &result, sizeof(result));
> + else if constexpr (is_same_v<T, fast_float::floating_type_bfloat16_t>)
> + {
> + uint32_t res = uint32_t{result} << 16;
> + memcpy(value.x, &res, sizeof(res));
> + }
> + else
> + {
> + // Otherwise float16_t which needs to be converted to float32_t.
> + uint32_t res;
> + if ((result & 0x7FFF) == 0)
> + res = uint32_t{result} << 16; // +/-0.0f16
> + else if ((result & 0x7C00) == 0)
> + { // denormal
> + unsigned n = (std::numeric_limits<unsigned int>::digits
> + - __builtin_clz (result & 0x3FF) - 1);
> + res = uint32_t{result} & 0x3FF & ~(uint32_t{1} << n);
> + res <<= 23 - n;
> + res |= (((uint32_t{n} + 0x67) << 23)
> + | ((uint32_t{result} & 0x8000) << 16));
> + }
> + else
> + res = (((uint32_t{result} & 0x3FF) << 13)
> + | ((((uint32_t{result} >> 10) & 0x1F) + 0x70) << 23)
> + | ((uint32_t{result} & 0x8000) << 16));
> + memcpy(value.x, &res, sizeof(res));
> + }
>
> return {first, errc{}};
> }
> @@ -826,9 +1149,7 @@ from_chars(const char* first, const char
> if (fmt == chars_format::hex)
> return __floating_from_chars_hex(first, last, value);
> else
> - {
> - return fast_float::from_chars(first, last, value, fmt);
> - }
> + return fast_float::from_chars(first, last, value, fmt);
> #else
> return from_chars_strtod(first, last, value, fmt);
> #endif
> @@ -842,9 +1163,7 @@ from_chars(const char* first, const char
> if (fmt == chars_format::hex)
> return __floating_from_chars_hex(first, last, value);
> else
> - {
> - return fast_float::from_chars(first, last, value, fmt);
> - }
> + return fast_float::from_chars(first, last, value, fmt);
> #else
> return from_chars_strtod(first, last, value, fmt);
> #endif
> @@ -863,9 +1182,7 @@ from_chars(const char* first, const char
> if (fmt == chars_format::hex)
> result = __floating_from_chars_hex(first, last, dbl_value);
> else
> - {
> - result = fast_float::from_chars(first, last, dbl_value, fmt);
> - }
> + result = fast_float::from_chars(first, last, dbl_value, fmt);
> if (result.ec == errc{})
> value = dbl_value;
> return result;
> @@ -874,6 +1191,31 @@ from_chars(const char* first, const char
> #endif
> }
>
> +#if USE_LIB_FAST_FLOAT
> +// Entrypoints for 16-bit floats.
> +[[gnu::cold]] from_chars_result
> +__from_chars_float16_t(const char* first, const char* last, float& value,
> + chars_format fmt) noexcept
> +{
> + struct fast_float::floating_type_float16_t val{ &value, 0 };
> + if (fmt == chars_format::hex)
> + return __floating_from_chars_hex(first, last, val);
> + else
> + return fast_float::from_chars_16(first, last, val, fmt);
> +}
> +
> +[[gnu::cold]] from_chars_result
> +__from_chars_bfloat16_t(const char* first, const char* last, float& value,
> + chars_format fmt) noexcept
> +{
> + struct fast_float::floating_type_bfloat16_t val{ &value, 0 };
> + if (fmt == chars_format::hex)
> + return __floating_from_chars_hex(first, last, val);
> + else
> + return fast_float::from_chars_16(first, last, val, fmt);
> +}
> +#endif
> +
> #ifdef _GLIBCXX_LONG_DOUBLE_COMPAT
> // Make std::from_chars for 64-bit long double an alias for the overload
> // for double.
> --- libstdc++-v3/testsuite/20_util/to_chars/float16_c++23.cc.jj 2022-10-31 16:09:43.486130628 +0100
> +++ libstdc++-v3/testsuite/20_util/to_chars/float16_c++23.cc 2022-10-31 16:27:00.690851973 +0100
> @@ -0,0 +1,76 @@
> +// 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 <iostream>
> +#include <cmath>
> +#include <testsuite_hooks.h>
> +
> +template<typename T>
> +void
> +test(std::chars_format fmt = std::chars_format{})
> +{
> + char str1[128], str2[128], str3[128];
> + union U { unsigned short s; T f; } u, v;
> + for (int i = 0; i <= (unsigned short) ~0; ++i)
> + {
> + u.s = i;
> + auto [ptr1, ec1] = std::to_chars(str1, str1 + sizeof(str1), u.f, fmt);
> + auto [ptr2, ec2] = std::to_chars(str2, str2 + sizeof(str2), std::float32_t(u.f), fmt);
> + VERIFY( ec1 == std::errc() && ec2 == std::errc());
> +// std::cout << i << ' ' << std::string_view (str1, ptr1)
> +// << '\t' << std::string_view (str2, ptr2) << '\n';
> + if (fmt == std::chars_format::fixed)
> + {
> + auto [ptr3, ec3] = std::to_chars(str3, str3 + (ptr1 - str1), u.f, fmt);
> + VERIFY( ec3 == std::errc() && ptr3 - str3 == ptr1 - str1 );
> + auto [ptr4, ec4] = std::to_chars(str3, str3 + (ptr1 - str1 - 1), u.f, fmt);
> + VERIFY( ec4 != std::errc() );
> + }
> + auto [ptr5, ec5] = std::from_chars(str1, ptr1, v.f,
> + fmt == std::chars_format{}
> + ? std::chars_format::general : fmt);
> + VERIFY( ec5 == std::errc() && ptr5 == ptr1 );
> + VERIFY( u.s == v.s || (std::isnan(u.f) && std::isnan(v.f)) );
> + }
> +}
> +
> +int
> +main()
> +{
> +#ifdef __STDCPP_FLOAT16_T__
> + test<std::float16_t>();
> + test<std::float16_t>(std::chars_format::fixed);
> + test<std::float16_t>(std::chars_format::scientific);
> + test<std::float16_t>(std::chars_format::general);
> + test<std::float16_t>(std::chars_format::hex);
> +#endif
> +#ifdef __STDCPP_BFLOAT16_T__
> + test<std::bfloat16_t>();
> + test<std::bfloat16_t>(std::chars_format::fixed);
> + test<std::bfloat16_t>(std::chars_format::scientific);
> + test<std::bfloat16_t>(std::chars_format::general);
> + test<std::bfloat16_t>(std::chars_format::hex);
> +#endif
> +}
>
> Jakub
>
@@ -673,6 +673,32 @@ namespace __detail
from_chars(const char* __first, const char* __last, long double& __value,
chars_format __fmt = chars_format::general) noexcept;
+ // Library routines for 16-bit extended floating point formats
+ // using float as interchange format.
+ from_chars_result
+ __from_chars_float16_t(const char* __first, const char* __last,
+ float& __value,
+ chars_format __fmt = chars_format::general) noexcept;
+ from_chars_result
+ __from_chars_bfloat16_t(const char* __first, const char* __last,
+ float& __value,
+ chars_format __fmt = chars_format::general) noexcept;
+
+#if defined(__STDCPP_FLOAT16_T__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) \
+ && defined(__cpp_lib_to_chars)
+ inline from_chars_result
+ from_chars(const char* __first, const char* __last, _Float16& __value,
+ chars_format __fmt = chars_format::general) noexcept
+ {
+ float __val;
+ from_chars_result __res
+ = __from_chars_float16_t(__first, __last, __val, __fmt);
+ if (__res.ec == errc{})
+ __value = __val;
+ return __res;
+ }
+#endif
+
#if defined(__STDCPP_FLOAT32_T__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32)
inline from_chars_result
from_chars(const char* __first, const char* __last, _Float32& __value,
@@ -709,6 +735,22 @@ namespace __detail
if (__res.ec == errc{})
__value = __val;
return __res;
+ }
+#endif
+
+#if defined(__STDCPP_BFLOAT16_T__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) \
+ && defined(__cpp_lib_to_chars)
+ inline from_chars_result
+ from_chars(const char* __first, const char* __last,
+ __gnu_cxx::__bfloat16_t & __value,
+ chars_format __fmt = chars_format::general) noexcept
+ {
+ float __val;
+ from_chars_result __res
+ = __from_chars_bfloat16_t(__first, __last, __val, __fmt);
+ if (__res.ec == errc{})
+ __value = __val;
+ return __res;
}
#endif
#endif
@@ -2448,6 +2448,8 @@ GLIBCXX_3.4.31 {
_ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE15_M_replace_cold*;
_ZSt20__to_chars_float16_tPcS_fSt12chars_format;
_ZSt21__to_chars_bfloat16_tPcS_fSt12chars_format;
+ _ZSt22__from_chars_float16_tPKcS0_RfSt12chars_format;
+ _ZSt23__from_chars_bfloat16_tPKcS0_RfSt12chars_format;
} GLIBCXX_3.4.30;
# Symbols in the support library (libsupc++) have their own tag.
@@ -75,6 +75,272 @@ extern "C" __ieee128 __strtoieee128(cons
namespace
{
# include "fast_float/fast_float.h"
+
+namespace fast_float
+{
+
+ // Wrappers around float for std::{,b}float16_t promoted to float.
+ struct floating_type_float16_t
+ {
+ float* x;
+ uint16_t bits;
+ };
+ struct floating_type_bfloat16_t
+ {
+ float* x;
+ uint16_t bits;
+ };
+
+ template<>
+ constexpr int
+ binary_format<floating_type_float16_t>::mantissa_explicit_bits()
+ { return 10; }
+
+ template<>
+ constexpr int
+ binary_format<floating_type_bfloat16_t>::mantissa_explicit_bits()
+ { return 7; }
+
+ // 10 bits of stored mantissa, pow(5,q) <= 0x4p+10 implies q <= 5
+ template<>
+ constexpr int
+ binary_format<floating_type_float16_t>::max_exponent_round_to_even()
+ { return 5; }
+
+ // 7 bits of stored mantissa, pow(5,q) <= 0x4p+7 implies q <= 3
+ template<>
+ constexpr int
+ binary_format<floating_type_bfloat16_t>::max_exponent_round_to_even()
+ { return 3; }
+
+ // 10 bits of stored mantissa, pow(5,-q) < 0x1p+64 / 0x1p+11 implies q >= -22
+ template<>
+ constexpr int
+ binary_format<floating_type_float16_t>::min_exponent_round_to_even()
+ { return -22; }
+
+ // 7 bits of stored mantissa, pow(5,-q) < 0x1p+64 / 0x1p+8 implies q >= -24
+ template<>
+ constexpr int
+ binary_format<floating_type_bfloat16_t>::min_exponent_round_to_even()
+ { return -24; }
+
+ template<>
+ constexpr int
+ binary_format<floating_type_float16_t>::minimum_exponent()
+ { return -15; }
+
+ template<>
+ constexpr int
+ binary_format<floating_type_bfloat16_t>::minimum_exponent()
+ { return -127; }
+
+ template<>
+ constexpr int
+ binary_format<floating_type_float16_t>::infinite_power()
+ { return 0x1F; }
+
+ template<>
+ constexpr int
+ binary_format<floating_type_bfloat16_t>::infinite_power()
+ { return 0xFF; }
+
+ template<>
+ constexpr int
+ binary_format<floating_type_float16_t>::sign_index()
+ { return 15; }
+
+ template<>
+ constexpr int
+ binary_format<floating_type_bfloat16_t>::sign_index()
+ { return 15; }
+
+ template<>
+ constexpr int
+ binary_format<floating_type_float16_t>::largest_power_of_ten()
+ { return 4; }
+
+ template<>
+ constexpr int
+ binary_format<floating_type_bfloat16_t>::largest_power_of_ten()
+ { return 38; }
+
+ template<>
+ constexpr int
+ binary_format<floating_type_float16_t>::smallest_power_of_ten()
+ { return -27; }
+
+ template<>
+ constexpr int
+ binary_format<floating_type_bfloat16_t>::smallest_power_of_ten()
+ { return -60; }
+
+ template<>
+ constexpr size_t
+ binary_format<floating_type_float16_t>::max_digits()
+ { return 22; }
+
+ template<>
+ constexpr size_t
+ binary_format<floating_type_bfloat16_t>::max_digits()
+ { return 98; }
+
+ // negative_digit_comp converts adjusted_mantissa to the (originally only)
+ // floating type and immediately back with slight tweaks (e.g. explicit
+ // leading bit instead of implicit for normals).
+ // Avoid going through the floating point type.
+ template<>
+ fastfloat_really_inline void
+ to_float<floating_type_float16_t>(bool negative, adjusted_mantissa am,
+ floating_type_float16_t &value)
+ {
+ constexpr int mantissa_bits
+ = binary_format<floating_type_float16_t>::mantissa_explicit_bits();
+ value.bits = (am.mantissa
+ | (uint16_t(am.power2) << mantissa_bits)
+ | (negative ? 0x8000 : 0));
+ }
+
+ template<>
+ fastfloat_really_inline void
+ to_float<floating_type_bfloat16_t>(bool negative, adjusted_mantissa am,
+ floating_type_bfloat16_t &value)
+ {
+ constexpr int mantissa_bits
+ = binary_format<floating_type_bfloat16_t>::mantissa_explicit_bits();
+ value.bits = (am.mantissa
+ | (uint16_t(am.power2) << mantissa_bits)
+ | (negative ? 0x8000 : 0));
+ }
+
+ template <>
+ fastfloat_really_inline adjusted_mantissa
+ to_extended<floating_type_float16_t>(floating_type_float16_t value) noexcept
+ {
+ adjusted_mantissa am;
+ constexpr int mantissa_bits
+ = binary_format<floating_type_float16_t>::mantissa_explicit_bits();
+ int32_t bias
+ = (mantissa_bits
+ - binary_format<floating_type_float16_t>::minimum_exponent());
+ constexpr uint16_t exponent_mask = 0x7C00;
+ constexpr uint16_t mantissa_mask = 0x03FF;
+ constexpr uint16_t hidden_bit_mask = 0x0400;
+ if ((value.bits & exponent_mask) == 0) {
+ // denormal
+ am.power2 = 1 - bias;
+ am.mantissa = value.bits & mantissa_mask;
+ } else {
+ // normal
+ am.power2 = int32_t((value.bits & exponent_mask) >> mantissa_bits);
+ am.power2 -= bias;
+ am.mantissa = (value.bits & mantissa_mask) | hidden_bit_mask;
+ }
+ return am;
+ }
+
+ template <>
+ fastfloat_really_inline adjusted_mantissa
+ to_extended<floating_type_bfloat16_t>(floating_type_bfloat16_t value) noexcept
+ {
+ adjusted_mantissa am;
+ constexpr int mantissa_bits
+ = binary_format<floating_type_bfloat16_t>::mantissa_explicit_bits();
+ int32_t bias
+ = (mantissa_bits
+ - binary_format<floating_type_bfloat16_t>::minimum_exponent());
+ constexpr uint16_t exponent_mask = 0x7F80;
+ constexpr uint16_t mantissa_mask = 0x007F;
+ constexpr uint16_t hidden_bit_mask = 0x0080;
+ if ((value.bits & exponent_mask) == 0) {
+ // denormal
+ am.power2 = 1 - bias;
+ am.mantissa = value.bits & mantissa_mask;
+ } else {
+ // normal
+ am.power2 = int32_t((value.bits & exponent_mask) >> mantissa_bits);
+ am.power2 -= bias;
+ am.mantissa = (value.bits & mantissa_mask) | hidden_bit_mask;
+ }
+ return am;
+ }
+
+ // Like fast_float.h from_chars_advanced, but for 16-bit float.
+ template<typename T>
+ from_chars_result
+ from_chars_16(const char* first, const char* last, T &value,
+ chars_format fmt) noexcept
+ {
+ parse_options options{fmt};
+
+ from_chars_result answer;
+ if (first == last)
+ {
+ answer.ec = std::errc::invalid_argument;
+ answer.ptr = first;
+ return answer;
+ }
+
+ parsed_number_string pns = parse_number_string(first, last, options);
+ if (!pns.valid)
+ return detail::parse_infnan(first, last, *value.x);
+
+ answer.ec = std::errc();
+ answer.ptr = pns.lastmatch;
+
+ adjusted_mantissa am
+ = compute_float<binary_format<T>>(pns.exponent, pns.mantissa);
+ if (pns.too_many_digits && am.power2 >= 0)
+ {
+ if (am != compute_float<binary_format<T>>(pns.exponent,
+ pns.mantissa + 1))
+ am = compute_error<binary_format<T>>(pns.exponent, pns.mantissa);
+ }
+
+ // If we called compute_float<binary_format<T>>(pns.exponent, pns.mantissa)
+ // and we have an invalid power (am.power2 < 0),
+ // then we need to go the long way around again. This is very uncommon.
+ if (am.power2 < 0)
+ am = digit_comp<T>(pns, am);
+
+ if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0)
+ || am.power2 == binary_format<T>::infinite_power())
+ {
+ // In case of over/underflow, return result_out_of_range and don't
+ // modify value, as per [charconv.from.chars]/1. Note that LWG 3081 wants
+ // to modify value in this case too.
+ answer.ec = std::errc::result_out_of_range;
+ return answer;
+ }
+
+ // Transform the {,b}float16_t to float32_t before to_float.
+ if constexpr (std::is_same_v<T, floating_type_float16_t>)
+ {
+ if (am.power2 == 0)
+ {
+ if (am.mantissa)
+ {
+ int n = (std::numeric_limits<unsigned int>::digits
+ - __builtin_clz (am.mantissa)) - 1;
+ am.mantissa &= ~(static_cast<decltype(am.mantissa)>(1) << n);
+ am.mantissa <<= (binary_format<float>::mantissa_explicit_bits()
+ - n);
+ am.power2 = n + 0x67;
+ }
+ }
+ else
+ {
+ am.mantissa <<= 13;
+ am.power2 += 0x70;
+ }
+ }
+ else
+ am.mantissa <<= 16;
+ to_float(pns.negative, am, *value.x);
+ return answer;
+ }
+} // fast_float
+
} // anon namespace
#endif
@@ -490,11 +756,14 @@ namespace
from_chars_result
__floating_from_chars_hex(const char* first, const char* last, T& value)
{
- static_assert(is_same_v<T, float> || is_same_v<T, double>);
-
- using uint_t = conditional_t<is_same_v<T, float>, uint32_t, uint64_t>;
- constexpr int mantissa_bits = is_same_v<T, float> ? 23 : 52;
- constexpr int exponent_bits = is_same_v<T, float> ? 8 : 11;
+ using uint_t = conditional_t<is_same_v<T, float>, uint32_t,
+ conditional_t<is_same_v<T, double>, uint64_t,
+ uint16_t>>;
+ constexpr int mantissa_bits
+ = fast_float::binary_format<T>::mantissa_explicit_bits();
+ constexpr int exponent_bits
+ = is_same_v<T, double> ? 11
+ : is_same_v<T, fast_float::floating_type_float16_t> ? 5 : 8;
constexpr int exponent_bias = (1 << (exponent_bits - 1)) - 1;
__glibcxx_requires_valid_range(first, last);
@@ -520,12 +789,21 @@ namespace
if (starts_with_ci(first, last, "inity"sv))
first += strlen("inity");
- uint_t result = 0;
- result |= sign_bit;
- result <<= exponent_bits;
- result |= (1ull << exponent_bits) - 1;
- result <<= mantissa_bits;
- memcpy(&value, &result, sizeof(result));
+ if constexpr (is_same_v<T, float> || is_same_v<T, double>)
+ {
+ uint_t result = 0;
+ result |= sign_bit;
+ result <<= exponent_bits;
+ result |= (1ull << exponent_bits) - 1;
+ result <<= mantissa_bits;
+ memcpy(&value, &result, sizeof(result));
+ }
+ else
+ {
+ // float +/-Inf.
+ uint32_t result = 0x7F800000 | (sign_bit ? 0x80000000U : 0);
+ memcpy(value.x, &result, sizeof(result));
+ }
return {first, errc{}};
}
@@ -566,12 +844,21 @@ namespace
// We make the implementation-defined decision of ignoring the
// sign bit and the n-char-sequence when assembling the NaN.
- uint_t result = 0;
- result <<= exponent_bits;
- result |= (1ull << exponent_bits) - 1;
- result <<= mantissa_bits;
- result |= (1ull << (mantissa_bits - 1)) | 1;
- memcpy(&value, &result, sizeof(result));
+ if constexpr (is_same_v<T, float> || is_same_v<T, double>)
+ {
+ uint_t result = 0;
+ result <<= exponent_bits;
+ result |= (1ull << exponent_bits) - 1;
+ result <<= mantissa_bits;
+ result |= (1ull << (mantissa_bits - 1)) | 1;
+ memcpy(&value, &result, sizeof(result));
+ }
+ else
+ {
+ // float qNaN.
+ uint32_t result = 0x7FC00001;
+ memcpy(value.x, &result, sizeof(result));
+ }
return {first, errc{}};
}
@@ -633,18 +920,27 @@ namespace
mantissa |= uint_t(hexit) << mantissa_idx;
else if (mantissa_idx >= -4)
{
- if constexpr (is_same_v<T, float>)
+ if constexpr (is_same_v<T, float>
+ || is_same_v<T,
+ fast_float::floating_type_bfloat16_t>)
{
__glibcxx_assert(mantissa_idx == -1);
mantissa |= hexit >> 1;
midpoint_bit = (hexit & 0b0001) != 0;
}
- else
+ else if constexpr (is_same_v<T, double>)
{
__glibcxx_assert(mantissa_idx == -4);
midpoint_bit = (hexit & 0b1000) != 0;
nonzero_tail = (hexit & 0b0111) != 0;
}
+ else
+ {
+ __glibcxx_assert(mantissa_idx == -2);
+ mantissa |= hexit >> 2;
+ midpoint_bit = (hexit & 0b0010) != 0;
+ nonzero_tail = (hexit & 0b0001) != 0;
+ }
}
else
nonzero_tail |= (hexit != 0x0);
@@ -808,7 +1104,34 @@ namespace
__glibcxx_assert(((mantissa & (1ull << mantissa_bits)) != 0)
== (biased_exponent != 0));
}
- memcpy(&value, &result, sizeof(result));
+ if constexpr (is_same_v<T, float> || is_same_v<T, double>)
+ memcpy(&value, &result, sizeof(result));
+ else if constexpr (is_same_v<T, fast_float::floating_type_bfloat16_t>)
+ {
+ uint32_t res = uint32_t{result} << 16;
+ memcpy(value.x, &res, sizeof(res));
+ }
+ else
+ {
+ // Otherwise float16_t which needs to be converted to float32_t.
+ uint32_t res;
+ if ((result & 0x7FFF) == 0)
+ res = uint32_t{result} << 16; // +/-0.0f16
+ else if ((result & 0x7C00) == 0)
+ { // denormal
+ unsigned n = (std::numeric_limits<unsigned int>::digits
+ - __builtin_clz (result & 0x3FF) - 1);
+ res = uint32_t{result} & 0x3FF & ~(uint32_t{1} << n);
+ res <<= 23 - n;
+ res |= (((uint32_t{n} + 0x67) << 23)
+ | ((uint32_t{result} & 0x8000) << 16));
+ }
+ else
+ res = (((uint32_t{result} & 0x3FF) << 13)
+ | ((((uint32_t{result} >> 10) & 0x1F) + 0x70) << 23)
+ | ((uint32_t{result} & 0x8000) << 16));
+ memcpy(value.x, &res, sizeof(res));
+ }
return {first, errc{}};
}
@@ -826,9 +1149,7 @@ from_chars(const char* first, const char
if (fmt == chars_format::hex)
return __floating_from_chars_hex(first, last, value);
else
- {
- return fast_float::from_chars(first, last, value, fmt);
- }
+ return fast_float::from_chars(first, last, value, fmt);
#else
return from_chars_strtod(first, last, value, fmt);
#endif
@@ -842,9 +1163,7 @@ from_chars(const char* first, const char
if (fmt == chars_format::hex)
return __floating_from_chars_hex(first, last, value);
else
- {
- return fast_float::from_chars(first, last, value, fmt);
- }
+ return fast_float::from_chars(first, last, value, fmt);
#else
return from_chars_strtod(first, last, value, fmt);
#endif
@@ -863,9 +1182,7 @@ from_chars(const char* first, const char
if (fmt == chars_format::hex)
result = __floating_from_chars_hex(first, last, dbl_value);
else
- {
- result = fast_float::from_chars(first, last, dbl_value, fmt);
- }
+ result = fast_float::from_chars(first, last, dbl_value, fmt);
if (result.ec == errc{})
value = dbl_value;
return result;
@@ -874,6 +1191,31 @@ from_chars(const char* first, const char
#endif
}
+#if USE_LIB_FAST_FLOAT
+// Entrypoints for 16-bit floats.
+[[gnu::cold]] from_chars_result
+__from_chars_float16_t(const char* first, const char* last, float& value,
+ chars_format fmt) noexcept
+{
+ struct fast_float::floating_type_float16_t val{ &value, 0 };
+ if (fmt == chars_format::hex)
+ return __floating_from_chars_hex(first, last, val);
+ else
+ return fast_float::from_chars_16(first, last, val, fmt);
+}
+
+[[gnu::cold]] from_chars_result
+__from_chars_bfloat16_t(const char* first, const char* last, float& value,
+ chars_format fmt) noexcept
+{
+ struct fast_float::floating_type_bfloat16_t val{ &value, 0 };
+ if (fmt == chars_format::hex)
+ return __floating_from_chars_hex(first, last, val);
+ else
+ return fast_float::from_chars_16(first, last, val, fmt);
+}
+#endif
+
#ifdef _GLIBCXX_LONG_DOUBLE_COMPAT
// Make std::from_chars for 64-bit long double an alias for the overload
// for double.
@@ -0,0 +1,76 @@
+// 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 <iostream>
+#include <cmath>
+#include <testsuite_hooks.h>
+
+template<typename T>
+void
+test(std::chars_format fmt = std::chars_format{})
+{
+ char str1[128], str2[128], str3[128];
+ union U { unsigned short s; T f; } u, v;
+ for (int i = 0; i <= (unsigned short) ~0; ++i)
+ {
+ u.s = i;
+ auto [ptr1, ec1] = std::to_chars(str1, str1 + sizeof(str1), u.f, fmt);
+ auto [ptr2, ec2] = std::to_chars(str2, str2 + sizeof(str2), std::float32_t(u.f), fmt);
+ VERIFY( ec1 == std::errc() && ec2 == std::errc());
+// std::cout << i << ' ' << std::string_view (str1, ptr1)
+// << '\t' << std::string_view (str2, ptr2) << '\n';
+ if (fmt == std::chars_format::fixed)
+ {
+ auto [ptr3, ec3] = std::to_chars(str3, str3 + (ptr1 - str1), u.f, fmt);
+ VERIFY( ec3 == std::errc() && ptr3 - str3 == ptr1 - str1 );
+ auto [ptr4, ec4] = std::to_chars(str3, str3 + (ptr1 - str1 - 1), u.f, fmt);
+ VERIFY( ec4 != std::errc() );
+ }
+ auto [ptr5, ec5] = std::from_chars(str1, ptr1, v.f,
+ fmt == std::chars_format{}
+ ? std::chars_format::general : fmt);
+ VERIFY( ec5 == std::errc() && ptr5 == ptr1 );
+ VERIFY( u.s == v.s || (std::isnan(u.f) && std::isnan(v.f)) );
+ }
+}
+
+int
+main()
+{
+#ifdef __STDCPP_FLOAT16_T__
+ test<std::float16_t>();
+ test<std::float16_t>(std::chars_format::fixed);
+ test<std::float16_t>(std::chars_format::scientific);
+ test<std::float16_t>(std::chars_format::general);
+ test<std::float16_t>(std::chars_format::hex);
+#endif
+#ifdef __STDCPP_BFLOAT16_T__
+ test<std::bfloat16_t>();
+ test<std::bfloat16_t>(std::chars_format::fixed);
+ test<std::bfloat16_t>(std::chars_format::scientific);
+ test<std::bfloat16_t>(std::chars_format::general);
+ test<std::bfloat16_t>(std::chars_format::hex);
+#endif
+}