11

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)

JVApen
  • 11,008
  • 5
  • 31
  • 67
  • "*Why would it be valid at runtime and not in constexpr context?*" Who says that it isn't? – Nicol Bolas May 12 '19 at 13:30
  • "it seems to give us more guarantees". What guarantees? – geza May 12 '19 at 13:38
  • Unfortunately, floating-point is a mess in the C and C++ standards. Phrases like “mathematically defined” and “range of representable values” are never defined in C++, although *C* says that having infinities makes the latter include is all real numbers. But that doesn’t say whether infinities *themselves* are in the range (*e.g.*, for `inf+0`), and isn’t normative for C++ anyway. – Davis Herring May 12 '19 at 13:45
  • 1
    Now we know that there are two floating point worlds: the IEC 559 world, and the `asdf` world. – L. F. May 13 '19 at 09:39

2 Answers2

2

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.

L. F.
  • 19,445
  • 8
  • 48
  • 82
  • 3
    Where is the "merely undefined behavior" coming from? – T.C. May 13 '19 at 16:50
  • 1
    As far as I understand, your answer still contains non-valid things: according to the quote, the `createInfinity` is surely not a core constant expression. The first two paragraphs still written in a way, which means that it could be CCE. – geza May 14 '19 at 08:33
  • @T.C. I am sorry. I was daydreaming. I have rewritten the answer. – L. F. May 14 '19 at 11:52
  • @geza I am sorry. I was looking at the wrong part in the standard. Now I have rewritten my answer and please help me spot mistakes. – L. F. May 14 '19 at 11:53
  • 1
    Seems OK for me :) The only thing I could argue with is that whether infinity is part of the range or not. In some sense, it is part of the range, as with iec559, a float can represent infinity. At least, if I was responsible for this decision, I'd definitely write the standard in a way that OP's program would be well-formed. – geza May 14 '19 at 12:55
  • @L.F. So, even when dropping the `constexpr` and executing the function, it's UB? So what's than the point if having this function in `nummeric_limits`? – JVApen May 15 '19 at 06:43
  • @JVApen Well, I’m not sure. Maybe I am wrong regarding the fact that `numeric_limits::infinity()` is `constexpr`. I will delete this answer concerning the controversy caused by this answer. – L. F. May 15 '19 at 06:57
  • You can keep it, it's interesting! – JVApen May 15 '19 at 06:58
  • @L.F. Even Nan is constexpr in it, understand if you can :( – JVApen May 15 '19 at 06:59
  • @JVApen Maybe `infinity()` and `nan()` aren't considered math at all and merely passing values is not undefined behavior. That's only a guess, anyway. – L. F. May 15 '19 at 09:03
  • Side note: Trying the example again with Clang-trunk (9.0.0) -> compiles as expected, doing the same with 8.0.0 gives the failure. Checking now why this changed – JVApen Jul 07 '19 at 08:18
  • 1
    @L.F.: Part of the (woefully incomplete) current thinking is that *producing* a non-finite value isn’t a constant expression, but that explicitly asking for one is fine. – Davis Herring Jul 11 '19 at 17:49
  • @JVApen I have thought better, I'd say this is ill-formed. See updated answer. – L. F. Jul 12 '19 at 09:00
  • @DavisHerring Yeah, producing a value that is too big is UB instead of automatically infinity. – L. F. Jul 12 '19 at 09:01
2

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");
JVApen
  • 11,008
  • 5
  • 31
  • 67
  • 2
    It would be very interesting to see the standards contradict with each other. Chaotic. – L. F. Jul 07 '19 at 10:08