libstdc++: std::from_chars std::{,b}float16_t support

Message ID Y2DoqF9dtrknNICD@tucnak
State Unresolved
Headers
Series libstdc++: std::from_chars std::{,b}float16_t support |

Checks

Context Check Description
snail/gcc-patch-check warning Git am fail log

Commit Message

Jakub Jelinek Nov. 1, 2022, 9:36 a.m. UTC
  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

Jonathan Wakely Nov. 1, 2022, 12:28 p.m. UTC | #1
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
>
  

Patch

--- 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
+}