Embercode
✕
C++ Variables and Data TypesLesson 6 of 15

35 min read

Literals and number bases

Why this matters

A program often needs a number written directly in the source code. A colour is written as 0xFF8800. A Unix file permission is written as 0755. A large population count is written as 8'000'000'000LL. Each of these is a literal, which means a value written out in full in the code. The opposite is a value that the program works out while it runs, such as the result of adding two variables.

The same number can be written in several ways. The number 255 can be written as 255, as 0xFF, as 0b11111111 and as 0377. All four spellings produce the same value in memory. The different spellings exist because programmers think about numbers in different bases, which is the subject of this lesson. A beginner who does not know the rules will sometimes get a wrong number with no error at all. For example, 010 is not ten. The leading zero makes it an octal number, and its value is 8.

This lesson covers four spellings, the digit separator, and the type that the compiler gives each literal. It does not explain why a number that is too big for its type gets a wrong value. That belongs to the two's complement lesson later in this module. Here it is enough to know that a 32-bit int holds whole numbers from about minus 2 billion to about 2 billion on the systems this lesson was tested on.

The examples use the GNU C++ compiler, g++, version 15.2, on 64-bit Linux. Compile a file with:

g++ -std=c++20 -Wall -o demo demo.cpp
./demo

The -std=c++20 part selects the C++20 language rules. The -Wall part turns on the common warnings. The -o demo part sets the name of the program that the compiler writes. The ./demo command runs it.

What a number base is

A base is the number of digit symbols a number system uses. Everyday counting uses base 10, which is called decimal. It has ten digit symbols, 0 to 9. The place of a digit decides how much it is worth. In the number 325, the 3 is worth 3 hundreds, the 2 is worth 2 tens, and the 5 is worth 5 ones. Each place is worth ten times the place on its right.

Base 2 is binary. It has two digit symbols, 0 and 1. Each place is worth twice the place on its right. Base 16 is hexadecimal, which is usually shortened to hex. It has sixteen digit symbols: 0 to 9 and then the letters A to F. The letter A means 10, and F means 15. Each place is worth sixteen times the place on its right. Base 8 is octal. It has eight digit symbols, 0 to 7, and each place is worth eight times the place on its right.

The table shows one amount counted in each base. Read the first row, then compare the others.

BaseDigits usedWritten asPlace values from the rightValue
100 to 92555, then 5 x 10, then 2 x 100255
20 and 1111111111, 2, 4, 8, 16, 32, 64, 128, all eight 1s255
160 to 9 and A to FFFF x 1, then F x 16255
80 to 73777 x 1, then 7 x 8, then 3 x 64255

The four rows describe one amount. The digits change from row to row, but the amount stays the same. C++ lets you type a literal in any of these bases. The compiler then stores the same pattern of bits, which are the ones and zeros that the computer keeps in memory.

Decimal literals

A decimal literal is the normal way to write a number. Any run of digits that does not start with 0 is a decimal literal. A single 0 on its own is also a decimal literal, because it is zero in every base.

C++ · editable

The output is:

42
0
-7

The minus sign is not part of the literal. C++ reads -7 as the minus operator applied to the literal 7. This makes no difference for most programs.

Hexadecimal, binary and octal literals

Each of the three other spellings starts with a marker that tells the compiler which base to use.

  • A hexadecimal literal starts with 0x or 0X, for example 0xFF.
  • A binary literal starts with 0b or 0B, for example 0b11111111. Binary literals were added in C++14, so an older compiler will reject them.
  • An octal literal starts with a single 0, for example 0377.

The marker is part of the spelling, but it is not a digit. The compiler reads the digits after it in the chosen base. Here is the same value written four ways, printed in decimal first and then printed in the three bases with std::format.

C++ · editable

The output is:

255 255 255 255
0xff 0b11111111 0377

The first line shows that all four variables hold the same value. The second line shows the reverse direction. The std::format function takes a format string, where each {} is replaced by the next argument. The text after the colon inside the braces is a format option. #x prints the number in hexadecimal with a 0x prefix. #b prints binary with a 0b prefix. #o prints octal with a leading 0. The std::format function is part of the C++20 standard library.

Notice that the printed hex digits are lower case, ff. The x option prints lower case letters. The X option prints upper case. The spelling in the source file can use either case. 0xff and 0xFF are the same literal.

The base of a number is a matter of printing, not of storage. The variable holds one value, and the stream settings called manipulators, std::hex, std::oct and std::dec, change only how std::cout shows it from then on. This program prints the same variable in three bases.

C++ · editable

The output is:

255
ff
0xff
0377
255
0x100

The std::showbase manipulator adds the 0x or 0 prefix to the output. The last line shows that arithmetic is not affected by the base. The literal 0xff is the number 255, so adding 1 gives 256, which prints as 0x100 in hex. The stream state stays in hex until std::dec is written, and a program that prints many numbers should set the base once and reset it when done.

Digit separators

A long number is hard to read at a glance. Is 1000000 one million or ten million? C++14 added the digit separator, which is a single quote placed between digits. The compiler ignores it. It is only there for people reading the code.

C++ · editable

The output is:

1000000
16746496
170
8000000000

The digit separator is not a comma. You may group the digits any way that makes sense, such as every four digits in a hex colour or every eight digits in a binary byte. The compiler accepts 0xFFFF'FFFF and 0xFF'FF'FF'FF as the same number, which is 4294967295.

The rules are strict about where the quote may go. The quote must sit between two digits. It cannot be the first or last character of a literal, and it cannot come right after the 0x or 0b marker. Each of these breaks a rule, and the compiler says so.

Worked example: a colour split into parts

Hex literals are useful when a number is made of separate bytes. A web colour such as orange is written as 0xFF8800. Each pair of hex digits is one byte, which is a group of 8 binary digits: red is FF, green is 88, and blue is 00. Each byte holds a value from 0 to 255. This program splits the colour back into its three bytes using division and remainder, which are the operators / and %.

C++ · editable

The output is:

red 255, green 136, blue 0

The number 65536 is 0x10000, which is 256 times 256. Dividing by it removes the two lower bytes and leaves the red byte. The division colour / 256 removes the lowest byte, and the remainder % 256 keeps the middle byte. The hex spelling makes it easy to see the three bytes in the source, and the division shows the same bytes as decimal numbers.

The keyword constexpr tells the compiler that colour is a fixed value that the compiler knows when it builds the program. It is not needed for the literal to work, but it is a good habit for named constants.

Worked example: Unix file permissions

On Unix systems, a file's permissions are often written as an octal number such as 0755. Each octal digit covers three permission bits: read, write and execute. The leading 0 tells the compiler that the number is octal. This program shows the same value in octal and decimal, and it shows what goes wrong when the leading zero is missing.

C++ · editable

The output is:

0755 as decimal: 493
493 as octal: 755
755 without the zero: 755

The octal literal 0755 has the value 493. The value of 493 written in octal is 755, so the two spellings match. Writing 755 with no leading zero makes it a decimal number, which is a different value. This is the reason that programs which set permissions often write the octal form on purpose, and why a missing zero is a real bug.

What type each literal gets

Every literal has a type, and the type decides how much memory the value takes and how it behaves in arithmetic. The C++ standard gives an ordered list of candidate types for each kind of spelling. The compiler chooses the first type in the list that can hold the value.

For decimal literals without a suffix, the list is int, then long, then long long. A decimal literal is never given an unsigned type unless you add a suffix.

For hexadecimal, octal and binary literals, the list is longer: int, then unsigned int (which cannot hold negative numbers), then long, then unsigned long, then long long, then unsigned long long. A hex literal that is too big for int may become unsigned int instead of long, even though the value is positive.

A suffix is a letter or pair of letters written after the digits. It forces a type. The suffix u or U means unsigned. The suffix l or L means long, and ll or LL means long long. The suffixes can be combined, so 10UL is an unsigned long.

The following program asks the compiler for the type of each literal. The decltype keyword gives the type of an expression, and std::is_same_v compares two types and gives 1 when they match. The sizes in these results depend on the system. On 64-bit Linux, long is 64 bits, while on 64-bit Windows it is only 32 bits.

C++ · editable

The output is:

decimal_small is int: 1
decimal_big is long: 1
decimal_huge is long: 1
hex_small is int: 1
hex_wide is unsigned int: 1
hex_huge is unsigned long: 1
with_u is unsigned int: 1
with_ll is long long: 1

The result shows the main pattern. The number 4294967295 is too large for a 32-bit int, so it moves up to long. The number 0xFFFFFFFF is also too large for int, but it fits in unsigned int, so the hex spelling gets the unsigned type first. This is why the same value can have a different type depending on how it is written.

The table below gives the type that the same kinds of literal got in the program above, with the value in the first column.

LiteralType on 64-bit Linux with g++ 15.2Why
1'000'000intFits in int.
4294967295longDecimal, too big for int, so the next candidate is used.
0xFFFF'FFFFunsigned intHex, fits in unsigned int.
0x1'0000'0000longHex, too big for any 32-bit type.
10uunsigned intThe suffix forces unsigned.
10ULunsigned longThe suffix forces unsigned long.
8'000'000'000LLlong longThe suffix forces long long.

Knowing the type matters when a literal is assigned to a smaller variable. Here is a short program that stores the number 3000000000 in an int and prints the result. The compiler gives no warning for it, even with -Wall.

C++ · editable

The program still runs and prints a wrong number. The output is shown below.

-1294967296

The value 3000000000 does not fit in a 32-bit int, so the stored value is wrong. Use long long or unsigned int when the number is large.

Which spelling to use

Pick the spelling that a reader will understand fastest. The compiler does not care which one you pick.

  • Use decimal for amounts, counts, sizes and anything a person reads as a normal number, such as 1'000'000 for a loop limit.
  • Use hex for bytes and colours, because two hex digits make exactly one byte, and 0xFF is easier to read than 255 when the value is a mask of bits.
  • Use binary when each bit has its own meaning, such as a set of on and off flags. Eight binary digits show eight flags in one line.
  • Use octal for Unix file permissions, where each octal digit covers three bits. Other uses of octal are rare, and a reader may take a leading zero for a typing error.

Common mistakes

A leading zero makes the number octal. A program that writes int count = 010; stores 8, not 10. The first zero changes the base. This is the most common surprise with octal literals. If you want decimal ten, write 10.

A digit that does not belong to the base is an error. Octal digits stop at 7, and binary digits stop at 1. The compiler reports these as errors. Here are the real messages from g++ 15.2 for three bad literals:

error: invalid digit ‘9’ in octal constant        (for int a = 09;)
error: invalid digit ‘2’ in binary constant       (for int b = 0b102;)

The first message comes from 09, where 9 is not a valid octal digit. The second comes from 0b102, where the 2 is not a valid binary digit.

A digit separator in the wrong place is an error. The quote cannot come right after a base marker, and it cannot end a literal. The compiler reports these as follows:

error: digit separator after base indicator        (for int a = 0x'FF;)
error: missing terminating ' character             (for int b = 1000';)

The first message is about 0x'FF. The quote sits between the x and the first digit. The second message is about 1000', where the quote is at the end. The compiler reads the quote as the start of a character literal and then finds no closing quote. The message also comes with a warning and a second error that you can ignore. Remove the quote and the literal is valid again.

A value too big for its type is silently changed. A large decimal literal assigned to int compiles, but it does not keep its value. With -Wall, the compiler warns:

warning: overflow in conversion from ‘long int’ to ‘int’ changes value from ‘1000000000000’ to ‘-727379968’ [-Woverflow]

Read every warning. A program that compiles with warnings can still store the wrong number.

Hex can look negative without being a mistake. The literal 0xFFFFFFFF is an unsigned int. Storing it in an int keeps the same bit pattern, and the same bits read as a signed int give -1. The program int x = 0xFFFFFFFF; prints -1 without any warning under -Wall -Wextra. The bits are right for a bit mask, but the number is not the one you may expect. Use unsigned int when the value is a pattern of bits.

A missing suffix can pick a type you did not plan for. The number 2147483648 has no suffix, so on 64-bit Linux it becomes long. On a system where long is 32 bits, the same literal becomes long long instead. Add a suffix such as LL when the exact type matters.

Practice

  1. What does this program print?
#include <iostream>
 
int main() {
    std::cout << 0x1F << ' ' << 017 << ' ' << 0b1001 << '\n';
}

Show the answer

It prints 31 15 9. The hex value 0x1F is 16 + 15, which is 31. The octal value 017 is 1 times 8 + 7, which is 15. The binary value 0b1001 is 8 + 1, which is 9.

  1. Which of these four literals does not compile: 1'000, 0xFF'FF, 0x'FF or 0b1'01?

Show the answer

The literal 0x'FF does not compile. The quote comes right after the 0x marker, and the compiler reports "digit separator after base indicator". The other three literals follow the placement rules and compile.

  1. Rewrite the number 1234567 with digit separators so that the groups of three are easy to see.

Show the answer

Write 1'234'567. The compiler stores the same value as 1234567.

  1. The variable colour holds 0xFF8800. Write an expression that gives the green part, which is the middle byte. The answer is 136.

Show the answer

Write colour / 256 % 256. The division by 256 removes the lowest byte, which is blue. The remainder after dividing by 256 keeps the middle byte, which is green.

  1. What type does the compiler give to each of auto a = 0xFFFFFFFF; and auto b = 4294967295; on 64-bit Linux?

Show the answer

The variable a has type unsigned int, because the hex literal fits in that type. The variable b has type long, because the decimal literal does not fit in int, and decimal literals never become unsigned without a suffix.

Summary

  • A literal is a value written in full in the source code, and the same number can be written in decimal, hex, binary or octal.
  • The prefix 0x means hex, 0b means binary, and a single leading 0 means octal. A leading zero on a decimal number changes its value.
  • A digit separator is a single quote between digits. The compiler ignores it, and it cannot sit next to a base marker or at either end of the literal.
  • A suffix such as u, L or LL forces the type of a literal.
  • Without a suffix, the compiler picks the first type from a fixed list that can hold the value, so a hex literal and a decimal literal of the same size can get different types.
  • A literal that is too big for its variable compiles with a warning, and the stored value is wrong. Read every warning that -Wall prints.
Check yourself: Literals and bases (6 questions) →Practice this topic: coding problems for C++ Variables and Data Types →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Module assignment: ForgeStack receipt