O.4 – Преобразование между двоичной и десятичной системами счисления
Рассмотрим обычное десятичное число, например 5623. Мы интуитивно понимаем, что эти цифры означают (5 * 1000) + (6 * 100) + (2 * 10) + (3 * 1). Поскольку имеется 10 десятичных чисел, значение каждой последующей цифры слева увеличивается в 10 раз.
Двоичные числа работают так же, за исключением того, что есть только 2 двоичные цифры (0 и 1), и значение каждой цифры при сдвиге влево увеличивается в 2 раза. Для облегчения чтения больших двоичных чисел мы часто записываем их группами по 4 бита (например, 1101 0101).
В следующей таблице приведены числа от 0 до 15 в десятичном и двоичном форматах:
Десятичное значение | Двоичное значение |
---|---|
0 | 0 |
1 | 1 |
2 | 10 |
3 | 11 |
4 | 100 |
5 | 101 |
6 | 110 |
7 | 111 |
8 | 1000 |
9 | 1001 |
10 | 1010 |
11 | 1011 |
12 | 1100 |
13 | 1101 |
14 | 1110 |
15 | 1111 |
Преобразование числа из двоичной системы счисления в десятичную
В следующих примерах предполагается, что мы имеем дело с целыми числами без знака.
Рассмотрим 8-битное (1 байт) двоичное число 0101 1110. Двоичное 0101 1110 означает (0 * 128) + (1 * 64) + (0 * 32) + (1 * 16) + (1 * 8) + (1 * 4) + (1 * 2) + (0 * 1). Если просуммировать все эти части, мы получим десятичное число 64 + 16 + 8 + 4 + 2 = 94.
Ниже показан тот же процесс в формате таблицы. Мы умножаем каждую двоичную цифру на ее числовое значение (определяемое ее позицией). Суммирование всех этих значений дает нам итоговый результат.
Преобразование 0101 1110 в десятичную форму:
Двоичное число | 0 | 1 | 0 | 1 | 1 | 1 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
* Значение цифры | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
= Результат (94) | 0 | 64 | 0 | 16 | 8 | 4 | 2 | 0 |
Преобразуем 1001 0111 в десятичную форму:
Двоичное число | 1 | 0 | 0 | 1 | 0 | 1 | 1 | 1 |
---|---|---|---|---|---|---|---|---|
* Значение цифры | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
= Результат (151) | 128 | 0 | 0 | 16 | 0 | 4 | 2 | 1 |
1001 0111 в двоичном формате = 151 в десятичном формате.
Этот способ можно легко расширить до 16- или 32-битных двоичных чисел, просто добавив дополнительные столбцы. Обратите внимание, что проще всего начать с правого конца и двигаться влево, умножая значение цифры на 2 по мере продвижения.
Метод 1 для преобразования числа из десятичной системы счисления в двоичную
Преобразование десятичного числа в двоичную форму немного сложнее, но всё же довольно простое. Для этого есть два хороших метода.
Первый метод заключается в постоянном делении на 2 и записи остатков. Двоичное число строится в конце из остатков снизу вверх.
Преобразование 148 из десятичной формы в двоичную (для обозначения остатка используется r, от «remainder»):
148 / 2 = 74 r0
74 / 2 = 37 r0
37 / 2 = 18 r1
18 / 2 = 9 r0
9 / 2 = 4 r1
4 / 2 = 2 r0
2 / 2 = 1 r0
1 / 2 = 0 r1
Записываем все остатки снизу вверх: 1001 0100
148 в десятичном формате = 1001 0100 в двоичном формате.
Вы можете проверить это, преобразовав двоичное число обратно в десятичное:
(1 * 128) + (0 * 64) + (0 * 32) + (1 * 16) + (0 * 8) + (1 * 4) + (0 * 2) + (0 * 1) = 148
Метод 2 для преобразования числа из десятичной системы счисления в двоичную
Второй метод, чтобы выяснить значение каждого бит, предполагает работу в обратном направлении. Этот метод может быть проще с небольшими двоичными числами.
Снова рассмотрим десятичное число 148. Какое наибольшее значение двойки, возведенной в степень, меньше 148? 128, так что мы начнем с него.
- 148 >= 128? Да, поэтому бит со значением 128 должен быть 1. 148 - 128 = 20, что означает, что нам нужно найти биты со значением больше, чем 20.
- 20 >= 64? Нет, поэтому бит со значением 64 должен быть 0.
- 20 >= 32? Нет, поэтому бит со значением 32 должен быть равен 0.
- 20 >= 16? Да, поэтому бит со значением 16 должен быть равен 1. 20 - 16 = 4, что означает, что нам нужно найти биты со значением больше, чем 4.
- 4 >= 8? Нет, поэтому бит со значением должен быть 0.
- 4 >= 4? Да, поэтому бит со значением должен быть 1. 4 - 4 = 0, что означает, что все остальные биты должны быть равны 0.
148 = (1 * 128) + (0 * 64) + (0 * 32) + (1 * 16) + (0 * 8) + (1 * 4) + (0 * 2) + (0 * 1) = 1001 0100
В формате таблицы:
Двоичное число | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 0 |
---|---|---|---|---|---|---|---|---|
* Значение цифры | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
= Результат (148) | 128 | 0 | 0 | 16 | 0 | 4 | 0 | 0 |
Еще один пример
Давайте, преобразуем 117 в двоичный код, используя метод 1:
117 / 2 = 58 r1
58 / 2 = 29 r0
29 / 2 = 14 r1
14 / 2 = 7 r0
7 / 2 = 3 r1
3 / 2 = 1 r1
1 / 2 = 0 r1
Построение числа из остатков снизу вверх, 117 = 111 0101 в двоичном формате.
И используя метод 2:
Наибольшее значение двойки, возведенной в степень, которое меньше 117, равно 64.
- 117 >= 64? Да, поэтому бит со значением 64 должен быть равен 1. 117 - 64 = 53.
- 53 >= 32? Да, поэтому бит со значением 32 должен быть равен 1. 53 - 32 = 21.
- 21 >= 16? Да, поэтому бит со значением 16 должен быть равен 1. 21 - 16 = 5.
- 5 >= 8? Нет, поэтому бит со значением 8 должен быть равен 0.
- 5 >= 4? Да, поэтому бит со значением 4 должен быть равен 1. 5 - 4 = 1.
- 1 >= 2? Нет, поэтому бит со значением 2 должен быть равен 0.
- 1 >= 1? Да, поэтому бит со значением 1 должен быть равен 1.
117 в десятичном формате = 111 0101 в двоичном формате.
Сложение в двоичном формате
В некоторых случаях (мы увидим один через секунду) бывает полезно иметь возможность сложить два двоичных числа. Складывать двоичные числа на удивление легко (возможно, даже проще, чем складывать десятичные числа), хотя поначалу это может показаться странным, потому что вы к этому еще не привыкли.
Рассмотрим два небольших двоичных числа:
0110 (6 в десятичной системе) +
0111 (7 в десятичной системе)
Давайте сложим их. Во-первых, выровняйте их, как мы делали ранее. Затем, начиная справа и продолжая влево, мы складываем цифры в каждом столбце, как мы это делаем с десятичными числами. Однако, поскольку двоичная цифра может быть только 0 или 1, есть только 4 возможных варианта:
- 0 + 0 = 0
- 0 + 1 = 1
- 1 + 0 = 1
- 1 + 1 = 0, перенести 1 в следующий столбец
Начнем с первого столбца:
0110 (6 в десятичной системе) +
0111 (7 в десятичной системе)
----
1
0 + 1 = 1. Легко.
Второй столбец:
1
0110 (6 в десятичной системе) +
0111 (7 в десятичной системе)
----
01
1 + 1 = 0, с перенесенной 1 в следующий столбец
Третий столбец:
11
0110 (6 в десятичной системе) +
0111 (7 в десятичной системе)
----
101
Это немного сложнее. Обычные 1 + 1 = 0, с переносом 1 в следующий столбец. Однако у нас уже есть 1, перенесенная из предыдущего столбца, поэтому нам нужно добавить 1. Таким образом, мы получаем 1 в этом столбце, и 1 переносится в следующий столбец.
Последний столбец:
11
0110 (6 в десятичной системе) +
0111 (7 в десятичной системе)
----
1101
0 + 0 = 0, но есть перенесенная 1, поэтому мы прибавляем 1. 1101 = 13 в десятичной системе.
Теперь, как нам добавить 1 к любому заданному двоичному числу (например, 1011 0011)? То же, что и выше, только нижнее число – это двоичная единица.
1 (перенос)
1011 0011 (исходное двоичное число)
0000 0001 (1 в двоичном формате)
---------
1011 0100
Числа со знаком и дополнительный код
В приведенных выше примерах мы имели дело исключительно с целыми числами без знака. В этом разделе мы рассмотрим, как обрабатываются числа со знаком (которые могут быть отрицательными).
Целые числа со знаком обычно хранятся с использованием метода, известного как дополнительный код (в англоязычной литературе известен как «дополнение двоек», «two's complement»). В дополнительном коде самый левый (самый старший) бит используется, как бит знака. Бит знака 0 означает, что число положительное, а бит знака 1 означает, что число отрицательное.
Положительные числа со знаком представлены в двоичном формате так же, как положительные числа без знака (с битом знака, установленным в 0).
Отрицательные числа со знаком представлены в двоичном виде как побитовая инверсия положительного числа плюс 1.
Преобразование целых чисел в двоичное представление в дополнительном коде
Например, как мы представляем -5 в двоичном дополнительном коде:
- сначала мы выясняем двоичное представление для 5: 0000 0101
- затем мы инвертируем все биты: 1111 1010
- затем прибавляем 1: 1111 1011
Преобразование числа -76 в двоичный формат:
- положительное число 76 в двоичном формате: 0100 1100
- инвертировать все биты: 1011 0011
- добавить 1: 1011 0100
Почему мы добавляем 1? Рассмотрим число 0. Если бы отрицательное значение было представлено просто как инвертированное положительное число, 0 имел бы два представления: 0000 0000 (положительный ноль) и 1111 1111 (отрицательный ноль). При добавлении единицы 1111 1111 намеренно переполняется и становится 0000 0000. Это предотвращает наличие двух представлений нуля и упрощает некоторую внутреннюю логику, необходимую для выполнения арифметических действий с отрицательными числами.
Преобразование двоичного числа в дополнительном коде в десятичную форму
Чтобы преобразовать двоичное число в дополнительном коде обратно в десятичную форму, сначала посмотрите на бит знака.
Если бит знака равен 0, просто преобразуйте число, как было показано выше для чисел без знака.
Если бит знака равен 1, мы инвертируем биты, добавляем 1, затем выполняем преобразование в десятичное число, а затем делаем это десятичное число отрицательным (поскольку бит знака изначально был отрицательным).
Например, чтобы преобразовать 1001 1110 в дополнительном коде в десятичное число:
- дано: 1001 1110
- инвертируем биты: 0110 0001
- добавляем 1: 0110 0010
- преобразуем в десятичную форму:
(0 * 128) + (1 * 64) + (1 * 32) + (0 * 16) + (0 * 8) + (0 * 4) + (1 * 2) + (0 * 1 ) = 64 + 32 + 2 = 98 - поскольку исходный бит знака был отрицательным, окончательное значение равно -98.
Почему типы имеют значение
Рассмотрим двоичное значение 1011 0100. Какое значение оно представляет? Вы, вероятно, сказали бы 180, и если бы это было стандартное двоичное число без знака, вы были бы правы.
Однако, если бы это значение было сохранено с использованием дополнительного кода, оно было бы равно -76.
А если бы значение было закодировано другим способом, оно могло быть чем-то совсем другим.
Итак, как C++ узнает, печатать ли переменную, содержащую двоичный код 1011 0100, как 180 или -76?
Здесь в игру вступают типы. Тип переменной определяет, как значение переменной кодируется в двоичном формате и декодируется обратно в десятичное представление. Таким образом, если тип переменной был целочисленным без знака, компилятор знал бы, что 1011 0100 было стандартным двоичным значением и должно быть напечатано как 180. Если бы переменная была целочисленного типа со знаком, компилятор знал бы, что 1011 0100 было закодировано с использованием дополнительного кода (в C++20 теперь это гарантировано) и должно быть напечатано как -76.
А как насчет преобразования чисел с плавающей запятой из/в двоичное?
Преобразование чисел с плавающей запятой из/в двоичный формат – это немного сложнее, и вам вряд ли когда-либо понадобится знать. Однако, если вам интересно, мы рассмотрим эту тему подробнее позже.
Небольшой тест
Вопрос 1
Преобразуйте 0100 1101 в десятичную форму.
Ответ
Двоичное число | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
---|---|---|---|---|---|---|---|---|
* Значение цифры | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
= Результат (77) | 0 | 64 | 0 | 0 | 8 | 4 | 0 | 1 |
Ответ 77.
Вопрос 2
Преобразуйте 93 в 8-битное двоичное число без знака. Используйте оба метода, описанные выше.
Ответ
Используя метод 1:
93 / 2 = 46 r1
46 / 2 = 23 r0
23 / 2 = 11 r1
11 / 2 = 5 r1
5 / 2 = 2 r1
2 / 2 = 1 r0
1 / 2 = 0 r1
Работая с остатками в обратном направлении, получаем 101 1101
Используя метод 2:
- Наибольшее значение двойки, возведенной в степень, которое меньше 93, равно 64.
- 93 >= 64? Да, поэтому бит со значением 64 равен 1. 93 - 64 = 29.
- 29 >= 32? Нет, поэтому бит со значением 32 равен 0.
- 29 >= 16? Да, поэтому бит со значением 16 равен 1. 29 - 16 = 13.
- 13 >= 8? Да, поэтому бит со значением 8 равен 1. 13 - 8 = 5.
- 5 >= 4? Да, поэтому бит со значением 4 равен 1. 5 - 4 = 1.
- 1 >= 2? Нет, поэтому бит со значением 2 равен 0.
- 1 >= 1? Да, поэтому бит со значением 1 равен 1.
Ответ: 0101 1101.
Вопрос 3
Преобразуйте -93 в 8-битное двоичное число со знаком (с использованием дополнительного кода).
Ответ
- Из предыдущего ответа мы уже знаем, что 93 – это 0101 1101
- Для дополнительного кода инвертируем биты: 1010 0010
- И прибавляем 1: 1010 0011
Вопрос 4
Преобразуйте 1010 0010 в десятичное число без знака.
Ответ
Работаем справа налево:
1010 0010 = (0 * 1) + (1 * 2) + (0 * 4) + (0 * 8) + (0 * 16) + (1 * 32) + (0 * 64) + (1 * 128) = 2 + 32 + 128 = 162
Ответ: 162.
Вопрос 5
Преобразуйте 1010 0010 в десятичное число со знаком (исходное число представлено в дополнительном коде).
Ответ
Поскольку нам сказано, что это число представлено в дополнительном коде, мы можем «отменить» дополнительный код, инвертировав биты и добавив 1.
- Сначала начнем с нашего двоичного числа: 1010 0010
- Инвертируем биты: 0101 1101
- Добавляем 1: 0101 1110
- Преобразуем в десятичную форму: 64 + 16 + 8 + 4 + 2 = 94
- Помним, что это дополнительный код, а исходный левый бит был отрицательным: -94
Ответ: -94
Вопрос 6
Напишите программу, которая просит пользователя ввести число от 0 до 255. Напечатайте это число как 8-битное двоичное число (в форме #### ####). Не используйте побитовые операторы. Не используйте std::bitset
.
Подсказка 1
Используйте метод 2. Предположим, что наибольшее значение двойки, возведенной в степень, равно 128.
Подсказка 2
Напишите функцию, чтобы проверить, больше ли введенное вами число, чем значение двойки, возведенной в некоторую степень. Если да, выведите '1' и верните свое число за вычетом значения двойки, возведенной в степень.
Ответ
#include <iostream>
int printAndDecrementOne(int x, int pow)
{
std::cout << '1';
return (x - pow);
}
// x - это наше число для проверки
// pow - это двойка, возведенная в некоторую степень (т.е. 128, 64, 32, и т.д.)
int printAndDecrementBit(int x, int pow)
{
// Проверяем, больше ли наш x двойки, возведенной в некоторую степень,
// и распечатываем бит
if (x >= pow)
return printAndDecrementOne(x, pow); // Если x больше, чем наша двойка, возведенная степень,
// вычесть эту двойку, возведенную в степень
// x меньше pow
std::cout << '0';
return x;
}
int main()
{
std::cout << "Enter an integer between 0 and 255: ";
int x{};
std::cin >> x;
x = printAndDecrementBit(x, 128);
x = printAndDecrementBit(x, 64);
x = printAndDecrementBit(x, 32);
x = printAndDecrementBit(x, 16);
std::cout << ' ';
x = printAndDecrementBit(x, 8);
x = printAndDecrementBit(x, 4);
x = printAndDecrementBit(x, 2);
x = printAndDecrementBit(x, 1);
std::cout << '\n';
return 0;
}