N3071 Conversions in constexpr initializers (C2x CD UK comment)

Joseph Myers

All subclause and paragraph references here are to the C2x CD, SC22 N5777. Throughout, it is supposed that Annex F is in effect for binary floating-point arithmetic, so float and double have the IEC 60559 binary32 and binary64 formats.

6.7.1 paragraph 5 says, regarding initializers for constexpr objects, says:

The value of any constant expressions or of any character in a string literal of the initializer shall be exactly representable in the corresponding target type; no change of value shall be applied.

with a footnote saying

In the context of arithmetic conversions, 6.3.1 describes the details of changes of value that occur if values of arithmetic expressions are stored in the objects that for example have a different signedness, excess precision or quantum exponent. Whenever such a change of value is necessary, the constraint is violated.

6.3.1 does not define the concepts of “exactly representable” or “change of value”. To some extent, it uses those concepts (without a precise definition). However, various ambiguities arise in the interpretation of those concepts for constexpr initializers of objects of (real or complex) floating type, that do not arise in 6.3.1, at least when 6.3.1 is taken together with the more detailed semantics in Annex F. Subclause 6.3.1 (plus Annex F) only has to define what the result of a conversion is; it doesn’t have to define whether values in one type are considered the same as values in another type.

In particular, the concept of “represented exactly” as used in 6.3.1.5 is considered as one of a group of cases with “in the range of values that can be represented but cannot be represented exactly” and “outside the range of values that can be represented”. This does not cover the cases of NaNs (not ordered, so not part of any range), or of additional information beyond that determined by the ordering (sign of zero, quantum exponent). Furthermore, the rules for conversions between real and complex types in 6.3.1.7 never need to say whether a value is considered exactly representable in another type; they simply say what the result of a conversion is.

Thus, consider the following specific questions and examples. In each case, WG14 needs to decide what the desired semantics are, and ensure that normative text (not just footnotes and examples) is sufficient to make those semantics clear. This should probably be done based on recommendations from the C floating-point group, and much of the normative text might reasonably go in Annex F (maybe F.8.5) and Annex G; I have previously suggested that defining a term “constexpr-representable” might be helpful. For some of the questions, the answers might be different in different sub-cases; the examples try to illustrate the main possible sub-cases.

Real to complex

Question 1: Suppose a constexpr object of complex type is initialized with an expression of real type (including the case of integer type), and that value is exactly representable (whatever that means) in the corresponding real type. Does this count as exactly representable in the complex type, or is (x, 0) considered as a different value from the real number x (one has an imaginary part (of 0), the other doesn’t), so resulting in a constraint violation?

constexpr _Complex double x1 = 1.0;

Complex to real

Question 2: Suppose a constexpr object of real floating type is initialized with an expression of complex type, and the real part of the expression is exactly representable (whatever that means) in the type of the object being initialized, and the imaginary part is the same (positive or unsigned) zero that results from a conversion from real to complex type. Does this count as exactly representable in the real type, or is (x, 0) considered as a different value from the real number x, so resulting in a constraint violation?

constexpr double x2 = (_Complex double) 1.0;

Question 3: If the example in Question 2 is valid, what about if the zero is a different one from that resulting from conversion from real to complex (for example, if it is a negative zero)? Is this valid or a constraint violation?

#include <complex.h>

constexpr double x3 = CMPLX (1.0, -0.0);

Quiet NaNs

Question 4: Is a quiet NaN of one standard floating type considered exactly representable in another standard floating type? Or is this only guaranteed in some cases, depending on whether the payload can be presented in both types? Does it depend in some way on how the implementation defines conversions between NaNs in different types?

#include <float.h>

constexpr double x4a = NAN;
constexpr long double x4b = NAN;
constexpr float x4c = (double) NAN;
constexpr float x4d = (long double) NAN;

Signaling NaNs

Question 5: Is a signaling NaN of one standard floating type considered exactly representable in another standard floating type? There is a strong argument that it should not be valid as an initializer if the two types are of different formats, because the implicit conversion would produce a quiet NaN. If the two types are of the same format, does validity depend on whether the conversion is a convertFormat or copy operation?

#include <float.h>

constexpr double x5a = FLT_SNAN;
// Does validity of these examples depend on whether double and long
// double have the same format, and, if they do, whether the
// conversion is a convertFormat or copy operation?
constexpr long double x5b = DBL_SNAN;
constexpr double x5c = LDBL_SNAN;

Standard, binary or integer to decimal

Question 6: Suppose a constexpr object of decimal floating type is initialized with an expression of standard or binary floating type, and that the value represented by that expression at IEEE Level 2 (“Floating-point data”) can also be represented in the decimal floating type. Is this valid, or is it a constraint violation because of differences at IEEE Level 3 (which represents quantum exponents, which don’t exist in binary types)? Does it make any difference if the value is an infinity or (quiet) NaN (no quantum exponents)? And what about integer values?

#include <float.h>

constexpr _Decimal32 x6a = 1.0;
constexpr _Decimal32 x6b = INFINITY;
constexpr _Decimal128 x6c = NAN;
constexpr _Decimal32 x6d = 1;

Decimal to standard or binary

Question 7: Suppose a constexpr object of standard or binary floating type is initialized with an expression of decimal floating type, and that the value represented by that expression at IEEE Level 2 (“Floating-point data”) can also be represented in the standard or binary floating type. Is this valid, or is it a constraint violation because of differences at IEEE Level 3 (which represents quantum exponents, which don’t exist in binary types)? Does it make any difference if the value is an infinity or (quiet) NaN (no quantum exponents)? For finite values, does it make any difference whether the quantum exponent is the one that would result from a conversion in the reverse direction?

#include <float.h>

constexpr float x7a = 1.DF;
constexpr float x7b = 1.00DF;
constexpr float x7c = DEC_INFINITY;
constexpr double x7d = DEC_NAN;

Decimal quantum exponents

Question 8: Suppose both the constexpr object and the expression initializing it have decimal floating type, and the real number represented by the expression is representable in the type of the object, but the quantum exponent is not representable. Is this valid? The example in 6.7.1 paragraph 14 (Note 2) is commented to say not, but it is not clear this follows from any normative text.

#include <float.h>

constexpr _Decimal32 x8 = DEC64_TRUE_MIN * 0;