A C++ expression can compile and still produce a result that looks wrong because a literal, promotion, or destination type changed the calculation. For example, on an 8-bit-byte, 32-bit-int model, unsigned char a = 250, b = 10; auto sum = a + b; makes sum the integer 260, while storing it in another unsigned char produces 4. The reliable method is not a memorised size table. Trace the value range, representation, and each explicit conversion. The C++ Tutorial: Complete Learning Path in 12 Weeks applies the same trace-first approach before arrays, pointers, classes, and the STL.
1. Fundamental types are value sets with rules, not a fixed size chart
C++ fundamental types fall into four useful groups: void, std::nullptr_t, integral types, and floating-point types. Integral types include bool, character types, and signed and unsigned integer types. Arrays, pointers, references, classes, and enumerations are not fundamental types, even when their elements or members are.
The portable anchors are deliberately limited. sizeof(char) == 1, but one byte has at least 8 bits. The language specifies integer ranks and minimum ranges, while the widths of short, int, long, and long long can differ across implementations. Plain char is a separate type from both signed char and unsigned char; whether it behaves as signed or unsigned is implementation-defined.
Inspect the actual implementation instead of guessing:
#include <climits>
#include <iostream>
#include <limits>
template<class T>
void inspect() {
std::cout << "byte bits: " << CHAR_BIT
<< ", sizeof(T): " << sizeof(T)
<< ", min: " << +std::numeric_limits<T>::min()
<< ", max: " << +std::numeric_limits<T>::max()
<< ", digits: " << std::numeric_limits<T>::digits << '\n';
}Values printed by calls such as inspect<int>() describe that implementation, not every conforming C++ system. The Coding & Skill Development Courses catalogue groups the broader programming tracks that build on this base.
2. Read integer bits as a range before doing arithmetic
For the integer examples below, assume 8-bit bytes, 32-bit int and unsigned int, and two's-complement signed representation. An 8-bit unsigned value ranges from 0 to 255, so 11111010₂ represents 250. An 8-bit signed value ranges from -128 to 127 on this model.
Unsigned conversion to an N-bit destination reduces the value modulo 2^N. If an unsigned char holding 255 is promoted, increased by 1, and stored back into an 8-bit unsigned char, the intermediate value is 256 and the stored value is 256 mod 256 = 0. Signed overflow is different. A runtime int calculation of INT_MAX + 1 has undefined behaviour, so it has no portable next bit pattern to predict.
bool has only the values false and true, but this does not promise one bit of storage. Arithmetic zero converts to false; any non-zero arithmetic value, including -7, converts to true.
3. Decode one floating-point value, then find the precision boundary
Now assume float uses IEEE 754 binary32. A program can test the relevant properties with std::numeric_limits<float>::is_iec559 and sizeof(float) == 4; C++ does not mandate binary32 everywhere.
Decode -12.5f field by field:
The sign bit is
1, so the value is negative.The exponent is
10000010₂ = 130. Subtract the bias:130 - 127 = 3.The fraction begins
10010000000000000000000, so the significand is1.1001₂.Therefore,
-1 × 1.1001₂ × 2³ = -1100.1₂ = -12.5.
Precision has a sharp boundary. Binary32 represents 16,777,216 exactly, but the next representable value there is 16,777,218. Under the default round-to-nearest, ties-to-even mode, 16,777,216.0f + 1.0f rounds back to 16,777,216.0f. The Floating point representation: encode and add in IEEE 754 walkthrough continues from field decoding to arithmetic.

4. A literal chooses a type before the expression begins
A literal arrives with a type before any receiving variable is considered:
Literal | Initial type or meaning |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| unsigned value |
On an LP64-style model with 32-bit int, 32-bit unsigned int, and 64-bit long, auto decimal = 2147483648; selects long. Yet auto hex = 0x80000000; can select unsigned int. Unsuffixed decimal and hexadecimal literals use different candidate-type lists. On a platform with 32-bit long, the decimal literal selects long long. Inspect the literal first, then predict its effect on the full expression.
5. Follow promotions and usual arithmetic conversions in order
Return to the opening example on the stated 8-bit-byte, 32-bit-int model:
unsigned char a = 250;
unsigned char b = 10;
auto sum = a + b;
unsigned char stored = sum;A 32-bit int represents every value from 0 to 255, so both operands promote to int. The addition is 250 + 10 = 260, making sum an int. Storage then converts 260 to an 8-bit unsigned value: 260 mod 256 = 4, with bits 00000100.
Mixed signs expose the next rule. With 32-bit types of equal rank, int n = -1; unsigned int u = 1; bool r = n < u; converts n to the unsigned value 4,294,967,295, or 0xFFFFFFFF. The comparison becomes 4,294,967,295 < 1, so r is false.
Use four questions every time:
What are the operand types?
Do integral promotions apply?
What common type do the usual arithmetic conversions choose?
Is there a final conversion during storage or return?
Write the type and value after every arrow. That prevents most output-prediction errors.

6. Narrowing and casts: value change or representation inspection?
These three statements request related but distinct operations:
int a{3.5}; // rejected: narrowing
int b = 3.5; // converts to 3; enable warnings
int c = static_cast<int>(-3.9); // deliberately becomes -3Floating-to-integer conversion truncates toward zero, provided the result is representable in the destination integer type. An out-of-range floating value converted to an integer has undefined behaviour, so modulo arithmetic cannot predict it.
Unsigned integer narrowing is defined. On the same model, static_cast<unsigned char>(300) gives 300 mod 256 = 44, stored as 00101100.
Numeric conversion is not bit inspection. Under the binary32 assumption, static_cast<std::uint32_t>(1.0f) produces the integer value 1. In C++20, std::bit_cast<std::uint32_t>(1.0f) produces the representation 0x3F800000 when the source and destination have the same size. C-style casts conceal which operation is intended, so prefer the narrowest named operation and state why it is correct.
7. Traps, test patterns, and the short version
Keep these corrections beside common traps:
State the model instead of assuming
intis 32 bits.Check plain
charinstead of assuming it is signed.Never predict signed overflow as wraparound.
Remember that small integer operands commonly promote to
int.Separate numeric conversion from inspection of the same bits.
Confirm the floating format before decoding it as binary32.
Typical practice asks you to identify an auto type, resolve a signed and unsigned comparison, calculate an unsigned modulo conversion, spot a list-initialisation error, or decode given binary32 fields.
Reliable prediction follows five steps: write the machine assumptions, label every literal and operand type, apply promotions, compute in the selected common type, then apply the destination conversion and check that it is defined. Build the full sequence in the C++ Programming course, or take the placement route through Coding For Placements.
Finish by predicting unsigned char x = 255; auto y = x + 2; unsigned char z = y;. On our stated model, x promotes to int, so y is the int value 257; storage gives 257 mod 256 = 1, so z == 1.




