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
| Category | Types |
|---|---|
| Fundamental (built-in) | int, short, long, long long, char, bool, float, double, long double, void, wchar_t |
| Derived | Arrays, pointers, references, functions |
| User-defined | class, struct, enum, union, type aliases |
| Standard library types | std::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
| Type | Stores | Example |
|---|---|---|
int | Whole numbers. The default choice for integers | int age{25}; |
short, long, long long | Smaller or larger whole numbers | long long population{8'100'000'000}; |
float | Decimal number, about 6-7 significant digits | float temp{36.6f}; |
double | Decimal number, about 15-16 significant digits. The default for decimals | double price{499.99}; |
char | A single character (stored as a small integer) | char grade{'A'}; |
bool | true or false | bool isActive{true}; |
void | "No type": used for functions that return nothing | void printReport(); |
std::string | Text 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
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';
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';
- 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 + 1on anintisn't guaranteed to give anything sensible. Convert to a bigger type before the arithmetic, as in the last line. - Use
unsignedfor bit patterns and sizes, not to say "this can't be negative". A plainintthat 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';
floatis accurate to about 7 digits (wrong from the 8th digit above);doubleto about 15-16. Usedoubleby 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.3is false. Compare with a small tolerance instead:std::abs(a - b) < 1e-9. - For money, don't rely on
doublefor exact totals. Store the amount in the smallest unit as an integer (paise or cents in along long). - A decimal literal like
3.14is adouble; addffor afloat(3.14f). std::fixedandstd::setprecisionfrom<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';
- A
charstores 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
boolistrueorfalse. Any non-zero number converts totrue, and zero converts tofalse.
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)
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';
- 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, theintis automatically converted todouble, 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::roundfrom<cmath>if you want 5. - Signed vs unsigned comparison:
-1 < 10uis 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::stodconvert text to numbers;std::to_stringconverts numbers to text.
Choosing the right type
| Data | Recommended type |
|---|---|
| Age, marks, quantity, loop counter | int |
| Population, file size, timestamps, money in paise | long long or std::int64_t |
| Measurements, percentages, scientific values | double |
| One character, a grade | char |
| Yes/no flags | bool |
| Names, messages, any text | std::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 usesstd::string. sizeofgives sizes in bytes;std::numeric_limitsgives ranges. Sizes such aslongdiffer between Windows and Linux.- Unsigned values wrap around; signed overflow is undefined behaviour. Use a bigger type before it happens.
doubleis the default decimal type, but floating-point values are approximate.- Integer division discards the fraction; use
static_castfor 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.