C / TYPES AND REPRESENTATION
unsigned integers and wraparound arithmetic
Predict what unsigned arithmetic does when a result leaves its range, and use modulo 2^N reasoning to avoid wrapped counters and bad subtractions.
What you will learn
- Compute any unsigned result by reducing the true value modulo 2^N for that width
- Know that 0u - 1u is UINT_MAX and UINT_MAX + 1u is 0, both fully defined by C
- Detect a wrapped unsigned sum portably with the test a + b < a
- Rewrite countdown and i < n - 1 loops so an unsigned index never drops below 0
Understanding unsigned integers and wraparound arithmetic
An unsigned type spends every one of its bits on magnitude, so an N-bit unsigned type represents exactly the integers 0 through 2^N - 1 and nothing else; there is simply no encoding left over for a negative value. C therefore defines unsigned arithmetic as arithmetic modulo 2^N: whenever a mathematical result falls outside that range, it is reduced by adding or subtracting 2^N until it fits. That reduction is a guarantee in the language, not a side effect of a particular CPU, so two conforming compilers on types of the same width must produce the same wrapped value.
The mental model that works is a car odometer with N wheels. Rolling forward past 2^N - 1 lands back on 0, and rolling backward past 0 lands on 2^N - 1, so with a 32-bit unsigned int, UINT_MAX + 1u is 0 and 0u - 1u is 4294967295. Nothing is trapped or flagged along the way: the value you get is the true result minus some multiple of 2^32, which is precisely why unsigned types are the right choice for hashes, checksums, sequence numbers and bit manipulation.
The trap is that code usually means "went negative" when it writes a subtraction. Since u >= 0 is a tautology for unsigned u, and a - b with b > a produces a huge positive number, the countdown loop for (unsigned i = n - 1; i >= 0; i--) never terminates, and strlen(s) - 1 on an empty string yields SIZE_MAX rather than -1. The fix is structural: guard the subtraction with if (b <= a), or turn the comparison into an addition, writing i + 1 < n instead of i < n - 1, so no expression ever has to leave the representable range.
Unsigned arithmetic never overflows in the undefined sense; it wraps, and the wrapped value is the true result reduced modulo 2^N.
<stdio.h>
<limits.h>
int main(void)
{
unsigned int zero = 0u;
unsigned int umax = UINT_MAX;
unsigned int big = 4000000000u;
printf("UINT_MAX = %u\n", umax);
printf("zero - 1u = %u\n", zero - 1u);
printf("umax + 1u = %u\n", umax + 1u);
printf("umax + 3u = %u\n", umax + 3u);
printf("big + big = %u\n", big + big);
printf("(zero-1u)+1u = %u\n", (zero - 1u) + 1u);
return 0;
}
Every unsigned result in C is the mathematical result reduced modulo 2^N, so values wrap around the ends of the range instead of going negative or overflowing.
Worked examples
Counting down without wrapping
Shows the safe descending-loop idiom for an unsigned counter and the value a naive i >= 0 test would leave behind.
<stdio.h>
int main(void)
{
unsigned int i;
for (i = 3; i-- > 0; )
printf("down %u\n", i);
i = 0;
printf("0 - 1 as unsigned: %u\n", i - 1u);
return 0;
}
Example explained
Line 1i-- > 0 tests the current value and then decrements, so the body sees 2, 1 and 0 in turn.
Line 2On the last test i is 0, so 0 > 0 is false; the decrement still wraps i to 4294967295, but the loop has already exited and that value is never used.
Line 3The final line prints what a for (i = n - 1; i >= 0; i--) loop would put in the counter at the bottom: the wrapped maximum, not -1, which is why such a loop never ends.
Detecting a wrapped sum
Uses defined wraparound to test after the fact whether an unsigned addition left the range.
<stdio.h>
int add_wrapped(unsigned int a, unsigned int b)
{
return a + b < a;
}
int main(void)
{
unsigned int a = 4000000000u;
printf("wrapped=%d sum=%u\n", add_wrapped(a, 100u), a + 100u);
printf("wrapped=%d sum=%u\n", add_wrapped(a, a), a + a);
return 0;
}
Example explained
Line 1a + b < a is true exactly when the sum wrapped, because a wrapped result equals a + b - 2^32 and that is always below a.
Line 24000000000 + 100 stays under 2^32, so the test yields 0 and the sum prints unchanged.
Line 34000000000 + 4000000000 is 8000000000, reduced to 8000000000 - 4294967296 = 3705032704, which is below a, so the test yields 1.
Line 4The idiom is valid only for unsigned operands; the signed equivalent would already have executed undefined behaviour before the test ran.
A narrower modulus
Demonstrates that the modulus is 2^N for the specific type, not always 2^32.
<stdio.h>
int main(void)
{
unsigned char c = 250;
int k;
for (k = 0; k < 3; k++) {
printf("c = %u\n", (unsigned int)c);
c += 3;
}
printf("c = %u\n", (unsigned int)c);
return 0;
}
Example explained
Line 1unsigned char has 8 value bits here, so its ring has 256 states and the modulus is 256, not 2^32.
Line 2253 + 3 is 256, which becomes 0 when stored back into c: the conversion into the narrower unsigned type is modular too.
Line 3The next iteration continues from 0 and produces 3, confirming the counter cycles rather than saturating at 255.
Line 4The (unsigned int) cast is there because %u expects an unsigned int, while c on its own would be passed as an int.
Important notes
The numbers above assume a 32-bit unsigned int, where 2^32 = 4294967296; on a platform with a different width the wrapped values differ but the modulo rule is unchanged.
Wraparound is defined only for unsigned types. The same out-of-range result on a signed int is undefined behaviour, so do not reason about signed overflow by analogy with this lesson.
Common mistakes
Writing for (unsigned int i = n - 1; i >= 0; i--): the condition can never be false, so at 0 the decrement wraps to UINT_MAX and the loop keeps running, indexing far past the end of the array.
Computing strlen(s) - 1 or count - 1 when the length is 0: size_t is unsigned, so the result is SIZE_MAX instead of -1, and the following loop walks memory the program does not own.
Checking for overflow with if (x + y < 0) on unsigned operands: an unsigned sum is never negative, so the branch is dead code and the compiler may drop it; the correct test is x + y < x.
Try it yourself
Change, predict, then run
Declare unsigned char t = 0, add 40 to it ten times in a loop and print t after each addition. Predict on paper which iteration crosses 255 and what value follows it, then run the code and compare.
Open the C workspaceCheck your understanding
On a platform where unsigned int is 32 bits, what is the value of the expression 3u - 10u?
- 4294967289, the true result -7 reduced modulo 2^32
- -7, since the subtraction is done in full precision before storage
- 0, because unsigned results below zero are clamped to the minimum
- Unpredictable, because leaving the range of an integer type is undefined behaviour
Show answer
The mathematical result is -7, and C requires unsigned results to be reduced modulo 2^32, giving 4294967296 - 7 = 4294967289. Clamping at 0 is what saturating arithmetic in some DSP and image-processing libraries does, but C never clamps; and unlike signed overflow, unsigned wraparound is fully specified, so the last option confuses the two.