This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

4101. LWG 117 loses the sign for negative NaN on some architectures

Section: 31.7.6.3.2 [ostream.inserters.arithmetic] Status: New Submitter: Jonathan Wakely Opened: 2024-05-10 Last modified: 2024-05-15

Priority: 3

View all other issues in [ostream.inserters.arithmetic].

View all issues with New status.

Discussion:

LWG 117(i) 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?

Previous resolution [SUPERSEDED]:

This wording is relative to N4981.

  1. Modify 31.7.6.3.2 [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]

[2024-05-15; Reflector poll]

Set priority to 3 after reflector poll.

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

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

Proposed resolution:

This wording is relative to N4981.

  1. Modify 31.7.6.3.2 [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.