Signed and Unsigned Types

Before we go on the next topic, we should learn one more thing about the numeric (integer) types in C. We already know that a value of each type takes some specific amount of bytes in the memory: char always uses 1 byte; the size of int type is defined by your compiler but, in the majority of systems you might see today, it will take 4 bytes. If in doubt, sizeof int will give you the result.

Assuming the 4 bytes = 32 bits of int, and knowing that each bit is 0 or 1, we can see that we can store as many as 232 different values: from 000...0 to 111...1. We'll talk more about how exactly those values are stored later in the course, when we discuss bit manipulation, but for now, it's enough to understand that all these numbers are split in half between negative and non-negative numbers. Specifically, on most systems, int can store a number between −231 to 231−1 (minus one because we have 0, so we cannot have the same amount of positive and negative numbers).

In some cases we don't need negative numbers, we would rather extend our available range to have more positive numbers. To do that, C allows to define your variable as unsigned:

unsigned int a;

On most modern systems, it will be able to store numbers between 0 and 232−1.

To print an unsigned value using printf, you should use %u instead of %d. Unfortunately, given the way how printf is implemented in C, and, in general, given the way how functions with arbitrary number of parameters work, it is impossible for a function to know if the specific parameter is signed or unsigned, so we should help it by either using %d or %u; we'll talk about such functions in detail much later in the course.

But for now, just make sure to use the correct specifier:

#include <stdio.h>

int main() {
	unsigned int a = 4294967295; /* this is 2³²-1, the largest possible unsigned int */
  printf("works as expected: %u\n", a);
  printf("wrong specifier prints something else: %d\n", a); /* wrong specifier! */
  return 0;
}

You might be wondering why the wrong specifier in the second printf produces −1; I promise we'll talk about it later!

There is actually a way to know the exact limits of your compiler. If you include limits.h, you will be able to look at the specific limits:

#include <stdio.h>
#include <limits.h>

int main() {
	printf("int limits are %d to %d (inclusive)\n", INT_MIN, INT_MAX);
  printf("unsigned int limits are 0 to %u (inclusive)\n", UINT_MAX);
    /* there is no UINT_MIN, it's always 0 */
  return 0;
}

One important property of the unsigned type is that the overflow, when we try to increment the value beyond its limit, it safe: it behaves as if the modulo arithmetic is used. See what happens here, if we start from UINT_MAX−4 and keep incrementing the value:

#include <stdio.h>
#include <limits.h>

int main() {
	unsigned int value = UINT_MAX - 4;
	int i;

	/* do 8 times: */
	for (i = 0; i < 8; ++i) {
		printf("value: %u\n", value);
		++value;
	}

	return 0;
}

Note that after it reached UINT_MAX, the next increment brought it back to 0. This is a very useful property that we will need very soon.

Now that we know that the unsigned int type exists, we can go use it on the next page!

© Alexander Fenster (contact)