Arithmetic types in C++ are categorized by size, signedness, and precision. When performing floating-point calculations, prefer double over float: it offers greater precision with negligible performance cost on modern hardware.
Size Guarantees and Relationships
The standard enforces minimum widths but allows implementations flexibility:
shortandint: at least 16 bitslong: at least 32 bitslong long: at least 64 bits
Size ordering is guaranteed as follows (expressed in terms of sizeof):
sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)
Memory Representation Fundamentals
Data resides in memory as sequences of binary digits (bits), each holding 0 or 1. The smallest addressable unit is the byte, typically composed of 8 bits. Larger units—such as words (commonly 4 or 8 bytes)—serve as natural alignment boundaries for fundamental types like int.
Each byte has a unique numeric adress, enabling precise data access. In C++, declaring a variable’s type informs the compiler how many bits to allocate and how to interpret the bit pattern—critical for correct storage, retrieval, and arithmetic behavior.
Signed vs. Unsigned Integer Types
Integer types fall into two families:
- Signed: support negative, zero, and positive values (
int,short,long,long long) - Unsigned: represent only non-negative values (
unsigned int,unsigned short, etc.)
The char type is distinct: it may be signed or unsigned depending on the implementation. Explicit alternatives exist: signed char and unsigned char. Though often used for character data, all three variants are integral types with differing value ranges.
Type Conversion Rules
Boolean Conversions
- To bool: any nonzero value becomes
true; zero becomesfalse - From bool:
false→0,true→1(as anint)
Floating-Point ↔ Integral Conversions
- Floating to integer: truncates toward zero (e.g.,
static_cast<int>(7.9)yields7;-3.2yields-3) - Integer to floating: preserves magnitude with exact representation if within the target type’s precision (e.g.,
42→42.0)
Signed ↔ Unsigned Conversions
Converting a signed value that cannot be represented in the target unsigned type results in modulo reduction using the unsigned type’s range. For example:
- Assigning
-1to anunsigned char(range 0–255) yields255(i.e.,(-1 + 256) % 256) - Assigning
300to the same type yields44(300 % 256)
Implicit Arithmetic Conversions
In mixed-type expressions involvign signed and unsigned operands of the same rank, the signed operand is converted to unsigned—potentialy yielding unexpected large positive values. Consider:
#include <iostream>
int main() {
const unsigned u_val = 10;
const int s_val = -42;
std::cout << (s_val + s_val) << "\n"; // -84
std::cout << (u_val + s_val) << "\n"; // 4294967264 (on 32-bit unsigned)
}
Subtraction with Unsigned Operands
Because unsigned types wrap on underflow, subtracting a larger value from a smaller one produces a large positive result:
#include <iostream>
int main() {
unsigned a = 10, b = 42;
int x = 10, y = 42;
std::cout << (b - a) << "\n"; // 32
std::cout << (a - b) << "\n"; // 4294967264 (32-bit)
std::cout << (y - x) << "\n"; // 32
std::cout << (x - y) << "\n"; // -32
std::cout << (x - a) << "\n"; // 0 (int→unsigned then back?)
std::cout << (a - x) << "\n"; // 0
}
Note: The last two lines involve promotion to unsigned, then subtraction, followed by output as signed—leading to implementation-defined or undefined behavior if not handled carefully. Modern practice favors explicit casting and static assertions to avoid such pitfalls.