Integer and Floating-Point Variants: Size, Sign, and Precision
Choosing how to store a number is a decision. A temperature reading, a population count, and a bank balance each have different requirements: some need to go negative, some need to be enormous, and some need to be exact. C++ gives you a family of numeric types to match those requirements.
C++ gives you more than one integer type and more than one decimal type. You don’t need to memorize all of them today. What matters is knowing they exist, understanding the differences, and knowing where to look when the default isn’t enough.
Integer types and sizes
int is the default integer type, but C++ gives you smaller and larger options depending on how big your values can get.
| Type | Typical Size | Signed Range (approx) | Unsigned Range (approx) |
|---|---|---|---|
short | 2 bytes | −32K to 32K | 0 to 65K |
int | 4 bytes | −2 billion to 2 billion | 0 to 4 billion |
long | 4 or 8 bytes | varies by platform | varies by platform |
long long | 8 bytes | −9 quintillion to 9 quintillion | 0 to 18 quintillion |
The pattern is straightforward: a larger type uses more bytes, which means more bits, which means a wider range of values. Most C++ code skips long in favour of int or long long.
The LL after 8100000000 is a hint to the compiler: treat this number as a long long. Without it, the compiler looks at the number on its own and treats it as an int. A value this large can’t be an int, so you get an error before the assignment even happens. You’ll encounter these suffixes formally in the next lesson; for now, just know that large numbers need LL tacked on.
Signed and unsigned integers
All four types in the table above are signed by default. “Signed” just means the type is capable of representing negative values.
Each of these types also has an unsigned variant, which can only hold zero and positive values. In exchange for giving up negatives, it’s possible to store larger values in the variable. The reason is mechanical: computers store numbers in binary (a sequence of bits, each 0 or 1), and a signed integer has to dedicate one of those bits to recording whether the number is negative. An unsigned integer puts that bit to use by storing part of the value instead, which means it can hold roughly twice the maximum value.
To declare an unsigned integer, place the unsigned keyword before the type name, as shown on line 5:
Overflow: when a value outgrows its type
If you try to store a value that’s too large for the type, the value overflows. For unsigned types, it wraps around predictably (like an odometer rolling over). For signed types, the result is unpredictable. In practice it usually also wraps around, but you can’t rely on it:
The value wrapped all the way from the maximum to the minimum. The solution to this problem is simple: if your values might exceed a type’s range, use a larger type.
When exact sizes are required
The table above shows typical sizes, and they hold on most modern desktop and server hardware. But C++ only guarantees minimums: int must be at least 16 bits, and long is 32 bits on some platforms and 64 bits on others.
In practice this rarely affects everyday code, but it matters when another system expects a specific number of bytes. For example, network packets, binary file formats, or hardware registers that expect precisely 32 bits, not “probably 32 bits.”
For those situations, <cstdint> gives you types with exact, guaranteed sizes:
The naming pattern is:
int+ width +_tfor signed types:int8_t,int16_t,int32_t,int64_tuint+ width +_tfor unsigned types:uint8_t,uint16_t,uint32_t,uint64_t
Here width means number of bits. The ULL suffix on line 6 works the same way as LL, but signals the value is an unsigned long long.
For most everyday work you don’t need these.
Floating-point types
C++ has three floating-point types: float, double, and long double. Before looking at them, it helps to understand what precision means, because it’s what actually separates them.
Understanding floating-point precision
Floating-point numbers are stored as approximations. Your computer works in binary, and most decimal fractions have no exact binary representation, similar to how 1/3 has no exact decimal representation. The computer stores the closest value it can fit.
Precision is how close that approximation is. A type with higher precision stays accurate further into the decimal places. In the table below, “~7 significant digits” means the type is accurate to roughly the first 7 digits, ignoring any leading zeros. After that, the digits are unreliable.
The three floating-point types
They differ mainly in size and how many digits they preserve:
| Type | Typical Size | Significant Digits |
|---|---|---|
float | 4 bytes | ~7 |
double | 8 bytes | ~15–16 |
long double | 8–16 bytes | ~18–19 (varies by platform) |
double is the default and the right choice for almost all general-purpose work. float saves memory at the cost of precision and is mainly used in graphics and signal processing. long double is used when you need the highest precision available, such as in scientific or financial software doing sensitive numerical work.
You can see the precision difference directly:
You’ll notice this pattern in the output:
float: 3.14159274101257 ^^^^^^^ matches, then driftsdouble: 3.14159265358979 ^^^^^^^^^^^^^^^ stays accurateThe float matches the true value for about 7 digits, then starts drifting. The double stays accurate for 15.
The f suffix on line 4 tells the compiler to treat that value as a float. Without it, the value is treated as a double, and assigning it to a float variable produces inaccuracies because of the loss in precision.
Floating-point is always approximate
Because every floating-point value is an approximation, arithmetic on them is also approximate. Small errors accumulate across calculations. For anything involving money, storing values as integer cents rather than decimal dollars is the standard approach.
Choosing the right type
Most of the time, the choice comes down to a few simple rules:
intfor everyday whole numbers. It’s the size the CPU handles most naturally.long longwhen values might exceed two billion: population figures, file sizes, totals in minor currency units.unsignedwhen a value is conceptually non-negative. We touch upon such pitfalls later when discussing operators.doubleas your default for anything with a decimal. Only drop tofloatwhen you have a specific memory or performance reason.int32_tand friends when the exact size matters for correctness across platforms.