HOWTO · C
Difference Between Unsigned Int and Signed Int in C
The following article will explore the distinctions between signed and unsigned int in the C programming language.
On this page
A signed int can represent negative and positive values, while an unsigned int represents nonnegative values. In C, int is signed by default, so write int for a signed integer and unsigned int when you need an unsigned one. Which type to use depends on the values and arithmetic your program needs. The difference looks small — one type allows a minus sign and the other does not — but it also changes the range of values, what happens at the limits of that range, and how expressions behave when the two types are mixed together.
Difference Between signed and unsigned int in C
Both types are integer types, but their ranges differ. The C standard guarantees that int can represent at least -32767 through 32767, and unsigned int can represent at least 0 through 65535. These are minimum guarantees, not the exact limits on every system. Include limits.h and inspect INT_MIN, INT_MAX, and UINT_MAX to find the limits for the implementation you compile with.
| Type | Represents | Portable minimum range |
|---|---|---|
int |
Negative and nonnegative values | -32767 through 32767 |
unsigned int |
Nonnegative values | 0 through 65535 |
The two types normally have the same storage size, so choosing between them does not buy more room overall; it decides where the room goes. A signed type splits its values between negative and positive numbers, while an unsigned type spends its whole range on nonnegative values, which makes its maximum roughly twice as large on the same implementation. On the 64-bit Linux system used for the examples in this article, both types occupy four bytes, and printing the limits gives the familiar 32-bit ranges:
#include <limits.h>
#include <stdio.h>
int main(void) {
printf("sizeof(int) = %zu\n", sizeof(int));
printf("sizeof(unsigned int) = %zu\n", sizeof(unsigned int));
printf("int range: %d to %d\n", INT_MIN, INT_MAX);
printf("unsigned int range: 0 to %u\n", UINT_MAX);
return 0;
}
sizeof(int) = 4
sizeof(unsigned int) = 4
int range: -2147483648 to 2147483647
unsigned int range: 0 to 4294967295
Treat those exact numbers as properties of this implementation, not of the language. Code that must run anywhere should compare against INT_MIN, INT_MAX, and UINT_MAX instead of hard-coding 32-bit limits.
Value ranges and conversions
The actual range and storage size are implementation-dependent. sizeof(int) and sizeof(unsigned int) do not have to be four bytes, and C does not guarantee that int has 32 bits. A signed integer is not portably described as reserving its leftmost bit for a sign; representation details depend on the implementation.
When a negative value is converted to an unsigned type, the result is congruent to that value modulo UINT_MAX + 1. The unsigned object therefore contains a nonnegative value; it does not retain a negative value for a later read to recover. Unsigned arithmetic also wraps modulo UINT_MAX + 1. The next example applies that rule three ways — converting -1, subtracting below zero, and adding past the maximum:
#include <limits.h>
#include <stdio.h>
int main(void) {
unsigned int converted = -1;
unsigned int wrapped_down = 0u - 1u;
unsigned int wrapped_up = UINT_MAX + 1u;
printf("converted -1: %u\n", converted);
printf("0u - 1u: %u\n", wrapped_down);
printf("UINT_MAX + 1u: %u\n", wrapped_up);
return 0;
}
converted -1: 4294967295
0u - 1u: 4294967295
UINT_MAX + 1u: 0
On this implementation, -1 becomes 4294967295, which is exactly UINT_MAX; on any implementation the result is UINT_MAX itself, because -1 is always one step below zero in modulo arithmetic. Subtracting one from zero wraps to the same maximum instead of producing -1, and adding one to the maximum wraps back to zero. None of these operations is an error: wrapping is the defined behavior for unsigned arithmetic.
This complete example uses %d for int and %u for unsigned int. It was run successfully; the output shown below is from that run.
#include <stdio.h>
int main(void) {
int temperature_change = -7;
unsigned int item_count = 42;
printf("Temperature change: %d\n", temperature_change);
printf("Item count: %u\n", item_count);
return 0;
}
Temperature change: -7
Item count: 42
Use the conversion rule above to reason about a negative-to-unsigned assignment; its exact resulting value depends on UINT_MAX. Passing an unsigned int to printf with %d instead of %u is a format mismatch and causes undefined behavior, so its printed result cannot be relied on.
Overflow: defined for unsigned, undefined for signed
The wrapping above has no signed counterpart. Signed integer overflow — computing a result above INT_MAX or below INT_MIN — is undefined behavior in C, not a guaranteed wrap. Compilers optimize on the assumption that it never happens, so a guard such as checking whether x + 1 is smaller than x cannot be trusted to detect signed overflow after the fact. If a calculation can approach the limits, check before you compute, for example by testing x > INT_MAX - y before adding y to x, or use a wider type. This asymmetry is a practical reason to prefer signed types for ordinary arithmetic: mistakes with unsigned values wrap silently to a wrong but valid-looking number, while signed code at least keeps the values you expect until a limit is genuinely exceeded.
Mixing signed and unsigned in one expression
The most surprising differences appear when the two types meet in a single expression. Under the usual arithmetic conversions, if an int and an unsigned int of the same rank are combined, the int operand is converted to unsigned int before the operation. The comparison or arithmetic is therefore unsigned, even when one side started out negative:
#include <stdio.h>
int main(void) {
printf("-1 < 1: %d\n", -1 < 1);
printf("-1 < 1u: %d\n", -1 < 1u);
printf("-1 == 4294967295u: %d\n", -1 == 4294967295u);
return 0;
}
-1 < 1: 1
-1 < 1u: 0
-1 == 4294967295u: 1
Read as mathematics, -1 < 1 is true and prints 1. Adding the u suffix changes the answer to 0: the -1 is converted to 4294967295 first, and that value is not less than 1. For the same reason, -1 compares equal to the largest unsigned value. GCC warns about exactly this pattern with -Wsign-compare, which is worth enabling permanently. Arithmetic follows the same rule, so a negative adjustment can turn into a huge positive result instead of a small negative one:
#include <stdio.h>
int main(void) {
unsigned int count = 10u;
int delta = -20;
int signed_total = 10 + delta;
printf("unsigned total: %u\n", count + delta);
printf("signed total: %d\n", signed_total);
return 0;
}
unsigned total: 4294967286
signed total: -10
Nothing overflowed the unsigned type here; the conversion alone produced 4294967286. Whenever a value can go negative — a difference between two counts, a balance, an error sentinel such as -1 — keep it in a signed type until the point where a nonnegative value is guaranteed.
The sizeof trap and unsigned loop counters
Mixing also happens where no cast or suffix is visible. The sizeof operator produces a size_t, an unsigned type, so comparing a signed variable against a sizeof result triggers the same conversion:
#include <stdio.h>
int main(void) {
int index = -1;
printf("sizeof(int): %zu\n", sizeof(int));
printf("index < (int)sizeof(int): %d\n", index < (int)sizeof(int));
printf("index < sizeof(int): %d\n", index < sizeof(int));
return 0;
}
sizeof(int): 4
index < (int)sizeof(int): 1
index < sizeof(int): 0
The last comparison should be true — -1 really is smaller than 4 — but the unsigned comparison says otherwise. Converting the sizeof result to int, when its value is known to fit, restores the expected answer. A related trap is the downward loop written as for (unsigned int i = count - 1; i >= 0; --i): an unsigned value is always greater than or equal to zero, so the condition never fails. When the counter reaches zero and is decremented, it wraps to UINT_MAX — the same wrap the conversion example printed for 0u - 1u — and the loop keeps going with a huge index. Count down in a signed type, or use the idiom for (unsigned int i = count; i-- > 0; ), which tests the old value before decrementing.
Choosing between signed and unsigned int
Choose int when negative values are meaningful, such as a change that can be above or below zero. Choose unsigned int when the domain is nonnegative and modulo arithmetic is appropriate, such as a count whose operations should wrap at the type’s maximum. Unsigned is not a general way to safely store larger positive values: its range depends on the implementation, and converting or mixing signed and unsigned values can change comparison and arithmetic results. Use the type whose range and arithmetic match the values your program actually handles.
In practice, that leads to a few reliable defaults. Use a signed type for general arithmetic, differences, and loop counters, even for quantities that start out nonnegative, because intermediate results often need to go below zero. Reserve unsigned types for bit patterns and masks, for values whose wrapping is deliberate, such as hash values and hardware registers, and for matching an interface that already uses an unsigned or size_t type. Do not choose unsigned only to document that a value should never be negative: it does not stop the value from being produced by subtraction, it just renames it to a very large positive number. Finally, if the real problem is range, move to a wider type such as long long instead of relying on the extra room an unsigned type appears to offer.
Three misconceptions cause most of the confusion around these types. The first is that unsigned int is simply int with a bigger positive range; in fact the two types differ in overflow behavior, conversions, and comparisons, not only in range. The second is that storing a negative value in an unsigned variable somehow remembers the negative value; the conversion replaces it immediately, as the examples above show. The third is that unsigned arithmetic is safer because it cannot go negative; it replaces negative results with large positive ones, silently, which is often harder to debug than the problem it was meant to prevent.