2

This

double what;
for (int i = 1; i < (long)pow(10, 7); i++)
    what = (i + i) / (i * i) - i; 

raises Floating point exception (core dumped). Why? I'm using clang++.

  • 2
    @FrançoisAndrieux Not true, the division `(i + i) / (i * i)` takes precedence over the subtraction. – dfrib Jun 11 '20 at 14:10
  • @FrançoisAndrieux Division by zero would be UB so anything, including floating point error would be possible. – eerorika Jun 11 '20 at 14:10
  • Depending on your size of `int` then `i * i` may overflow when close to `pow(10, 7)` – Richard Critten Jun 11 '20 at 14:12
  • @dfri Oops. you're right. – François Andrieux Jun 11 '20 at 14:12
  • 1
    `i * i` overflows as `i` approaches `pow(10, 7)` – Kevin Jun 11 '20 at 14:13
  • It's more likely that `i * i` overflows, results in UB and possibly and by coincidence yields a value of `0`, thus leading to, by post-UB division by zero, more by UB coincidence, a floating point exception. – dfrib Jun 11 '20 at 14:13
  • If you're trying to figure this out, look at the value of `i` when the exception is thrown. That info should really be included in the question. – Caleb Jun 11 '20 at 14:33

3 Answers3

2

Depending on you platform, int is likely 32 or 64 bit wide.

From [basic.fundamental]/2 and [basic.fundamental]/3 [extract, emphasis mine]:

[basic.fundamental]/2

There are five standard signed integer types : “signed char”, “short int”, “int”, “long int”, and “long long int”. In this list, each type provides at least as much storage as those preceding it in the list. [...] Plain ints have the natural size suggested by the architecture of the execution environment; the other signed integer types are provided to meet special needs.

[basic.fundamental]/3

For each of the standard signed integer types, there exists a corresponding (but different) standard unsigned integer type: “unsigned char”, “unsigned short int”, “unsigned int”, “unsigned long int”, and “unsigned long long int”, each of which occupies the same amount of storage and has the same alignment requirements as the corresponding signed integer type; [...]

The signed and unsigned integer types shall satisfy the constraints given in the C standard, section 5.2.4.2.1.

We could go to the C11 Standard draft [extract, emphasis mine]:

5.2.4.2.1 Sizes of integer types <limits.h>

[...] Their implementation-defined values shall be equal or greater in magnitude (absolute value) to those shown, with the same sign.

[...]

  • maximum value for an object of type int: INT_MAX +32767

[...]

This doesn't help us without knowing target/architectural details, however, so to simplify your questions, let's consider the example using fixed-width signed integers instead, and note that the following example is "fine" (in this context):

#include <cstddef>
#include <math.h>

int main() {    
    double what;
    for (int32_t i = 1; i < (int32_t)pow(10, 4); i++)
    {
        what = (i + i) / (i * i) - i; 
    }
    (void)what;
    return 0;
}

whereas the following results in a "Floating point exception" for the particular executions I have attempted (UB; dragons may fly out of our noses, see below):

#include <cstddef>
#include <math.h>

int main() {    
    double what;
    for (int32_t i = 1; i < (int32_t)pow(10, 5); i++)
    {
        what = (i + i) / (i * i) - i; 
    }
    (void)what;
    return 0;
}

The key here is that the maximum value of an int32_t is 2,147,483,647, which means i * i will overflow for values of the magnitude of pow(10, 5). Signed integer overflow is undefined behaviour (UB), and from there on anything goes. Likely in this case is that the UB, by coincidence, yields a value 0 from the overflow of the expression i * i, which in turn leads to division by zero (UB again) in the expression (i + i) / (i * i), which, by further coincidence, is likely to be the root case of the Floating point exception.

I'm emphasizing on by coincidence here, as any point beyond UB makes for a useless target for logical analysis; the compiler vendor may assume we never have UB and does anything goes once we've entered the domain of UB. Any results we see should be considered coincidental, unless you are working in some non-standard dialect of C++ on a particular target architecture and hardware where e.g. a particular case such as signed integer overflow may be defined as non-standard implementation-defined (and thus specified by the particular implementation) rather than UB.

dfrib
  • 70,367
  • 12
  • 127
  • 192
1

Your int overflows because 10^14 fits in no int that I have ever seen (note that int is platform dependent). On some platforms, 10^7 will already overflow. Signed overflow always results in undefined behaviour and sometimes results in 0.

  • You might want to use an adequately sized type such as std::uint64_t or double directly to store the loop variable.
  • You compute pow for every single loop iteration. Compute this outside the loop once.
  • Your loop appears to throw away all but the last computation. Perhaps you could just compute that one?
bitmask
  • 32,434
  • 14
  • 99
  • 159
  • `pow` not being `constexpr` shouldn't affect anything. Even a `constexpr` function might be recalculated on every iteration in a debug build; and even a non-`constexpr` function might be called only once before the loop as an optimization. – HolyBlackCat Jun 11 '20 at 14:33
  • @HolyBlackCat As I understand constexpr functions, they must be evaluated at compile time if their arguments all are constexpr. This should not depend on any definition of what a debug build is. – bitmask Jun 11 '20 at 14:36
  • From the behavior of the program alone, it should be impossible to determine if it's evaluated once or not (and if it's evaluated at compile-time or not, if you don't use the result in a constexpr context). As I understand it, because of the as-if rule the standard can't force the evaluation to happen at compile-time. – HolyBlackCat Jun 11 '20 at 14:38
  • @HolyBlackCat You are correct. I just checked the standard. Do you suppose any compiler would actually defer the call to run time? – bitmask Jun 11 '20 at 14:47
  • I've just tested it, and at least GCC seems to do it. Example: https://gcc.godbolt.org/z/C8w9MA You can check if the function is called several times either by putting a breakpoint in it, or by looking at the assembly. – HolyBlackCat Jun 11 '20 at 14:57
  • @HolyBlackCat A program containing a constexpr function without arguments is a no diagnostic-required-ill-formed program. Yes, you read that correctly. – bitmask Jun 11 '20 at 14:58
  • Anyway, you seem to be correct that constexpr functions seem to not be resolved at compile time on -O0. If you crank up optimisation to at least -O1, you see `f` disappear. – bitmask Jun 11 '20 at 15:01
  • *"program containing a constexpr function without arguments is a no diagnostic-required-ill-formed"* Huh?! Do you have a citation/source for that? – HolyBlackCat Jun 11 '20 at 15:03
  • @HolyBlackCat That's how I understand [decl.constexpr] 5: "*For a non-template, non-defaulted constexpr function or a non-template, non-defaulted, non-inheriting constexpr constructor, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression (5.19), the program is ill-formed; no diagnostic required.*" – bitmask Jun 11 '20 at 15:05
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/215752/discussion-between-holyblackcat-and-bitmask). – HolyBlackCat Jun 11 '20 at 15:08
  • For people coming here later. [**I was wrong about my interpretation of the standard.**](https://stackoverflow.com/a/62327866/430766). – bitmask Jun 11 '20 at 15:31
1

The i < (long)pow(10, 7) condition in your for loop causes the integer (i * i) expression to become larger than its maximum value, resulting in integer overflow and undefined behavior. On some implementations, i might become 0 or 1, causing the following expression: (i / i) to become 0. This in turn might (in Visual Studio), for example, result in the Integer Division By Zero exception during the debugging.

Ron
  • 14,674
  • 4
  • 34
  • 47