Title
LWG 117 loses the sign for negative NaN on some architectures
Status
new
Section
[ostream.inserters.arithmetic]
Submitter
Jonathan Wakely

Created on 2024-05-10.00:00:00 last changed 1 month ago

Messages

Date: 2024-05-15.10:12:25

Proposed resolution:

This wording is relative to N4981.

  1. Modify [ostream.inserters.arithmetic] as indicated:

    -3- When `val` is of type `float` the formatting conversion occurs as if it performed the following code fragment:
    
      bool failed = use_facet<
        num_put<charT, ostreambuf_iterator<charT, traits>>
          >(getloc()).put(*this, *this, fill(),
            static_cast<double>(val)dval).failed();
    
    where dval is `val` converted to type `double` as if by static_cast<double>(val), except that the sign and payload of NaN values may be preserved rather than discarded.
Date: 2024-05-15.00:00:00

[ 2024-05-15; Peter Dimov provides improved wording ]

Jonathan observes that the same problem exists for operator<<(extended-floating-point-type).

Date: 2024-05-15.00:00:00

[ 2024-05-15; Reflector poll ]

Set priority to 3 after reflector poll.

Date: 2024-05-15.10:12:25

LWG 117 fixed insertion of a `float` into an ostream by requiring a cast to `double`. This gives a surprising result on RISC-V when inserting a negative NaN, because RISC-V floating-point instructions do not preserve the sign or payload of NaN values. This means that std::cout << -std::numeric_limits<float>::quiet_NaN() prints `"nan"` rather than `"-nan"` as most users probably expect.

11.3 section of the RISC-V ISA manual (20191213):

Except when otherwise stated, if the result of a floating-point operation is NaN, it is the canonical NaN. The canonical NaN has a positive sign and all significand bits clear except the MSB, a.k.a. the quiet bit. For single-precision floating-point, this corresponds to the pattern 0x7fc00000.

This is allowed by IEEE 754 (2019), as per section 6.3:

When either an input or result is a NaN, this standard does not interpret the sign of a NaN.

So it is standard-conforming for static_cast<double>(val) to lose the sign (and payload) of a NaN. This might also affect Apple M1 chips, if they use the ARMv8 default-NaN mode.

The current wording does not permit an implementation to use something like std::copysign(static_cast<double>(val), std::signbit(val) ? -1.0 : +1.0) to restore the sign bit. Should this be allowed? Maybe we should say that it's unspecified whether the cast to `double` preserves the sign of a NaN? If not, should we add a note about the surprising behaviour?

This wording is relative to N4981.

  1. Modify [ostream.inserters.arithmetic] as indicated:

    -3- When `val` is of type `float` the formatting conversion occurs as if it performed the following code fragment:
    
      bool failed = use_facet<
        num_put<charT, ostreambuf_iterator<charT, traits>>
          >(getloc()).put(*this, *this, fill(),
            static_cast<double>(val)).failed();
    
    [Note ?: When `val` is a NaN value, the conversion to `double` can alter the sign and payload. — end note]
History
Date User Action Args
2024-05-15 10:12:25adminsetmessages: + msg14145
2024-05-15 10:12:25adminsetmessages: + msg14144
2024-05-13 13:33:51adminsetmessages: + msg14141
2024-05-10 00:00:00admincreate