Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are floating point operations resulting in infinity undefined behavior for IEC 559/IEEE 754 floating-point types

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?

  • If Invalid? What's the point of having is_iec559?
  • If Valid? Why would it be valid at runtime and not in constexpr context?

(Answers can use both C++17 as the upcoming C++20, please clearly indicate which is used)

like image 966
JVApen Avatar asked May 12 '19 09:05

JVApen


People also ask

What is IEEE floating-point representation?

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.

Why is the IEEE 754 standard important?

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.

How does the result of invalid operation represent in ieee754?

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.

How do you represent zero in a floating-point?

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.


2 Answers

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");
like image 96
JVApen Avatar answered Nov 15 '22 20:11

JVApen


The program is ill-formed.

Per [expr.const]/4,

An expression e is a core constant expression unless the evaluation of e, 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 which is_­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.

like image 25
L. F. Avatar answered Nov 15 '22 19:11

L. F.