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 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.
Modify 31.7.6.3.2 [ostream.inserters.arithmetic] as indicated:
-3- Whenval
is of typefloat
the formatting conversion occurs as if it performed the following code fragment:[Note ?: Whenbool failed = use_facet< num_put<charT, ostreambuf_iterator<charT, traits>> >(getloc()).put(*this, *this, fill(), static_cast<double>(val)).failed();
val
is a NaN value, the conversion todouble
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.
Modify 31.7.6.3.2 [ostream.inserters.arithmetic] as indicated:
-3- Whenval
is of typefloat
the formatting conversion occurs as if it performed the following code fragment:wherebool failed = use_facet< num_put<charT, ostreambuf_iterator<charT, traits>> >(getloc()).put(*this, *this, fill(),
static_cast<double>(val)dval).failed();dval
isval
converted to typedouble
as if bystatic_cast<double>(val)
, except that the sign and payload of NaN values may be preserved rather than discarded.