Integer ranges, signedness, and floating-point precision
Understand integer ranges, unsigned arithmetic, overflow, and floating-point rounding.
Learning objectives
- Recognize that built-in integer widths are implementation-dependent within standard limits.
- Explain signed and unsigned integer types.
- Avoid relying on signed overflow.
- Understand floating-point approximation.
Prerequisites: variables, initialization, and basic arithmetic. Review Variables, values, and data types and Expressions and arithmetic first.
Integer types and ranges
C++ provides integer types such as short, int, long, and long long, each with signed and unsigned forms. The standard defines minimum ranges and ordering requirements, but exact widths can vary by implementation. Use <limits> and std::numeric_limits<T> when your program needs to inspect a type's range.
#include <iostream>
#include <limits>
int main() {
std::cout << std::numeric_limits<int>::min() << '\n';
std::cout << std::numeric_limits<int>::max() << '\n';
}Signed and unsigned
Signed integer types represent negative and non-negative values. Unsigned integer types represent non-negative values and use modular arithmetic for operations within their range. Mixing signed and unsigned values can produce surprising conversions, especially when a negative value is converted to unsigned. For example, in -1 < 1u, the usual arithmetic conversions convert -1 to an unsigned value, so the comparison is false.
Overflow
Signed integer overflow is undefined behavior in C++. Do not rely on it wrapping around. Unsigned arithmetic is defined modulo one more than the maximum representable value, but wraparound can still be a logic bug. Check ranges before performing arithmetic when inputs can be large.
Floating-point approximation
float and double use finite-precision binary floating-point representations on common implementations. Many decimal fractions cannot be represented exactly, so calculations can contain small rounding differences. Compare measured values using a tolerance chosen for the problem rather than expecting exact equality in every case.
double result = 0.1 + 0.2; // result may not be represented as exactly 0.3
For financial calculations requiring exact decimal amounts, consider integer minor units (such as paise or cents) or a suitable decimal arithmetic design rather than assuming double is exact.
Common mistakes
- Assuming int is always 32 bits.
- Mixing signed and unsigned values without checking conversion rules.
- Expecting signed overflow to wrap.
- Comparing floating-point calculations for exact equality when rounding matters.
Practice
- Print the minimum and maximum values for int and long long using numeric_limits.
- Explain why a negative int compared with an unsigned value may behave unexpectedly.
- Demonstrate why 0.1 + 0.2 may not equal 0.3 exactly.
- Design a safe range check before adding two user-supplied integers.
Check your understanding
Is signed integer overflow guaranteed to wrap around?
Answer: No. Signed overflow is undefined behavior, so a program must not rely on wraparound.
Practice solutions
Show reasoned solutions
- Use
std::numeric_limits<int>::min(),std::numeric_limits<int>::max(), and the correspondinglong longspecializations. Include<limits>. - When the usual arithmetic conversions choose an unsigned type, the negative value is converted to that unsigned type. For example,
-1 < 1uis false. - Binary floating-point cannot represent many decimal fractions exactly. The stored approximations of 0.1 and 0.2 can add to a value slightly different from the stored approximation of 0.3.
- Before adding
aandb, reject the operation if(b > 0 && a > max - b)or(b < 0 && a < min - b), wheremaxandmincome fromstd::numeric_limits<int>. These checks avoid performing the overflowing addition.
Summary
Know the actual range of the types you use, avoid unsafe signed arithmetic, be careful when mixing signed and unsigned values, and remember that floating-point values are approximations.