Re: [PATCH] libstdc++: Format std::float16_t and std::bfloat16_t using respective std::to_chars overloads [PR126731]

Jonathan Wakely <[email protected]>
Newsgroups gmane.comp.gcc.patches,gmane.comp.gcc.libstdc++.devel
Message-ID <[email protected]>
On Thu, 13 Aug 2026 at 10:36 +0200, Tomasz Kamiński wrote:
>This patch removes the partial specialization for float16_t and bfloat16_t
>that formatted the values by casting to float. In consequence an implicit
>specialization of generic floating-point specialization is used, and
>to_chars overloads for their types are used.
>As explained in r13-3591-g0ae26533b3e268 (that adds corresponding to_chars
>overload), the shortest string (produced when no precision is specified)
>is shorter for above, than float.
>
>As formatter specializations were defined under the same condition as
>corresponding to_chars overloads, the __formattable_float constrain on
>formatter floating-point specialization makes it enable in same cases.
>
>This reverts parts of r14-3305-g6cf214b4fc97f5 (revereted partially

"reverted"

>in r14-3329-g27d0cfcb2b33de), leading to (necessary for correctness)
>increase of the number template specializations. It also depends on
>preserving values of above types (instead of float) in basic_format_arg
>introduced r16-616-g9c9a7316adb996.

Ah, so I added these specializations in r14-3305-g6cf214b4fc97f5 to
support printing extended floating-point types in C++20, but that
turned out to not work. So I adjusted the conditions for enabling them
in r14-3329-g27d0cfcb2b33de, so that they are only enabled for C++23.
But really we should have just removed the specializations again,
because the __formattable_float specialization formats them properly
since r16-616-g9c9a7316adb996.

>The specializations for float32_t and float64_t are left unchanged,
>as their to_chars overloads are also implemented in terms of casting
>to float/double respectivelly.
>
>libstdc++-v3/ChangeLog:
>
>	PR libstdc++/126731
>	* include/std/format (std::formatter<_Float16, _CharT>)
>	(std::formatter<__format::__bflt16_t, _CharT>): Remove
>	partial specializations.
>	* testsuite/std/format/formatter/ext_float.cc: Test output
>	for closest value to 1/10.
>---
>As far as I can tell this case will not be affected by P3505R4
>"Fix the default floating-point representation in std::format",
>as float(0.1f16) is 1.998p-4, while 0.1 f is 1.99999ap-4, so
>only later could use 0.1 as shortest string.
>
>Testing on x86_64-linux. *format* passed in all standard modes,
>-m32, debug and assertions. OK for trunk and 16?
>(We cannot backport it to eariel standards due dependency described
>above).

Yes OK for trunk and gcc-16.

If I understand correctly, I think we could remove them in gcc-14 and
gcc-15, because they're redundant (the __formattable_float
specialization can format them).  But we would still get incorrect
results, because basic_format_arg would convert them to float. So the
extra specializations in gcc-14 and gcc-15 are not doing any harm, and
it doesn't seem worth backporting it.


> libstdc++-v3/include/std/format               | 44 -------------------
> .../std/format/formatter/ext_float.cc         | 22 ++++++----
> 2 files changed, 13 insertions(+), 53 deletions(-)
>
>diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
>index 729cb89ec60..8d530e883c3 100644
>--- a/libstdc++-v3/include/std/format
>+++ b/libstdc++-v3/include/std/format
>@@ -3003,28 +3003,6 @@ namespace __format
>     };
> #endif
>
>-#if defined(__STDCPP_FLOAT16_T__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32)
>-  // Reuse __formatter_fp<C>::format<float, Out> for _Float16.
>-  template<__format::__char _CharT>
>-    struct formatter<_Float16, _CharT>
>-    {
>-      formatter() = default;
>-
>-      [[__gnu__::__always_inline__]]
>-      constexpr typename basic_format_parse_context<_CharT>::iterator
>-      parse(basic_format_parse_context<_CharT>& __pc)
>-      { return _M_f.parse(__pc); }
>-
>-      template<typename _Out>
>-	typename basic_format_context<_Out, _CharT>::iterator
>-	format(_Float16 __u, basic_format_context<_Out, _CharT>& __fc) const
>-	{ return _M_f.format((float)__u, __fc); }
>-
>-    private:
>-      __format::__formatter_fp<_CharT> _M_f;
>-    };
>-#endif
>-
> #if defined(__FLT32_DIG__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32)
>   // Reuse __formatter_fp<C>::format<float, Out> for _Float32.
>   template<__format::__char _CharT>
>@@ -3114,28 +3092,6 @@ namespace __format
>     };
> #endif
>
>-#if defined(__STDCPP_BFLOAT16_T__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32)
>-  // Reuse __formatter_fp<C>::format<float, Out> for bfloat16_t.
>-  template<__format::__char _CharT>
>-    struct formatter<__format::__bflt16_t, _CharT>
>-    {
>-      formatter() = default;
>-
>-      [[__gnu__::__always_inline__]]
>-      constexpr typename basic_format_parse_context<_CharT>::iterator
>-      parse(basic_format_parse_context<_CharT>& __pc)
>-      { return _M_f.parse(__pc); }
>-
>-      template<typename _Out>
>-	typename basic_format_context<_Out, _CharT>::iterator
>-	format(__gnu_cxx::__bfloat16_t __u,
>-	       basic_format_context<_Out, _CharT>& __fc) const
>-	{ return _M_f.format((float)__u, __fc); }
>-
>-    private:
>-      __format::__formatter_fp<_CharT> _M_f;
>-    };
>-#endif
> #endif // __cpp_lib_to_chars
>
>   /** Format a pointer.
>diff --git a/libstdc++-v3/testsuite/std/format/formatter/ext_float.cc b/libstdc++-v3/testsuite/std/format/formatter/ext_float.cc
>index 82e696fa797..7b231b07006 100644
>--- a/libstdc++-v3/testsuite/std/format/formatter/ext_float.cc
>+++ b/libstdc++-v3/testsuite/std/format/formatter/ext_float.cc
>@@ -4,10 +4,14 @@
> #include <testsuite_hooks.h>
>
> template<typename T>
>-bool format_float()
>+void
>+verify_output()
> {
>-    auto s = std::format("{:#} != {:<+7.3f}", (T)-0.0, (T)0.5);
>-    return s == "-0. != +0.500 ";
>+  auto s = std::format("{:#} != {:<+7.3f}", T(-0.0), T(0.5));
>+  VERIFY( s == "-0. != +0.500 ");
>+
>+  s = std::format("{}", T(1)/T(10));
>+  VERIFY( s == "0.1");
> }
>
> #if __cplusplus > 202002L
>@@ -23,7 +27,7 @@ test_float16()
> {
> #if __FLT16_DIG__
>   if constexpr (formattable<_Float16>)
>-    VERIFY( format_float<_Float16>() );
>+    verify_output<_Float16>();
>   else
>     std::puts("Cannot format _Float16 on this target");
> #endif
>@@ -34,7 +38,7 @@ test_float32()
> {
> #if __FLT32_DIG__
>   if constexpr (formattable<_Float32>)
>-    VERIFY( format_float<_Float32>() );
>+    verify_output<_Float32>();
>   else
>     std::puts("Cannot format _Float32 on this target");
> #endif
>@@ -45,7 +49,7 @@ test_float64()
> {
> #if __FLT64_DIG__
>   if constexpr (formattable<_Float64>)
>-    VERIFY( format_float<_Float64>() );
>+    verify_output<_Float64>();
>   else
>     std::puts("Cannot format _Float64 on this target");
> #endif
>@@ -56,13 +60,13 @@ test_float128()
> {
> #ifdef __SIZEOF_FLOAT128__
>   if constexpr (formattable<__float128>)
>-    VERIFY( format_float<__float128>() );
>+    verify_output<__float128>();
>   else
>     std::puts("Cannot format __float128 on this target");
> #endif
> #if __FLT128_DIG__
>   if constexpr (formattable<_Float128>)
>-    VERIFY( format_float<_Float128>() );
>+    verify_output<_Float128>();
>   else
>     std::puts("Cannot format _Float128 on this target");
> #endif
>@@ -75,7 +79,7 @@ test_bfloat16()
>   using bfloat16_t = decltype(0.0bf16);
>
>   if constexpr (formattable<bfloat16_t>)
>-    VERIFY( format_float<bfloat16_t>() );
>+    verify_output<bfloat16_t>();
>   else
>     std::puts("Cannot format bfloat16_t on this target");
> #endif
>-- 
>2.55.0
>
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.