]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
libstdc++: Preserve signbit of nan when converting float to double [PR113578]
authorJonathan Wakely <jwakely@redhat.com>
Tue, 30 Jan 2024 14:47:34 +0000 (14:47 +0000)
committerJonathan Wakely <redi@gcc.gnu.org>
Thu, 26 Sep 2024 20:32:01 +0000 (21:32 +0100)
LWG 117 specifies that inserting a float into an ostream should cast it
to double, because there's no std::num_put::put member that takes a
float. However, on RISC-V converting a NaN float to double loses the
sign, which means that negative NaN floats are printed as positive.

This has been reported as LWG 4101 and there is good support for fixing
the standard to preserve the sign bit when printing negative NaN values.

This change uses copysign((double)f, (double)std::bit_cast<int>(f)) to
get a double that preserves the sign. The bit_cast gives us an integer
with the same signbit, and casting that to the target type preserves
the signbit. We don't care about the value, as copysign only uses the
signbit.

The inserters for extended floating-point types need the same treatment,
so add a new _S_cast_flt helper to do the signbit-preserving conversion
generically.

So far only RISC-V has been confirmed to need this treatment, but we
might need to extend it to other targets later.

libstdc++-v3/ChangeLog:

PR libstdc++/113578
* include/std/ostream (_S_cast_flt): New static member function
to restore signbit after casting to double or long double.
(operator<<(float), operator<<(_Float16), operator<<(_Float32))
(operator<<(_Float64), operator(_Float128))
(operator<<(__bfloat16_t)): Use _S_cast_flt.
testsuite/27_io/basic_ostream/inserters_arithmetic/lwg4101.cc:
New test.

Co-authored-by: Andrew Waterman <andrew@sifive.com>
libstdc++-v3/include/std/ostream
libstdc++-v3/testsuite/27_io/basic_ostream/inserters_arithmetic/lwg4101.cc [new file with mode: 0644]

index d8462efe764d4ebd432de0032021aed8f8be3fe4..a1cb7ce1765e770d926cb25257fd7ca817152fef 100644 (file)
@@ -235,7 +235,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       {
        // _GLIBCXX_RESOLVE_LIB_DEFECTS
        // 117. basic_ostream uses nonexistent num_put member functions.
-       return _M_insert(static_cast<double>(__f));
+       return _M_insert(_S_cast_flt<double>(__f));
       }
 
       __ostream_type&
@@ -248,7 +248,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       __ostream_type&
       operator<<(_Float16 __f)
       {
-       return _M_insert(static_cast<double>(__f));
+       return _M_insert(_S_cast_flt<double>(__f));
       }
 #endif
 
@@ -257,7 +257,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       __ostream_type&
       operator<<(_Float32 __f)
       {
-       return _M_insert(static_cast<double>(__f));
+       return _M_insert(_S_cast_flt<double>(__f));
       }
 #endif
 
@@ -266,7 +266,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       __ostream_type&
       operator<<(_Float64 __f)
       {
-       return _M_insert(static_cast<double>(__f));
+       return _M_insert(_S_cast_flt<double>(__f));
       }
 #endif
 
@@ -275,7 +275,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       __ostream_type&
       operator<<(_Float128 __f)
       {
-       return _M_insert(static_cast<long double>(__f));
+       return _M_insert(_S_cast_flt<long double>(__f));
       }
 #endif
 
@@ -284,7 +284,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       __ostream_type&
       operator<<(__gnu_cxx::__bfloat16_t __f)
       {
-       return _M_insert(static_cast<double>(__f));
+       return _M_insert(_S_cast_flt<double>(__f));
       }
 #endif
 
@@ -475,7 +475,38 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       _M_write(const char_type* __s, streamsize __n)
       { std::__ostream_insert(*this, __s, __n); }
 #endif
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wc++17-extensions" // for if-constexpr
+      template<typename _To, typename _From>
+       static _To
+       _S_cast_flt(_From __f)
+       {
+         _To __d = static_cast<_To>(__f);
+         // _GLIBCXX_RESOLVE_LIB_DEFECTS
+         // 4101: LWG 117 loses the sign for negative NaN on some arches.
+#if defined __riscv
+         _To __sign;
+#if __cpp_constexpr && __has_builtin(__builtin_bit_cast)
+         if constexpr (sizeof(__f) == sizeof(short))
+           __sign = static_cast<_To>(__builtin_bit_cast(short, __f));
+         else if constexpr (sizeof(__f) == sizeof(int))
+           __sign = static_cast<_To>(__builtin_bit_cast(int, __f));
+         else if constexpr (sizeof(__f) == sizeof(long long))
+           __sign = static_cast<_To>(__builtin_bit_cast(long long, __f));
+         else
+#endif
+         __sign = __builtin_signbit(__f) ? _To(-1.0) : _To(+1.0);
+
+         if _GLIBCXX17_CONSTEXPR (__is_same(_To, double))
+           __d = __builtin_copysign(__d, __sign);
+         else if _GLIBCXX17_CONSTEXPR (__is_same(_To, long double))
+           __d = __builtin_copysignl(__d, __sign);
+#endif
+         return __d;
+       }
     };
+#pragma GCC diagnostic pop
 
   /**
    *  @brief  Performs setup work for output streams.
diff --git a/libstdc++-v3/testsuite/27_io/basic_ostream/inserters_arithmetic/lwg4101.cc b/libstdc++-v3/testsuite/27_io/basic_ostream/inserters_arithmetic/lwg4101.cc
new file mode 100644 (file)
index 0000000..1e1b8e0
--- /dev/null
@@ -0,0 +1,14 @@
+// { dg-do run }
+// LWG 4101. LWG 117 loses the sign for negative NaN on some architectures
+
+#include <sstream>
+#include <limits>
+#include <testsuite_hooks.h>
+
+int main()
+{
+  float nan = std::numeric_limits<float>::quiet_NaN();
+  std::ostringstream os;
+  os << -nan;
+  VERIFY( os.str()[0] == '-' );
+}