Numbers in C++

An introduction to the different types of numbers in C++, and how we can do basic math operations on them.
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

3D art of a boy using an abacus
Ryan McCombe
Ryan McCombe
Edited

In the previous lesson, we discussed the notion of integers and floating point numbers. Here, we will go into a bit more depth on how numbers work in C++

As a reminder, here is how we create variables. This is similar to what we saw in the previous chapter, except we're now also showing the creation of a float type. A float is a number with a decimal point.

bool isDead { false };
int Level { 5 };
float Armour { 0.2 };
int LargeNumber { 100000000 };

C++ Thousands Seperators

When dealing with large numbers, it is often helpful to add seperators to make them more readable for humans. For example, we often add commas to a number like 1,000,000.

We can do something similar in our code, if we want. In C++, we can not use commas for this purpose - instead, we can use the single quote character: '

int LargeNumber { 100'000'000 };

Basic Maths Operators in C++

We can do all the standard maths operations to our numbers. This includes all the basic operators:

  • + for addition
  • - for subtraction
  • * for multiplication
  • / for division

For example:

// Level is initialised with a value of 5
int Level { 2 + 3 };

Level = 5 + 1; // Level is 6
Level = 5 - 1; // Level is 4
Level = 5 * 2; // Level is 10
Level = 6 / 2; // Level is 3
Level = 1 + 2 + 3; // Level is 6

Order of operations applies in C++ just as it does in maths. For example, multiplication happens before addition

// Level is initialised with a value of 7
int Level { 1 + 2 * 3 };

As with maths, we can introduce brackets - ( and ) to manipulate the order of operations

// Level is initialised with a value of 9
int Level { (1 + 2) * 3 };

C++ Variables with Maths

We can also use the values contained in other variables within our statements

int StartingHealth { 500 };
int HealthLost { 100 };

// This will have a starting value of 400
int RemainingHealth { StartingHealth - HealthLost };

We can also use the current value of the variable when updating it, using an expression like this for example:

int Level { 5 };
Level = Level + 1;  // Level is now 6

This may seem incorrect if we interpret it as an equation, but it is important to realise that, in programming, this is not an equation.

The = is an assignment operator - it does not represent equality. This line is taking Level, adding 1 to it, and assign the result of that expression back to the Level variable.

Test your Knowledge

After running the following code, what will be the value of Health?

int BaseHealth { 200 };
int HealthBuff { 2 };
int Health { BaseHealth * HealthBuff };

After running the following code, what will be the value of Level?

int Level { 10 };
int Level = Level + 1

Increment, Decrement and Compound Assignment

The above example of increasing the value contained in a variable by 1 is so common, that programming languages often offer a quicker way of writing it.

Increasing a value by one is commonly called incrementing, and it has its own operator: ++.

The operator can go before or after the variable. There is a subtle difference, which we'll discuss in a later chapter.

int Level { 5 };
Level++;  // Level is now 6
++Level;  // Level is now 7

We also have the -- operator for decrementing:

int Level { 5 };
Level--;  // Level is now 4
--Level;  // Level is now 3

Additionally, we have operators to provide a quick way to add or subtract any number from the value contained in a variable, and to assign the result back to that variable.

These are referred to as compound assignments

+= will increase a variable by a specific number whilst -= will decrease it.

For example, instead of writing the following code:

int Level { 5 };
Level = Level + 3;  // Level is now 8
Level = Level - 6; // Level is now 2

This can be compacted to:

int Level { 5 };
Level += 3;  // Level is now 8
Level -= 6; // Level is now 2

We also have the *= and /= operators to apply multiplication or division in the same manner

int Level { 5 };
Level *= 3;  // Level is now 15
Level /= 5; // Level is now 3

Test your Knowledge

After running the following code, what will be the value of Level?

int Level { 2 };
Level = Level * Level + 2;
Level -= 2;

Negative Numbers

We also have access to negative numbers

int NegativeValue { -5 };

They behave exactly like they would in maths

int Health { 100 };
int HealthModifier { -10 };

Health += HealthModifier; // Health is now 90

Health *= -1; // Health is now -90

Different Types of Integers in C++

So far, we've just been using the simple int data type for storing our integers, but that is not the only option. C++ has many different integer types, with names such as short int and unsigned long int

Why would there be multiple integer types? One reason is to give us options for how much memory we want to allocate for our number.

For example, if our maximum Level is 50, we don't need to allocate enough memory for that variable to store huge numbers. 8 bits of memory would be enough - that would allow for 256 possible values in that space.

Bits and Bytes

A bit (short for "binary digit") is the smallest unit of data in computing. A bit can store two possible values - 1 or 0. By combining multiple bits, we can store more and more complicated data.

A byte is a combination of 8 bits, for example, 01001110 or 10101001. There are 256 different possible configurations for 8 bits. Therefore, a byte can store one of 256 possible values.

Those values could represent anything we want - for example, an integer from 0-255, one of 256 different alphabetic characters, or a colour from a palette of 256 options

If we use a variable that has more bits, we can store a wider range of values, at the expense of our software using some additional resources. 16 bit integers can store 65,536 different possibilities; 32 bits could store over 4 billion

The specifics of the different types of integers and their memory usage isn't that important at this stage. The basic int will have enough bits for our needs. It's just worth noting at this stage that there are many different types of integers. You may see alternatives being used in other places.

Signed and Unsigned Integers in C++

Another reason we have different types of integers is to give us more options on how we use the memory. We saw how 32 bits of memory give us access to around 4 billion different possible integers. But what range of integers should we map to those options?

We could use a range from around negative 2 billion to posiive 2 billion. But, what if we knew our variable could never be negative? We're wasting half of that range.

A number that cannot be negative is said to be unsigned. We can create an unsigned integer like this:

unsigned int Health { 5 };

This makes the intent of what our variables will contain clearer for anyone reading our code. In addition, our health values can get to be twice as large before our variable runs out of available memory.

C++ Overflows

Unfortunately, there is a major drawback with unsigned integers. Even for values we don't ever want to be negative, such as a Health value, using an unsigned number can cause a lot of pain.

Something weird happens when we push a number beyond its range. This is called an overflow and it happens when we decrease a number below it's lower limit, or increase a number above its upper limit.

With an unsigned integer, its lower limit is 0. A unsigned int cannot go below 0 - if our code causes that to happen, we will create an overflow.

Lets imagine we have an unsigned int that currently has a value of 0, and we cause an overflow by subtracting 1 from it:

int main()
{
  unsigned int Health { 0 };
  Health -= 1;
}

The effect is not that Health remains at 0. Rather, it wraps around to its other extreme.

Health has a value of 4,294,967,295 after running the previous code. This seems likely to cause some issues!

Having our variables regularly storing values that are at the extremes of their range makes our lives a lot more difficult. We could always be mindful of this and constantly code against overflows, but it's a pain and generally not worth it.

Rather, we should design our software that our variables are not constantly close to overflowing.

As a result, this means using unsigned integers for any variable we're going to be doing maths on is generally not recommended.

C++ Integer Division

What would the output to the console given the following line of code?

cout << 5/2;

Perhaps surprisingly, the output is 2.

We may have expected it to be 2.5 as we are dividing 5 by 2. However, dividing an integer by an integer always yields an integer.

So why is it not 3? The specification of the built in int data type is to discard any floating point component - not to round it. So, this leaves us with the integer 2

This is true whatever we do with the result of the expression. Above, we are logging it out, but we could also be assigning it to a variable, even a floating point variable:

int IntegerLevel { 5/2 };  // IntegerLevel is 2
float FloatingLevel { 5/2 }; // FloatingLevel is 2.0

Even though we're assigning the result of the expression to a floating point variable that could store 2.5, the 2/5 expression happens first.

5/2 resolves to 2, and then we convert 2 to a float, which yields 2.0.

Test your Knowledge

After running the following code, what will be the value of Level?

int Level { 3 / 2 };

After running the following code, what will be the value of Level?

float Level { 1 / 2 };

C++ Floating Point Numbers

In the previous example where we cout we saw that the division of two integers always yields an integer. If either, or both of the values in the expression were floating point numbers, we would get a floating point output.

All of these examples would log out 2.5:

cout << 5.0/2 << endl;
cout << 5/2.0 << endl;
cout << 5.0/2.0 << endl;

This is also true for the other basic maths operations. Combining a float and an integer in the same operation yields a float, regardless of whether the float is on the left or the right side of the operator:

5 + 1.0; // 6.0
5.0 + 1; // 6.0

5 - 1.0; // 4.0
5.0 - 1; // 4.0

5 * 2.0; // 10.0
5.0 * 2; // 10.0

5.0 / 2; // 2.5
5 / 2.0; // 2.5

We can create floating point variables in the way you might expect:

float Health { 2.5 };

The float data type also has the ability to be created from an integer

float Health { 5 }; // Health is 5.0

Like with any other variable, we can initialise it with an expression. For example, this can be the result of a maths operation, or the value contained in another variable:

float MaxHealth { 5.0 / 2.0 };  // MaxHealth is 2.5
float CurrentHealth { MaxHealth }; // CurrentHealth is 2.5

Remember, the expression happens before the variable type is considered.

In the below example, we perform integer division to yield 5 / 2 = 2. We then assign the integer 2 to the float Health, which will convert it to the floating point number 2.0

float Health { 5/2 }; // Health is 2.0

C++ Floating Point Operations

All the operators we've seen for integers are also available to floating point numbers.

float Health { 5.0 }; // Health is 5.0
Health = Health + 20.0; // Health is now 25.0
Health++; // Health is now 26.0
Health--; // Health is now 25.0

// We can freely combine floating and integer numbers in the expressions
Health += 25; // Health is now 50.0
Health -= 10; // Health is now 40.0
Health *= 2.5; // Health is now 100.0
Health /= 3; // Health is now 33.3333...

C++ Doubles and Other Float Types

Like with integers, we have different options for how much memory we want our floating point number to consume. Unlike with integers however, floating points are often implemented slightly differently in terms of how they use their memory.

What changes when we give floating points more memory is how precise they are. More bits allows floats to store more "significant figures", increasing their accuracy.

The most common data types for floating point numbers are float and double. A double is given the name because it uses double the number of bits as a regular float. This means a double is more precise than float at the expense of consuming more memory.

This is also the source of the phrase "double precision", which you may have heard in the context of science or programming.

The basic float will be totally sufficient for our needs. What's important is just to recognise that there are different options, and you may see them being used in other code samples.

Literals in C++

Literals can be quite a confusing concept. We've been using them quite a lot already, but it's worth taking a moment to explain them in a bit more depth.

When we write an expression like 4.0 or "Goblin Warrior", we are using a literal. A literal is simply a way of expressing a fixed value in our code.

The compiler infers the type of the literal based on the syntax we use. It correctly infers that an expression like 6 is an integer. However, perhaps surprisingly, 4.0 is not a float and "Goblin Warrior" is not a string.

4.0 is a double and "Hello World" is something called a char*, which we will explain in a future chapter.

If we wanted to be purist, a float literal should have an f suffix, for example, 4.0f

A string literal should have an s suffix, for example, "Goblin Warrior"s

float MyNumber { 4.0f };
string MyString { "Goblin Warrior"s };

However, we've seen the compiler is fully capable of converting a double to a float for us, and a char* to a string.

We saw this when statements like float Health { 25.7 }; work totally as we'd expect.

There is some nuance and caveats here, but to make our code samples as simple as possible for beginners, we will not include the f and s unless needed. In those rare scenarios where it is needed, we will point it out and explain why.

The Floating Component is Optional in C++

Often, we will be creating floating point numbers that are iniitally "round", eg, 4.0 and 5.0.

Where the floating component is 0, it can be removed entirely. This is shown below, where both variables will have the value 4.0:

float VariableA { 4. };
float VariableB { 4.f };

We don't do this in our code samples, but it's worth mentioning, as this often causes some confusion when looking at other people's code.

With our overview of numbers out of the way, lets pick up in the next lesson and look at the second fundamental data type - booleans.

Was this lesson useful?

Ryan McCombe
Ryan McCombe
Edited
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

C++ Fundamentals
3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, unlimited access!

This course includes:

  • 66 Lessons
  • Over 200 Quiz Questions
  • Capstone Project
  • Regularly Updated
  • Help and FAQ
Next Lesson

C++ Booleans - True and False values

An overview of the fundamental true or false data type, how we can create them, and how we can combine them.
3D art of a wizard character
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved