I was reading through Infinity not constexpr, which seems to indicate that creating infinity is undefined behavior:
[expr]/4:
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.
However, if std::numeric_limits::is_iec559
equals true, it seems to give us more guarantees.
The code below makes use of this guarantee in order to create an infinite number. When executed in constexpr
context, it results in a compiler failure as this is undefined behavior
in case is_iec559
equals false.
// clang++ -std=c++17 -O3
#include <limits>
constexpr double createInfinity()
{
static_assert(std::numeric_limits<double>::is_iec559, "asdf");
double d = 999999999999;
while (d != std::numeric_limits<double>::infinity())
{
d *= d;
}
return -1*d;
}
static_assert(createInfinity() == std::numeric_limits<double>::infinity(), "inf");
Code at Compiler Explorer
As this function always results in infinite, it can never be called in a valid C++ program. However, as we assert on the is_iec559
, we get extra guarantees. Is this program still invalid?
is_iec559
?(Answers can use both C++17 as the upcoming C++20, please clearly indicate which is used)
The IEEE-754 standard describes floating-point formats, a way to represent real numbers in hardware. There are at least five internal formats for floating-point numbers that are representable in hardware targeted by the MSVC compiler. The compiler only uses two of them.
The standard specifies optional extended and extendable precision formats, which provide greater precision than the basic formats. An extended precision format extends a basic format by using more precision and more exponent range.
The value NAN is used to represent a value that is an error. This is represented when exponent field is all ones with a zero sign bit or a mantissa that it not 1 followed by zeros. This is a special value that might be used to denote a variable that doesn't yet hold a value.
The number 0 is usually encoded as +0, but can be represented by either +0 or −0. The IEEE 754 standard for floating-point arithmetic (presently used by most computers and programming languages that support floating-point numbers) requires both +0 and −0.
Waiting some time sometimes helps, looks like Clang has received a patch that makes this code compile: https://reviews.llvm.org/D63793
Prior to r329065, we used [-max, max] as the range of representable values because LLVM's fptrunc did not guarantee defined behavior when truncating from a larger floating-point type to a smaller one. Now that has been fixed, we can make clang follow normal IEEE 754 semantics in this regard and take the larger range [-inf, +inf] as the range of representable values.
Interesting element to remark (part of the code comments in that revision) is that operations resulting in NaN are not (yet) allowed:
// [expr.pre]p4:
// If during the evaluation of an expression, the result is not
// mathematically defined [...], the behavior is undefined.
// FIXME: C++ rules require us to not conform to IEEE 754 here.
Example at compiler explorer:
#include <limits>
constexpr double createNan()
{
static_assert(std::numeric_limits<double>::is_iec559, "asdf");
double d = std::numeric_limits<double>::infinity() / std::numeric_limits<double>::infinity();
return -1*d;
}
static_assert(createNan() != 0., "NaN");
The program is ill-formed.
Per [expr.const]/4,
An expression
e
is a core constant expression unless the evaluation ofe
, following the rules of the abstract machine, would evaluate one of the following expressions:
[...]
an operation that would have undefined behavior as specified in [intro] through [cpp] of this document [ Note: including, for example, signed integer overflow ([expr.prop]), certain pointer arithmetic ([expr.add]), division by zero, or certain shift operations — end note ];
[...]
And [expr.pre]/4 says:
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined. [ Note: Treatment of division by zero, forming a remainder using a zero divisor, and all floating-point exceptions vary among machines, and is sometimes adjustable by a library function. — end note ]
Note that mathematically, the product of two finite numbers is never infinity, so the product triggers undefined behavior. The implementation may specify such expressions as defined, but it is still undefined behavior from the perspective of the standard. The definition by the implementation does not apply retrospectively to other parts of the standard. Therefore, the program is ill-formed because it tries to evaluate an expression that triggers undefined behavior as a constant expression.
It is interesting though, that numeric_limits<double>::infinity()
is constexpr
. This is fine. Per [numeric.limits]/infinity:
static constexpr T infinity() noexcept;
Representation of positive infinity, if available.
Meaningful for all specializations for which
has_infinity != false
. Required in specializations for whichis_iec559 != false
.
If is_iec559 == true
, then has_infinity == true
, and the infinity value is returned. If is_iec559 == false
, has_infinity
may be true
, in which case the infinity value is also returned, or it may be false
, in which case infinity()
returns 0
. (!)
However, since the product of two big numbers is not automatically infinity (it is undefined behavior instead), there is no contradiction. Passing infinity around is fine (the infinity value is always in the range of representable values), but multiplying two big numbers and assuming the result is infinity is not.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With