Skip to content

C++ Data Types with Sizes and Ranges

Every variable in C++ has a data type. The type decides three things: what kind of value the variable can hold (whole number, decimal, character, true/false), how much memory it uses, and which operations are allowed on it. Choosing the right type is about correctness as much as memory: the wrong type can silently lose decimals, wrap a negative number into a huge positive one, or compare money values incorrectly.

This lesson follows on from variables. Every output comes from real programs compiled with g++ 14.2 on 64-bit Linux; where Windows differs, the difference is called out.

Categories of data types

CategoryTypes
Fundamental (built-in)int, short, long, long long, char, bool, float, double, long double, void, wchar_t
DerivedArrays, pointers, references, functions
User-definedclass, struct, enum, union, type aliases
Standard library typesstd::string, std::vector and many more (classes provided by the library)

This lesson focuses on the fundamental types, plus std::string, which you'll use from day one.

The fundamental types

TypeStoresExample
intWhole numbers. The default choice for integersint age{25};
short, long, long longSmaller or larger whole numberslong long population{8'100'000'000};
floatDecimal number, about 6-7 significant digitsfloat temp{36.6f};
doubleDecimal number, about 15-16 significant digits. The default for decimalsdouble price{499.99};
charA single character (stored as a small integer)char grade{'A'};
booltrue or falsebool isActive{true};
void"No type": used for functions that return nothingvoid printReport();
std::stringText of any length (from <string>, not built in)std::string name{"Ravi"};

Sizes with sizeof

The sizeof operator returns the size of a type or variable in bytes:

std::cout << "int         : " << sizeof(int) << " bytes\n";
// ...the same for every type
Output (g++ on 64-bit Linux)
bool        : 1 byte
char        : 1 byte
short       : 2 bytes
int         : 4 bytes
long        : 8 bytes
long long   : 8 bytes
float       : 4 bytes
double      : 8 bytes
long double : 16 bytes
wchar_t     : 4 bytes

Sizes are not the same everywhere. The C++ standard only guarantees minimums and an order: sizeof(char) is always 1, and short <= int <= long <= long long. On 64-bit Windows (both MSVC and MinGW), long is 4 bytes, not 8, and wchar_t is 2 bytes; with MSVC, long double is 8 bytes. When the exact size matters (file formats, network protocols), use the fixed-width types from <cstdint>: std::int32_t, std::int64_t, std::uint8_t.

Ranges with std::numeric_limits

The size decides the range. You can ask the compiler for the exact limits with std::numeric_limits from <limits>:

std::cout << std::numeric_limits<int>::min() << " to "
          << std::numeric_limits<int>::max() << '\n';
Output
short          : -32768 to 32767
unsigned short : 0 to 65535
int            : -2147483648 to 2147483647
unsigned int   : 0 to 4294967295
long long      : -9223372036854775808 to 9223372036854775807
float  digits  : 6
double digits  : 15
double max     : 1.79769e+308

An int tops out at about 2.1 billion. That's enough for ages and marks, but not for a country's population, a file size in bytes, or an amount in paise. Use long long for those.

Signed and unsigned

Integer types are signed by default (they can be negative). Adding unsigned removes negative values and doubles the positive range, as the unsigned short and unsigned int lines above show. The modifiers are signed, unsigned, short and long, and they can be combined: unsigned long long.

Overflow: what happens past the limit

unsigned int stock = 0;
stock = stock - 1;                     // unsigned values wrap around
std::cout << "0u - 1        = " << stock << '\n';

unsigned char small = 255;
small = small + 1;
std::cout << "255 + 1 (uchar) = " << static_cast<int>(small) << '\n';

long long population = 8'100'000'000;  // too big for int, fine for long long
std::cout << "population    = " << population << '\n';

int big = std::numeric_limits<int>::max();
long long safe = static_cast<long long>(big) + 1;
std::cout << "INT_MAX + 1 as long long = " << safe << '\n';
Output
0u - 1        = 4294967295
255 + 1 (uchar) = 0
population    = 8100000000
INT_MAX + 1 as long long = 2147483648
  • Unsigned overflow wraps around: 0 - 1 becomes the maximum value, and 255 + 1 in one byte becomes 0, like a car's odometer rolling over. This behaviour is defined by the standard, but it's rarely what you want: a stock count of 4,294,967,295 is a bug.
  • Signed overflow is undefined behaviour. INT_MAX + 1 on an int isn't guaranteed to give anything sensible. Convert to a bigger type before the arithmetic, as in the last line.
  • Use unsigned for bit patterns and sizes, not to say "this can't be negative". A plain int that becomes -1 is much easier to detect than a wrapped unsigned value.

Floating-point types: float and double

float  f = 1.0f / 3.0f;
double d = 1.0 / 3.0;

std::cout << std::setprecision(20);
std::cout << "float  1/3 = " << f << '\n';
std::cout << "double 1/3 = " << d << '\n';

double sum = 0.1 + 0.2;
std::cout << "0.1 + 0.2  = " << sum << '\n';
std::cout << std::boolalpha << "0.1 + 0.2 == 0.3 ? " << (sum == 0.3) << '\n';

std::cout << std::fixed << std::setprecision(2);
std::cout << "price with 2 decimals: " << 1499.5 << '\n';
Output
float  1/3 = 0.3333333432674407959
double 1/3 = 0.33333333333333331483
0.1 + 0.2  = 0.30000000000000004441
0.1 + 0.2 == 0.3 ? false
price with 2 decimals: 1499.50
  • float is accurate to about 7 digits (wrong from the 8th digit above); double to about 15-16. Use double by default.
  • Floating-point numbers are stored in binary, and most decimals (0.1, 0.2) can't be represented exactly. So 0.1 + 0.2 == 0.3 is false. Compare with a small tolerance instead: std::abs(a - b) < 1e-9.
  • For money, don't rely on double for exact totals. Store the amount in the smallest unit as an integer (paise or cents in a long long).
  • A decimal literal like 3.14 is a double; add f for a float (3.14f).
  • std::fixed and std::setprecision from <iomanip> control how many decimals are printed.

char and bool

char letter = 'A';
char next = letter + 1;          // characters are small integers

std::cout << "letter       : " << letter << '\n';
std::cout << "code of 'A'  : " << static_cast<int>(letter) << '\n';
std::cout << "letter + 1   : " << next << '\n';
std::cout << "char from 97 : " << static_cast<char>(97) << '\n';
std::cout << "'7' - '0'    : " << '7' - '0' << '\n';

bool isAdult = 20 >= 18;
std::cout << "isAdult      : " << isAdult << '\n';
std::cout << std::boolalpha << "isAdult      : " << isAdult << '\n';
std::cout << "bool from 42 : " << static_cast<bool>(42) << '\n';
std::cout << "bool from 0  : " << static_cast<bool>(0) << '\n';
Output
letter       : A
code of 'A'  : 65
letter + 1   : B
char from 97 : a
'7' - '0'    : 7
isAdult      : 1
isAdult      : true
bool from 42 : true
bool from 0  : false
  • A char stores a character's code (ASCII: 'A' = 65, 'a' = 97), so you can do arithmetic on it. '7' - '0' converts a digit character into the number 7, a classic trick in interview problems.
  • Use single quotes for a char: 'A'. "A" in double quotes is a string, a different type.
  • A bool is true or false. Any non-zero number converts to true, and zero converts to false.

Literals and their types

auto a = 42;          // int
auto b = 42u;         // unsigned int
auto c = 42L;         // long
auto d = 42LL;        // long long
auto e = 3.14;        // double
auto f = 3.14f;       // float
auto g = 'x';         // char
auto h = 0x1F;        // hexadecimal int
auto i = 0b1010;      // binary int (C++14)
auto j = 1'000'000;   // digit separators (C++14)
Output
0x1F = 31, 0b1010 = 10, 1'000'000 = 1000000
sizeof(42)   = 4, sizeof(42LL) = 8
sizeof(3.14) = 8, sizeof(3.14f) = 4
C++ Phase 1 has 11 characters

The suffix decides the literal's type. The last line uses std::string's length(): strings know their own size, and unlike a char, they can grow.

Type conversion

// Integer division throws away the fraction
int total = 7, people = 2;
std::cout << "7 / 2              = " << total / people << '\n';
std::cout << "7 % 2              = " << total % people << '\n';

// Implicit conversion: int is promoted to double
std::cout << "7 / 2.0            = " << total / 2.0 << '\n';

// Explicit conversion with static_cast
double share = static_cast<double>(total) / people;
std::cout << "static_cast result = " << share << '\n';

// double to int truncates (does not round)
double rating = 4.89;
int stars = static_cast<int>(rating);
std::cout << "(int)4.89          = " << stars << '\n';

// Mixing signed and unsigned
int balance = -1;
unsigned int limit = 10;
std::cout << std::boolalpha << "-1 < 10u           = " << (balance < limit) << '\n';

// Strings and numbers
std::string text = "250";
int value = std::stoi(text);
std::string back = std::to_string(value * 2);
std::cout << "stoi(\"250\") * 2    = " << back << '\n';
Output
7 / 2              = 3
7 % 2              = 1
7 / 2.0            = 3.5
static_cast result = 3.5
(int)4.89          = 4
-1 < 10u           = false
stoi("250") * 2    = 500
  • Integer division: when both operands are integers, the result is an integer and the fraction is discarded (7 / 2 = 3). % gives the remainder.
  • Implicit conversion (promotion): in 7 / 2.0, the int is automatically converted to double, the "wider" type, so the result is 3.5.
  • Explicit conversion: static_cast<double>(total) converts on purpose and is easy to search for. Prefer it over the old C-style cast (double)total.
  • double to int truncates toward zero: 4.89 becomes 4. Use std::round from <cmath> if you want 5.
  • Signed vs unsigned comparison: -1 < 10u is false. -1 is converted to unsigned and becomes 4,294,967,295. g++ warns about this with -Wall -Wextra: comparison of integer expressions of different signedness: 'int' and 'unsigned int'. Never ignore that warning.
  • std::stoi / std::stod convert text to numbers; std::to_string converts numbers to text.

Choosing the right type

DataRecommended type
Age, marks, quantity, loop counterint
Population, file size, timestamps, money in paiselong long or std::int64_t
Measurements, percentages, scientific valuesdouble
One character, a gradechar
Yes/no flagsbool
Names, messages, any textstd::string
Exact size required (binary formats)std::int32_t, std::uint8_t from <cstdint>

Interview questions

What is the size of int in C++?

It depends on the compiler and platform. The standard guarantees at least 16 bits, but on all modern desktop and server compilers it's 4 bytes (32 bits), range -2,147,483,648 to 2,147,483,647. Check with sizeof(int).

What is the difference between float and double?

float is usually 4 bytes with about 7 significant digits; double is 8 bytes with about 15-16 digits. double is the default for decimal literals and calculations.

What happens when an unsigned int goes below zero?

It wraps around to the largest value: 0u - 1 gives 4,294,967,295 for a 32-bit unsigned int. Signed overflow, by contrast, is undefined behaviour.

Why does 0.1 + 0.2 == 0.3 return false?

Floating-point numbers are stored in binary and can't represent 0.1, 0.2 or 0.3 exactly. The sum is 0.30000000000000004. Compare with a tolerance instead of ==.

What is the difference between implicit and explicit type conversion?

Implicit conversion is done automatically by the compiler (for example int to double in mixed arithmetic). Explicit conversion is requested by the programmer with a cast, preferably static_cast<T>(value).

Summary

  • Fundamental types: int (and its short/long variants), char, bool, float, double, void; text uses std::string.
  • sizeof gives sizes in bytes; std::numeric_limits gives ranges. Sizes such as long differ between Windows and Linux.
  • Unsigned values wrap around; signed overflow is undefined behaviour. Use a bigger type before it happens.
  • double is the default decimal type, but floating-point values are approximate.
  • Integer division discards the fraction; use static_cast for explicit conversions and heed signed/unsigned warnings.

Phase 1 complete! Test yourself with the C++ basics interview questions, or go back to the C++ introduction.