Skip to content
C++ Better Explained
Go back
INT_MAX and numeric_limits in C++: Finding a Type's Range

INT_MAX and numeric_limits in C++: Finding a Type’s Range

Every numeric type in C++ has a ceiling. Push past it and your program doesn’t error out — it quietly produces a wrong answer, often a large negative number where you expected a large positive one.

Knowing how to ask a type for its range is the first step to writing code that doesn’t hit that wall.


The Quick Answer

#include <iostream>
#include <limits>

int main() {
    std::cout << "int max:       " << std::numeric_limits<int>::max() << "\n";
    std::cout << "int min:       " << std::numeric_limits<int>::min() << "\n";
    std::cout << "long long max: " << std::numeric_limits<long long>::max() << "\n";
    std::cout << "double max:    " << std::numeric_limits<double>::max() << "\n";
    return 0;
}

Output on a typical 64-bit system:

int max:       2147483647
int min:       -2147483648
long long max: 9223372036854775807
double max:    1.79769e+308

An int is 32 bits: one for the sign, 31 for the value. That gives 2³¹ − 1 = 2,147,483,647 as the maximum. The minimum is one further from zero because zero occupies a slot on the positive side.


numeric_limits vs INT_MAX

You’ll see both in real code:

#include <climits>   // INT_MAX, LONG_MAX, CHAR_BIT, ...
#include <limits>    // std::numeric_limits

int a = INT_MAX;
int b = std::numeric_limits<int>::max();   // identical value

INT_MAX is a C macro — short and familiar. std::numeric_limits is the C++ way and is better for one concrete reason: it’s a template, so it works with a type you don’t know yet.

template <typename T>
T findMax(const std::vector<T>& values) {
    T largest = std::numeric_limits<T>::lowest();   // works for any T
    for (const T& v : values) {
        if (v > largest) largest = v;
    }
    return largest;
}

There’s no macro that can do that. See C++ templates explained for why generic code needs this.


min() vs lowest(): The Floating-Point Gotcha

This trips up nearly everyone the first time:

#include <iostream>
#include <limits>

int main() {
    std::cout << "int    min: " << std::numeric_limits<int>::min()    << "\n";
    std::cout << "double min: " << std::numeric_limits<double>::min() << "\n";
    std::cout << "double low: " << std::numeric_limits<double>::lowest() << "\n";
    return 0;
}

Output:

int    min: -2147483648
double min: 2.22507e-308
double low: -1.79769e+308

For floating-point types, min() means the smallest positive value, not the most negative one. If you initialise a “find the maximum” loop with numeric_limits<double>::min(), every negative input will fail to beat your starting value and you’ll get a wrong answer.

Use lowest() when you want the most negative value. It does the right thing for both integers and floats.

If you're looking to go deeper with C++, the C++ Better Explained Ebook is perfect for you — whether you're a complete beginner or looking to solidify your understanding. Just $19.

Watching an Overflow Happen

#include <iostream>
#include <limits>

int main() {
    int big = std::numeric_limits<int>::max();
    std::cout << "Before: " << big << "\n";

    ++big;                                    // undefined behaviour
    std::cout << "After:  " << big << "\n";

    return 0;
}

Typical output:

Before: 2147483647
After:  -2147483648

The value wrapped from the largest positive to the most negative — the bit pattern rolled over into the sign bit.

But note the comment: signed overflow is undefined behaviour, not “wrap around.” The wrap is what the hardware happens to do. The compiler is permitted to assume it never happens, which is why this check does not work:

if (big + 1 < big) {           // compiler may delete this entirely
    std::cout << "overflowed\n";
}

The optimiser reasons “overflow can’t happen, so big + 1 is always greater than big, so this branch is dead” and removes it. Check before the operation instead:

#include <limits>

bool canAdd(int a, int b) {
    if (b > 0 && a > std::numeric_limits<int>::max() - b) return false;
    if (b < 0 && a < std::numeric_limits<int>::min() - b) return false;
    return true;
}

Unsigned types are different: unsigned overflow is defined to wrap around. That’s also why mixing signed and unsigned causes surprises — see size_t in C++.


Other Useful numeric_limits Members

#include <iostream>
#include <limits>

int main() {
    std::cout << "digits10:  " << std::numeric_limits<double>::digits10  << "\n";
    std::cout << "epsilon:   " << std::numeric_limits<double>::epsilon() << "\n";
    std::cout << "is_signed: " << std::numeric_limits<char>::is_signed   << "\n";
    return 0;
}

epsilon() is the principled answer to the “never compare doubles with ==” rule from float vs double:

#include <cmath>
#include <limits>

bool nearlyEqual(double a, double b) {
    return std::fabs(a - b) <= std::numeric_limits<double>::epsilon() * std::fabs(a + b);
}

Picking a Type That Fits

TypeTypical range
int±2.1 billion
unsigned int0 to 4.3 billion
long long±9.2 quintillion
float~7 significant digits
double~15 significant digits

If a value might exceed roughly two billion — factorials, file sizes in bytes, millisecond timestamps, accumulated counters — reach for long long from the start. See C++ variables and data types for the complete list.



Take Your C++ Further

If you’re looking to go deeper with C++, the C++ Better Explained Ebook is perfect for you — whether you’re a complete beginner or looking to solidify your understanding. Just $19.

👉 Get the C++ Better Explained Ebook — $19

📋

Free Download: The 10 Mistakes Every C++ Beginner Makes

A free 1-page checklist that shows the exact traps that slow down every C++ beginner — so you can avoid them from day one.

🔒 No spam. Unsubscribe anytime.


Share this post on:

Written by

Sahil Bora

Software Engineer. Author and creator of C++ Better Explained.


Previous Post
push_back vs emplace_back in C++: What's the Real Difference?
Next Post
Quick Sort in C++: How It Works, With Full Code and Complexity

Keep Learning