Перейти к содержимому
Topos Uranos

Краткое содержание

Целое число в программе не есть целое число математики: это конечная последовательность битов, и в nn битах помещается ровно 2n2^n различных значений. Этот урок изучает такое представление и его следствия. Он учит записывать целое число в двоичной и шестнадцатеричной системах и проверять запись спецификациями формата {:b} и {:x}; представляет целочисленные типы C++, их размер, который измеряется с помощью sizeof, и их диапазон значений, который узнают с помощью std::numeric_limits; объясняет, как представляются отрицательные числа с помощью дополнительного кода и почему диапазон несимметричен; различает знаковые и беззнаковые целые и изучает, что происходит, когда операция выходит за пределы диапазона: для беззнаковых стандарт определяет результат, и это арифметика по модулю 2n2^n; для знаковых не определяет, и программа лишается всяких гарантий. В конце дан критерий выбора типа для каждого значения.

Цели обучения

  1. Переводить целые числа между системами с основаниями 10, 2 и 16 и проверять перевод спецификациями формата {:b} и {:x}.
  2. Вычислять диапазон значений целого числа из nn битов, знакового и беззнакового, и проверять его с помощью std::numeric_limits и sizeof.
  3. Объяснять дополнительный код и записывать представление небольшого отрицательного числа в восьми битах.
  4. Отличать знаковое переполнение, которое есть неопределённое поведение, от беззнакового переполнения, которое есть арифметика по модулю 2n2^n, и предсказывать результат второго.
  5. Выбирать подходящий целочисленный тип для значения по его ожидаемому диапазону, используя типы фиксированной ширины из <cstdint>, когда ширина важна.

Позиционные системы счисления

Урок Переменные и память представил переменную как ячейку с именем, типом и значением. Однако память хранит не десятичные цифры, а биты, каждый из которых равен 0 или 1, сгруппированные в байты по восемь битов (стандарт требует не менее восьми; все современные системы используют ровно восемь). Чтобы знать, какие значения может принимать целочисленная переменная, нужно, следовательно, знать, как записать число всего двумя цифрами, и именно это делает позиционная система счисления с основанием 2.

В позиционной системе счисления с основанием bb каждая цифра весит в зависимости от занимаемой позиции: последняя умножается на b0=1b^0 = 1, стоящая перед ней на b1b^1, стоящая перед той на b2b^2, и так для всех цифр. В десятичной системе 20262026 означает 2⋅103+0⋅102+2⋅101+6⋅1002 \cdot 10^3 + 0 \cdot 10^2 + 2 \cdot 10^1 + 6 \cdot 10^0. В двоичной системе цифры суть 0 и 1, а веса суть степени двойки: 1,2,4,8,16,32,64,128,…1, 2, 4, 8, 16, 32, 64, 128, \ldots Отсюда следует правило перевода из двоичной системы в десятичную: складываются веса позиций, где стоит 1.

ПримерИз двоичной системы в десятичную

Рисунок читает восьмибитовую комбинацию 00101101. Над каждой ячейкой указан её вес; единицы зажигаются по одной, слева направо, и значение накапливает их веса.

Доказательство

  1. Восемь ячеек, все погашены: значение равно 0. Веса, справа налево, равны 1,2,4,…,1281, 2, 4, \ldots, 128.

  2. Первая единица стоит в позиции с весом 32: накопленное значение равно 32.

  3. Следующая, в позиции с весом 8: 32+8=4032 + 8 = 40.

  4. Следующая, в позиции с весом 4: 40+4=4440 + 4 = 44.

  5. Последняя, в позиции с весом 1: 44+1=4544 + 1 = 45. Комбинация 00101101 представляет число 45.

  6. Сгруппированные по четыре, биты дают шестнадцатеричные цифры: 0010 есть 2, а 1101 есть 13, что в шестнадцатеричной системе записывается как d. Следовательно, 45 записывается в шестнадцатеричной системе как 2d.

Обратный путь, из десятичной системы в двоичную, проходится последовательным делением: число делят на 2, записывают остаток (0 или 1) и повторяют с частным, пока не получится 0; остатки, прочитанные от последнего к первому, и есть двоичные цифры. Для 45: 45=2⋅22+145 = 2 \cdot 22 + 1, 22=2⋅11+022 = 2 \cdot 11 + 0, 11=2⋅5+111 = 2 \cdot 5 + 1, 5=2⋅2+15 = 2 \cdot 2 + 1, 2=2⋅1+02 = 2 \cdot 1 + 0, 1=2⋅0+11 = 2 \cdot 0 + 1; прочитанные в обратном порядке, остатки дают 101101, что с двумя нулями слева есть комбинация с рисунка. Причина в том, что каждый остаток есть цифра единиц делимого числа, а каждое частное есть это число, сдвинутое на одну позицию вправо. Тот же метод годится для любого основания: для основания 16 делят на 16, а остатки от 10 до 15 записывают буквами от a до f.

Основание 16, или шестнадцатеричная система, существует ради удобства: так как 16=2416 = 2^4, каждая шестнадцатеричная цифра соответствует ровно четырём битам, и байт записывается двумя цифрами. 32-битовое число занимает тридцать две цифры в двоичной записи, которая плохо читается, и всего восемь в шестнадцатеричной, которые переводятся в двоичную запись без вычислений, цифра за цифрой. Поэтому программисты читают память в шестнадцатеричной записи. Математическое обоснование позиционных систем изучается в курсе «Алгебра и геометрия I», в уроке «Позиционные системы счисления».

C++ допускает целые числа, записанные прямо в этих системах: литерал, начинающийся с 0b, записан в двоичной системе, а начинающийся с 0x, в шестнадцатеричной. Внутри любого литерала можно поставить апостроф как разделитель разрядов, который компилятор игнорирует, а читатель ценит: 0b0010'1101, 1'000'000. При выводе спецификация формата {:b} записывает целое число в двоичной системе, {:x} в шестнадцатеричной строчными буквами, а {:X} прописными; знак # добавляет префикс, а ширина, перед которой стоит ноль, дополняет число нулями слева.

ПримерОдно число, три записи
Программа на C++
#include <print>​int main(){    const int decimal{45};    const int binary{0b0010'1101};    const int hexadecimal{0x2d};    std::println("{} {} {}", decimal, binary, hexadecimal);    std::println("двоичная: {:b}", decimal);    std::println("восемь битов: {:08b}", decimal);    std::println("шестнадцатеричная: {:x} {:X} {:#x}", decimal, decimal, decimal);}
Вывод
45 45 45двоичная: 101101восемь битов: 00101101шестнадцатеричная: 2d 2D 0x2d

Три переменные хранят одно и то же значение, записанное в трёх системах, и первая строка вывода это показывает: система счисления есть свойство записи, а не числа. Память во всех трёх случаях хранит одну и ту же битовую комбинацию. Спецификация {:b} опускает нули слева; {:08b} дополняет запись до восьми цифр.