May 7, 2019

Learning C++, day three: Integer overflow

I was happily playing with my shiny new prime-number-checker, trying out how loops work in C++.

When I started entering stupidly big numbers, something strange happened.

./main
Enter a number: 5784320578432578493207508493
Congratulations, it's prime!

Except, that is not actually a prime number. I can’t have typed a prime number by randomly banging on the keypad. What’s going on?

I didn’t code any input sanitization in my prime-number-checker, so let’s check what my program actually gets. I am now removing all the boring logic, and compiling this simple code:

// Main.cpp

#include <iostream>
using std::cin;
using std::cout;
using std::endl;

int main()
{
	int x;

	cout << "Enter a number: ";
	cin >> x;

	cout << "You entered " << x << endl;

	return 0;
}

Let’s play.

./main
Enter a number: 2147483647 <-- int32 maximum positive value
You entered 2147483647

./main
Enter a number: 2147483648 <-- int32 maximum positive value +1
You entered 2147483647

./main
Enter a number: 99999999999999999999999999999999999999999999999999 <-- a lot
You entered 2147483647

With this, the first mystery is solved: I was always checking the same value, no matter what big number I was entering. And it happened to be prime (yes: (2^32)/2-1 is indeed prime!).

But now I have another problem: why do I always get the same number? The variable x is a 32-bit integer and it can’t hold those big numbers. But still, I was expecting the integer overflow to wrap around the maximum value.

For example: 2147483647 is the maximum value for a signed 32-bit integer. I expected the input 2147483648 to be stored in x as the smallest possible value: -2147483648.

Instead, I get 2147483647 every time.

Let’s try something different.

./main
Enter a number: I am not a number, I am a free man!
You entered 0

Woops. OK there’s some magic going on here, and it is not about integer representation. It’s std::cin doing the magic!

We need some differential diagnosis. I am now removing std::cin and hardcoding the values.

// Main.cpp

#include <iostream>
using std::cout;
using std::endl;

int main()
{
	// By the way: "int" has its constructor overloaded, too!
	int x(2147483647);

	cout << "2147483647   = " << x << endl;

	x = x + 1;
	cout << "2147483647+1 = " << x << endl;

	return 0;
}
./main
2147483647   = 2147483647
2147483647+1 = -2147483648

OK, that’s better! Finally, a good old wrapping integer overflow.