C++: Which floating-point operations, if any, are guaranteed by the standard to be non-throwing?

215 Views Asked by At

In C++20 (or later), are there any operations involving floating point numbers that are guaranteed by the standard to never throw?

What about

  1. assignment and copy construction?
  2. comparison?
  3. arithmetic?

Note, I am concerned here only with ordinary regular C++ exceptions, not the special notion of "floating-point exceptions".

Here is a more precise formulation of my question:

If a and b are mutable objects of type double, which of the following expressions, if any, are guaranteed by the C++ standard to evaluate to true?

  1. noexcept(a = b)
  2. noexcept(a == b)
  3. noexcept(a + b)

For new readers, I'd like to clarify that I am only interested in "defined behavior", that is, everything that is not undefined behavior.

2

There are 2 best solutions below

1
n. m. could be an AI On BEST ANSWER

Note 1 An exception can be thrown from one of the following contexts: throw-expressions (expr.throw), allocation functions (basic.stc.dynamic.allocation), dynamic_cast (expr.dynamic.cast), typeid (expr.typeid), new-expressions (expr.new), and standard library functions (structure.specifications).

Floating point operators are none of those.

Notes are not normative, but they are good enough for me.

16
Jan Schultke On

It's important to note that floating point exception refers to exceptions such as FE_DIVBYZERO or FE_INEXACT defined in <cfenv>. These are not exceptions in the std::exception sense, but flags in the floating point environment which can be checked using std::fexceptflag.

Exceptions could be thrown as the result of undefined behavior

That being said, floating point operations could in principle throw regular exceptions:

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 varies among machines, and is sometimes adjustable by a library function. — end note]

- [expr.pre] p4

This paragraph could come into action when a floating point operation produces an undefined result. Division by zero is generally undefined behavior in C++, and no exception is made for floating point numbers, not even when std::numeric_limits<float>::is_iec559 is true.

The implementation could choose to throw an exception when division by zero is performed simply because division by zero is UB, and so the implementation can do anything it wants then. This is one of the reasons why functions with narrow contracts (see Lakos Rule) aren't marked noexcept, even those from the C standard library, which otherwise cannot throw (see [res.on.exception.handling] p2).

noexept(...) is true for floating point operations regardless

  1. noexcept(a = b)
  2. noexcept(a == b)
  3. noexcept(a + b)

Also, all of these are guaranteed to be true because the noexcept operator only considers whether an exception is potentially throwing. It does not consider whether an exception could be thrown as the result of undefined behavior.

Equality comparison (see [expr.eq]) and assignment (see [expr.ass]) are never UB, and so they cannot even throw by that means (unless uninitialized values are used). See [except.throw] Note1 for a list of expressions that can throw under normal circumstances.

Conclusion

Floating point exceptions are different from regular exceptions; they only have the name in common. It is theoretically possible that traditional exceptions are thrown when floating point operations run into UB, although no compiler does this in practice.