CodeGym /Courses /C++ SELF /Signed vs unsigned

Signed vs unsigned

C++ SELF
Level 8, Lesson 1
Available

1. Two worlds of integers: "signed" and "unsigned"

If integers used to be seen as "well, it's just numbers", today we'll take a step toward a more mature view: a type is not just a box for a value, it's also a contract. When you choose signed or unsigned, you're not picking "what's trendier", you're choosing the meaning: can the value be negative or is that forbidden by the problem logic.

What signed and unsigned mean

A signed type (for example, int) can store negative and positive values. An unsigned type (for example, unsigned int) stores only non-negative values.

On a simple level you can think: signed is a "thermometer" (it has minus), unsigned is a "count of people in a room" (no minus, unless someone went to a parallel universe).

Here's a short comparison table:

Property Signed (int) Unsigned (unsigned)
Negative values Yes No
Zero Yes Yes
Positive values Yes Yes
Main idea "may be negative" "no negatives by contract"

And a small example — just to reinforce that these are two different types:

#include <iostream>

int main() {
    int a = -10;
    unsigned b = 10;

    std::cout << "a = " << a << '\n'; // a = -10
    std::cout << "b = " << b << '\n'; // b = 10
}

2. unsigned — not an "improved int", but a different contract

A very common novice thought goes like this: "unsigned stores more positive numbers, so it's better". That's like saying: "A truck is better than a bicycle because it fits more potatoes". Maybe yes... but try parking the truck inside an apartment. You choose a type not by "better/worse" but by what values are possible according to the problem.

Where unsigned appears in real life

Unsigned most often shows up where values cannot be negative by definition: lengths, counts, sizes, indices, bytes, various counters. You don't have to memorize all this perfectly now, but it's useful to know in advance: when you start working with the standard library, it often "thinks about sizes" as non-negative numbers.

Note about char and bool

Sometimes students ask: "Is char signed or unsigned?" Here the language trick appears: char and bool belong to the integral types, but char can be either signed or unsigned — it depends on the compiler and platform.

Practical takeaway for a beginner is simple: if you really need a signed byte — write explicitly signed char or unsigned char. And use char for text characters for now.

3. Why -1 becomes a large number in unsigned

Now comes the main "wow" of this lecture. In C++ the statement unsigned x = -1; is not a syntax error. The compiler is not required to yell "you can't!". It does what the language allows: it converts the value to the variable's type.

And here the magic begins, similar to a disappearing-coin trick: we put -1 in, and we get a "huge positive number" out.

Mini example: "minus one" becomes large

#include <iostream>

int main() {
    unsigned x = -1;   // compiles
    std::cout << x << '\n'; // usually some very large number (depends on bitness)
}

What happens conceptually: unsigned cannot represent -1. So the value is translated into the unsigned range by language rules. On common platforms (where unsigned is often 32-bit) you'll see 4294967295, but it's important to remember: the exact number depends on the type size on your system.

To avoid tying yourself to exact 32 bits, it's useful to keep a "mathematical picture": unsigned arithmetic lives as if on a circle.

flowchart LR
    A["signed: ... -2 -1 0 1 2 ..."] --> B["conversion to unsigned"]
    B --> C["unsigned: 0 1 2 ... MAX"]
    D["-1"] --> B --> E["MAX (the largest unsigned value)"]

The idea: if unsigned stores numbers from 0 to MAX, then -1 after conversion becomes exactly MAX. That's why novices often feel that "minus one turned into the maximum".

Another example: "minus" does not exist in unsigned

#include <iostream>

int main() {
    unsigned u = 0;
    std::cout << (u - 1) << '\n'; // again a very large number
}

Here u - 1 cannot become negative because the result is unsigned. So it "wraps to the end of the range". This is not a compiler bug, but a property of the type: in the unsigned world there is no "below zero", and the language handles it according to representation rules.

4. Brace initialization {} as a safety net

We already mentioned different forms of initialization, but today braces get a very practical role: forbidding some dangerous conversions. If ordinary = can "silently" accept and convert a value, then {} often prefers to say: "Nope, think first".

Example: = allows it, {} stops it

#include <iostream>

int main() {
    unsigned ok = -1;     // compiles: value is converted
    std::cout << ok << '\n'; // large number

    unsigned bad{-1};     // usually a compilation error: braces forbid this
}

The point: {} try to protect you from "non-obvious change of meaning". In real code this is often good: better to see a compile error once than spend an hour in the debugger whispering: "Why is the index 4 billion?.."

5. Practice: where unsigned shows up and how to choose a type

Even if you don't write unsigned yourself, you'll still encounter it. And that's important because the bug usually doesn't happen at the declaration, but later — when values are compared, subtracted or used in checks.

Where unsigned appears "by itself"

The language has operations and functions that by meaning return "non-negative" values. For example, the sizeof operator returns a size in bytes — a negative size, you must admit, sounds like a plot for a sci-fi about "antibytes". In the standard library many "sizes" and "lengths" also live in the unsigned world.

I won't dive into the specific size type right now — we'll approach that carefully in later lectures. Our current task is simpler: learn to recognize when unsigned unexpectedly enters the game, and understand that -1 can then turn into "a very large number".

Input indices: why it's better to start with signed

When you write an interactive program, the user can type anything. Even if you're sure a "normal person" won't input -1, your user can be very creative. Or just make a mistake. So an index coming from input is often wiser to store temporarily in a signed type: then you can legitimately treat negative values as an error, rather than a "wrap to a parallel universe of large numbers".

Bad option: index as unsigned immediately.

#include <iostream>

int main() {
    unsigned idx = 0;

    std::cout << "Enter index: ";
    std::cin >> idx;

    std::cout << "idx = " << idx << '\n'; // if you enter -1, you'll see a large number
}

If the user types -1, the stream will try to read that into unsigned. As a result the variable gets a converted value, and you lose the information that the user entered a minus.

Good option for input: index as int.

#include <iostream>

int main() {
    int idx = 0;

    std::cout << "Enter index: ";
    std::cin >> idx;

    if (idx < 0) {
        std::cout << "Index can't be negative\n";
    } else {
        std::cout << "Index looks non-negative\n";
    }
}

Note the idea: during input and initial validation signed is often more convenient. Later, when you compare the index to a string length and carefully reconcile types, additional rules and techniques will appear — but that's the next step.

Rule of common sense: if a negative is possible — use signed

There is a very practical habit that saves a lot of nerves: if intermediate values can be negative by the logic of the task (differences, offsets, "how much is left", "how much in the red"), keep calculations in signed types. Unsigned doesn't prevent an error — it just converts it into a strange result.

Imagine you calculate "how many days left until deadline". If the deadline already passed, the correct answer can be negative: "-2 days" (that is, two days overdue). Then signed is the natural choice.

But if you count "how many apples are in a box", a negative number of apples usually means someone already started eating the box with the apples. Then unsigned can reflect the contract: "no negatives".

6. Typical mistakes when working with signed/unsigned

Mistake #1: choosing unsigned "just in case".
This seems logical only for the first five minutes: "it stores more positives, I'll pick it". The problem is that unsigned changes not only the range but also the semantics of computations. As soon as a negative value appears somewhere by mistake, you don't get a clear minus — you get a large number that starts living its own life and breaks checks.

Mistake #2: writing unsigned x = -1; and expecting it to remain "minus one".
In C++ the type matters more than the written symbols. The literal -1 is an expression, but the variable's value after initialization must conform to its type. In the unsigned world there are no minuses, so conversion happens, and you actually store a different number. If you see a huge index or giant size in the debugger, first check whether such a conversion occurred.

Mistake #3: ignoring brace initialization {} where it could stop trouble.
Beginners often get used to = and use it everywhere because "it's easier". But braces are useful exactly where you don't want "silent magic" conversion. If the compiler can warn or even forbid a dangerous initialization — that's not its whim, it's your future time saved.

Mistake #4: storing user input directly in unsigned.
A user can make a mistake, and you should be able to catch it. If the index is unsigned immediately, negative values become large and you lose the chance to do a proper check idx < 0. It's much safer to first accept the value in a signed type, validate it, and only then (if needed) convert to the unsigned world.

Mistake #5: blaming the if rather than the types.
When a program behaves strangely, it's natural to blame the condition: "maybe I wrote the if wrong". But signed/unsigned traps are usually born earlier: in type choices and conversions. The condition just honestly compares values after the language coerced them to a common representation. So when you see odd numbers first ask: "What are the types here, and did my minus turn into a very big plus?"

1
Task
C++ SELF, level 8, lesson 1
Locked
Wrap-around Odometer
Wrap-around Odometer
1
Task
C++ SELF, level 8, lesson 1
Locked
Cautious counter
Cautious counter
1
Task
C++ SELF, level 8, lesson 1
Locked
Character at index
Character at index
1
Task
C++ SELF, level 8, lesson 1
Locked
No Stock Underflow
No Stock Underflow
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION