Fundamental Data Types and Integer Overflow
Variables and Data Types

1.3 Fundamental Data Types and Integer Overflow

C++ gives you direct access to memory, which means you have to choose exactly how many bytes of RAM your variables use. If you select a type that is too small, your values will overflow. If you choose one that is too large, you waste cache space.

Let's break down the basic primitive types in C++, their typical sizes, and the systems hazards you need to watch out for.

Primitive Types and Byte Widths

Unlike languages where all numbers are 64-bit floats under the hood, C++ has specific integer and floating point types:

* char: 1 byte (stores characters or small integers from -128 to 127).

* short: 2 bytes (integers from -32,768 to 32,767).

* int: 4 bytes (integers from -2.1 billion to 2.1 billion).

* long: Typically 4 bytes on Windows, but 8 bytes on macOS and Linux (which is a common source of porting bugs!).

* long long: 8 bytes (integers from -9 quintillion to 9 quintillion).

* float: 4 bytes (single precision floating point).

* double: 8 bytes (double precision floating point).

The Signed vs Unsigned Comparison Hazard

In C++, integers can be signed (can store positive and negative numbers) or unsigned (can only store positive numbers and zero).

Here is a critical trap: never mix signed and unsigned integers in comparisons. If you compare them, C++ will implicitly convert the signed integer to an unsigned integer. This leads to silent logic bugs:

#include <iostream>

int main() {
    int x{-5};
    unsigned int y{2};

    if (x < y) {
        std::cout << "This will NOT print!\n";
    } else {
        std::cout << "This prints! x is converted to: " << (unsigned int)x << "\n";
    }
    return 0;
}
The signed vs unsigned comparison hazard.

Why does this happen? Because -5 in binary is represented in two's complement. When converted to an unsigned int, it becomes 4,294,967,291 on 32-bit systems, which is obviously much larger than 2!

Integer Overflow and Underflow

What happens if you try to store a number that exceeds a variable's maximum capacity?

C++ handles this in two different ways depending on whether the type is signed or unsigned:

1. Unsigned Overflow (Defined Wrap around)

For unsigned integers, overflow is fully defined by the C++ standard. It wraps around using modulo arithmetic. If you add 1 to the maximum unsigned value, it goes back to 0:

unsigned short u{65535}; // Maximum value for 16-bit unsigned int
u = u + 1;
std::cout << u; // Prints 0 (wraps around)
Unsigned integer wrap around behavior.

2. Signed Overflow (Undefined Behavior)

For signed integers, overflow is undefined behavior (UB). The C++ standard makes no guarantees about what happens. The hardware might wrap it around to a negative number, freeze, or the compiler might optimize away your overflow checks entirely because it assumes signed overflow never happens.

short s{32767}; // Maximum value for 16-bit signed int
s = s + 1;      // Undefined Behavior! Never write code that triggers this.
Signed overflow triggers Undefined Behavior.

To write safe systems code, always check your values before performing arithmetic if they are close to their limits.

Floating Point Epsilon Comparison Hazards

Unlike integers, floating point numbers (like float and double) cannot represent decimals exactly in base-2 binary format. For example, adding 0.1 and 0.2 in C++ results in 0.30000000000000004 rather than exactly 0.3.

Because of this tiny precision loss, comparing floating point numbers directly using the double-equals operator == is a critical systems bug. The check will almost always return false:

double a{0.1 + 0.2};
double b{0.3};
if (a == b) {
    // This code will almost never run!
}
The floating point comparison bug.

To safely compare floating point variables, you must check if the absolute difference between them is smaller than a tiny tolerance value called Epsilon:

#include <cmath> // Required for std::abs
#include <iostream>

int main() {
    double a{0.1 + 0.2};
    double b{0.3};
    constexpr double epsilon{0.00001};

    if (std::abs(a - b) < epsilon) {
        std::cout << "They are equal within tolerance!\n";
    }
    return 0;
}
Safe float comparison using epsilon thresholds.
Finished reading this lesson?