How does C++ integer division work for limit and negative values?
Asked Answered
B

3

16

I am facing some strange results with integer division in C++. I am trying to calculate this: -2147483648 / -1.

What I get is 3 different results in 3 different scenarios:

int foo(int numerator, int denominator) {
    int res = numerator / denominator; // produces SIGFPE, Arithmetic exception interrupt

    cout << res << endl;
}

int main() {
    int res = -2147483648 / -1;
    cout << res << endl;               // prints -2147483648
    cout << -2147483648 / -1 << endl;  // prints 2147483648
    foo(-2147483648, -1);
    return 0;
}

Why does the integer division operation produces different results in different situations?

Bliss answered 4/8, 2016 at 14:13 Comment(10)
Worth mentioning that code does not compile on Windows VS-2015 saying negative integral constant converted to unsigned type and unary minus operator applied to unsigned type, result still unsigned on all -2147483648 / -1 linesWiring
Simple answer here.Prochora
This is how visual studio does it: #define INT_MIN (-2147483647 - 1) // minimum (signed) int valueProchora
@flatmouse if that is the definition it's pretty clear that MSVC does not make use of signed integer overflow UB. Incredible that UB as a practice is so fundamentally anchored on that platform.Ferbam
@Ferbam I'm not sure I follow. 2147483647 is DB, -2147483647 is still ok, and finally -2147483647 - 1 would still be DB as it would fit? Of course, in the program above overflow is unavoidable as (-2147483647 - 1) / -1 would still overflow and be UB.Prochora
@flatmouse oh I though you had mistyped and meant INT_MAX (-2147483648 - 1). Why is VC using what you quoted? What's the reason?Ferbam
@Ferbam 2147483648 is UB even before the unary - is considered. So the VC define gets around the problem by avoiding the 2147483648 literal.Prochora
@flatmouse: A literal 2147483648 does not produce UB. "The type of an integer literal is the first of the corresponding list in Table 5 in which its value can be represented." It's guaranteed to fit in unsigned int, which isn't in the list so it seems wrong that Visual C++ will choose it. Also guaranteed to fit in long long int, which is why it is not UB.Quinquereme
@BenVoigt Good point. 2147483648; is ok. And so is auto test{2147483648};. I am surprised that VS will use an unsigned type, contrary to what is stipulated in the standard. Yet I found that it uses unsigned long. I wonder why table 5 lists the unsigned types for binary, octal and hex, but not decimal literals?Prochora
@flatmouse: I guess they erroneously used the list that's applicable for integral promotions, namely "int, unsigned int, long int, unsigned long int, long long int, or unsigned long long int" Or they used the list intended for binary/octal/hexadecimal literals instead of the one for decimal literals. As for why the rules are different for binary/octal/hex literals, I believe it's because the programmer is then controlling the length of the literal, in bits. Actually, I'd like to see auto x = 0x0000000000000001; automatically become whatever basic type meets the int64_least_t ideaQuinquereme
S
13

The literal -2147483648 / -1 is calculated by your compiler as 2147483648 in a data type that is wide enough to hold that value.

When the literal is printed out directly, it prints the value correctly.

When the literal is stored in res, it is cast to an int. An int appears to be 32 bits wide on your system. The value 2147483648 cannot be represented as a 32 bit signed integer, so the cast causes an overflow. On your system, this overflow results in the value -2147483648 (likely it's using two's complement).

Finally, when trying to perform the division at runtime (in the foo function), the SIGFPE exception occurs due to the overflow (because the int datatype cannot represent the result).

Note that all of these three options rely on platform dependent behavior :

  • the fact that the compiler doesn't generate any errors (or other issues) when the literal calculation overflows and just uses a data type large enough to hold the result
  • the fact that the int overflow when storing the literal generates that specific value (and no other issues)
  • the fact that the SIGFPE exception is thrown when overflowing at runtime
Sphalerite answered 4/8, 2016 at 14:22 Comment(4)
AFAICT integer overflow during compilation is Undefined Behavior; using a larger datatype is one possible behavior but as FirstSTep shows, another behavior is to error out.Businesslike
@Businesslike : yes, I state as much in the last paragraph.Sphalerite
Which is also a wrong-ish: every platform has to support a wide enough datatype; long long int is certainly wide enough.Businesslike
@Businesslike : you're right - I'm in the habit of answering questions tagged with just "C++" according to C++03, but since it's been a while since C++11 (and C++14 for that matter) came out, I should get with the times and adjust that habit. I've re-worded that part of my answer accordingly.Sphalerite
H
12
int res = -2147483648 / -1;
cout << res << endl;               // prints -2147483648
cout << -2147483648 / -1 << endl;  // prints 2147483648
int res = numerator / denominator; // produces SIGFPE, Arithmetic exception interrupt

Note there're no negative integer literals.

There are no negative integer literals. Expressions such as -1 apply the unary minus operator to the value represented by the literal, which may involve implicit type conversions.

The literal 2147483648 is larger than the max value of int, so its type will be long (or long long, depends on implementation). Then -2147483648 's type is long, and the result of calculation (-2147483648 / -1) is long too.

For the 1st case, the result 2147483648 of type long is implicitly converted to int, but it's larger than the max value of int, the result is implementation-defined. (It seems the result is wrapped around according to the rules of the representation (2's complement) here, so you get the result -2147483648.)

For the 2nd case, the result with type long is printed out directly, so you get the correct result.

For the 3rd case, you're doing the calculation on two ints, and the result can't fit in the result type (i.e. int), signed integer arithmetic operation overflow happened, the behavior is undefined. (Produces SIGFPE, Arithmetic exception interrupt here.)

Hock answered 4/8, 2016 at 14:32 Comment(5)
-2147483648 could be long long, e.g. on MSVC (which has LONG_MAX=2147483647)Businesslike
iirc in older versions of C++ it could also be a 32-bit unsigned long (which doesn't actually change the final result, but it does change how you get there).Galleywest
@Galleywest I don't know much about "older" C++. :) For now, it'll be long or long long (since C++11), it won't be unsigned unless use suffix u. The representation is implementation-defined, so long might be represented by 32-bit integer.Hock
@Galleywest I found it here, "In C prior to C99 (but not in C++), unsuffixed decimal values that do not fit in long int are allowed to have the type unsigned long int. "Hock
Great explanation! This post should be the accepted answer.Loupe
B
11

Your outcome might be INT_MAX+1, in other words it probably overflows. That is Undefined Behavior, and anything can happen. For instance, a compiler may reject the code outright.

(A system might have INT_MAX >= 2147483648, but then you would expect the same result for your 3 testcases)

Businesslike answered 4/8, 2016 at 14:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.