Variables, Types and Overflow
Why an int has a maximum value, what happens when you exceed it, and the type mistakes that silently produce wrong answers.
Every variable has a fixed size
In Python an integer grows as large as it needs to. In C++ it does not. When you writeint x;, you reserve a fixed block of memory — normally 4 bytes — and that block can only represent so many values.
| Type | Typical size | Range |
|---|---|---|
int | 4 bytes | −2,147,483,648 to 2,147,483,647 |
long long | 8 bytes | ±9.2 × 10¹⁸ |
unsigned int | 4 bytes | 0 to 4,294,967,295 |
float | 4 bytes | ~7 decimal digits of precision |
double | 8 bytes | ~15 decimal digits |
char | 1 byte | −128 to 127 |
bool | 1 byte | true or false |
The exact sizes are implementation-defined, but the table above holds on essentially every platform you will meet.
Overflow is silent
This is the mistake that costs people marks in coding rounds:
int a = 2000000000;
int b = 2000000000;
int sum = a + b; // expected 4,000,000,000
std::cout << sum << '\n'; // prints -294967296
There is no error, no warning at runtime, no exception. The value simply wraps around and every subsequent calculation is wrong.
int maxes out around 2.1 billion. The moment your intermediate result can exceed that, you need long long:
long long a = 2000000000;
long long b = 2000000000;
long long sum = a + b; // 4000000000, correct
The subtle version
Declaring the result aslong long is not enough if the arithmetic happens in int:
int a = 100000;
int b = 100000;
long long wrong = a * b; // overflows BEFORE the assignment
long long right = (long long)a * b; // promotes first, then multiplies
a * b is computed as int × int → int, overflows, and then the broken value is widened to long long. Casting one operand first forces the whole expression into 64-bit arithmetic.
Rule of thumb for interviews: if the problem says values up to 10⁹ and asks you to multiply or sum them, reach for long long immediately.
Integer division truncates
std::cout << 7 / 2 << '\n'; // 3, not 3.5
std::cout << 7.0 / 2 << '\n'; // 3.5
std::cout << 7 / 2.0 << '\n'; // 3.5
std::cout << (double)7 / 2 << '\n' // 3.5
-7 / 2 is -3, not -4.
A very common bug:
int total = 7, count = 2;
double average = total / count; // 3.0 — the damage is already done
double correct = (double)total / count; // 3.5
average as double does not help. The division happened in integer arithmetic before the assignment.
The modulo operator with negatives
std::cout << 7 % 3 << '\n'; // 1
std::cout << -7 % 3 << '\n'; // -1 (Python gives 2)
In C++ the result takes the sign of the left operand. Python differs, which trips people up when translating solutions. To force a non-negative result:
int mod(int a, int m) {
return ((a % m) + m) % m;
}
Floating point is not exact
double x = 0.1 + 0.2;
std::cout << (x == 0.3) << '\n'; // 0 — false!
0.1 and 0.2 cannot be represented exactly in binary, so their sum is 0.30000000000000004. This is not a C++ quirk; it applies to almost every language.
Never compare floating point with ==. Compare within a tolerance:
#include <cmath>
bool nearlyEqual(double a, double b, double eps = 1e-9) {
return std::fabs(a - b) < eps;
}
double over float. float gives only about 7 significant digits, which is rarely enough, and modern hardware is no slower with double.
Uninitialised variables hold garbage
int x;
std::cout << x << '\n'; // undefined behaviour — could be anything
0, may print -858993460, and may behave differently between debug and release builds — which is exactly what makes it so painful to debug.
Always initialise:
int x = 0;
int y{0}; // brace init — also rejects narrowing conversions
double z = 0.0;
Brace initialisation has a real advantage:
int a = 3.9; // compiles, silently truncates to 3
int b{3.9}; // compile error — narrowing conversion
The second form catches the mistake at compile time.
char is a number
char c = 'A';
std::cout << c << '\n'; // A
std::cout << (int)c << '\n'; // 65
std::cout << c + 1 << '\n'; // 66 — arithmetic promotes to int
std::cout << (char)(c + 1) << '\n'; // B
Characters are small integers holding an ASCII code. This is genuinely useful:
char lower = 'a' + (c - 'A'); // uppercase to lowercase
int digit = '7' - '0'; // character '7' to the number 7
'7' is ASCII 55; subtracting '0' (48) gives 7.
Try it
Predict each line before running.
-2147483648 (wraparound), 2 (truncation), 2, 0 (false), 122.
auto and const
auto asks the compiler to deduce the type:
auto count = 10; // int
auto price = 19.99; // double
auto name = std::string("Ada");
It is most valuable with long iterator types. Do not use it where the type matters to the reader.
const means the value cannot change after initialisation:
const int MAX_SIZE = 100;
MAX_SIZE = 200; // compile error
const that you accidentally try to modify becomes a compile error rather than a bug.
Common mistakes
- Using
intwhen values exceed 2.1 billion. Silent wraparound, no warning. - Casting after the overflow instead of before.
(long long)a * b, not(long long)(a * b). - Expecting
int / intto give a decimal. - Comparing doubles with
==. - Reading an uninitialised variable. Works on your machine, fails on the judge.
- Assuming
-7 % 3is positive. It is-1in C++. - Using
floatwhere you need precision. Preferdouble.
Summary
C++ types have fixed sizes, and exceeding them wraps silently rather than raising an error. Reach forlong long when values pass 10⁹, cast before multiplying rather than after, remember that integer division truncates, never compare floating point with ==, and always initialise your variables.