Summary
An integer in a program is not an integer of mathematics: it is a finite sequence of bits, and with bits exactly distinct values fit. This lesson studies that representation and its consequences. It teaches how to write an integer in base 2 and in base 16 and how to check it with the format specifications {:b} and {:x}; it presents the integer types of C++, their size, which is measured with sizeof, and their range, which is looked up with std::numeric_limits; it explains how negatives are represented by means of two’s complement and why the range is not symmetric; it distinguishes signed integers from unsigned integers, and it studies what happens when an operation leaves the range: for unsigned ones, the standard defines the result, which is arithmetic modulo ; for signed ones, it does not, and the program falls outside every guarantee. It ends with a criterion for choosing the type of each piece of data.
Learning objectives
- Convert integers between bases 10, 2 and 16, and check the conversion with the format specifications
{:b}and{:x}. - Compute the range of a signed and of an unsigned integer of bits, and check it with
std::numeric_limitsandsizeof. - Explain two’s complement and write the eight-bit representation of a small negative number.
- Distinguish signed overflow, which is undefined behavior, from unsigned overflow, which is arithmetic modulo , and predict the result of the latter.
- Choose the appropriate integer type for a value according to its foreseeable range, using the fixed-width types of
<cstdint>when the width matters.
Positional systems
The lesson Variables and memory presented the variable as a cell with a name, a type and a value. Now, memory does not store decimal digits: it stores bits, each of which is 0 or 1, grouped into bytes of eight bits (the standard requires at least eight; all current systems use exactly eight). To know which values an integer variable can take, one must therefore know how a number is written with only two digits, and that is what a positional system of base 2 does.
In a positional system of base , each digit counts according to the position it occupies: the last one multiplies , the one before it , the one before that , and so on. In base 10, means . In base 2 the digits are 0 and 1, and the weights are the powers of 2: From this follows the rule for going from binary to decimal: add the weights of the positions that hold a 1.
The figure reads the eight-bit pattern 00101101. Above each cell is its weight; the ones light up one by one, from left to right, and the value accumulates their weights.
Proof
Eight cells, all off: the value is 0. The weights, from right to left, are .
The first one is in the position of weight 32: the accumulated value is 32.
The next, in the one of weight 8: .
The next, in the one of weight 4: .
The last, in the one of weight 1: . The pattern
00101101represents the number 45.Grouped four by four, the bits give the hexadecimal digits:
0010is 2 and1101is 13, which in base 16 is writtend. Therefore, 45 is written2din base 16.
The reverse path, from decimal to binary, is traveled by repeated division: divide the number by 2, write down the remainder (0 or 1) and repeat with the quotient until reaching 0; the remainders, read from the last to the first, are the binary digits. For 45: , , , , , ; read backward, the remainders give 101101, which with two zeros on the left is the pattern of the figure. The reason is that each remainder is the units digit of the number being divided, and each quotient is that number shifted one position to the right. The same method works for any base: for base 16 one divides by 16, and the remainders from 10 to 15 are written with the letters a to f.
Base 16, or hexadecimal, exists for convenience: since , each hexadecimal digit corresponds exactly to four bits, and a byte is written with two digits. A 32-bit number takes thirty-two digits in binary, which are hard to read, and only eight in hexadecimal, which convert to binary without calculation, digit by digit. That is why programmers read memory in hexadecimal. The mathematical foundation of positional systems is studied in the course Algebra and Geometry I, in the lesson “Positional numeral systems”.
C++ accepts integers written directly in these bases: a literal that begins with 0b is in binary, and one that begins with 0x, in hexadecimal. Inside any literal an apostrophe can be placed as a digit separator, which the compiler ignores and the reader appreciates: 0b0010'1101, 1'000'000. In the output, the format specification {:b} writes an integer in binary, {:x} in hexadecimal with lowercase letters and {:X} with uppercase letters; the sign # prepends the prefix, and a width preceded by a zero pads with zeros on the left.
#include <print>int main(){ const int decimal{45}; const int binary{0b0010'1101}; const int hexadecimal{0x2d}; std::println("{} {} {}", decimal, binary, hexadecimal); std::println("binary: {:b}", decimal); std::println("eight bits: {:08b}", decimal); std::println("hexadecimal: {:x} {:X} {:#x}", decimal, decimal, decimal);}45 45 45binary: 101101eight bits: 00101101hexadecimal: 2d 2D 0x2dThe three variables hold the same value, written in three bases, and the first line of the output shows it: the base is a property of the notation, not of the number. In all three cases memory holds the same bit pattern. The specification {:b} omits the zeros on the left; {:08b} completes eight digits.
Loading the content…
Completed
Sign in to save your progress