Чтобы научиться конвертировать числа из двоичной (бинарной) системы счисления в десятичную и наоборот, прежде всего необходимо понять, как целые числа представлены в двоичной системе. Мы уже немного говорили об этом на уроке №31.
- Представление чисел в двоичной системе
- Конвертация чисел из двоичной системы в десятичную
- Способ №1: Конвертация чисел из десятичной системы в двоичную
- Способ №2: Конвертация чисел из десятичной системы в двоичную
- Еще один пример
- Сложение двоичных чисел
- Числа signed и метод «two’s complement»
- Почему так важен тип данных?
- Тест
- Ответы
Представление чисел в двоичной системе
Рассмотрим обычное десятичное число, например, число 5623. Интуитивно понятно, что означают все эти цифры: (5 * 1000) + (6 * 100) + (2 * 10) + (3 * 1)
. Так как в десятичной системе счисления всего 10 цифр, то каждое значение умножается на множитель 10 в степени n. Выражение, приведенное выше, можно записать следующим образом: (5 * 103) + (6 * 102) + (2 * 101) + (3 * 1)
.
Двоичные числа работают по аналогичной схеме, за исключением того, что в системе всего 2 числа (0 и 1) и множитель не 10, а 2. Так же как запятые (или пробелы) используются для улучшения читабельности больших десятичных чисел (например, 1, 427, 435
), двоичные числа пишутся группами — в каждой по 4 цифры (например, 1101 0101
).
Десятичное значение | Двоичное значение |
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 |
Конвертация чисел из двоичной системы в десятичную
В примерах, приведенных ниже, предполагается, что мы работаем с целочисленными значениями unsigned. Рассмотрим 8-битное (1-байтовое) двоичное число: 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 и записывании остатков. Если остаток («r» от англ. «remainder») есть, то пишем 1
, если нет — пишем 0
. Затем, читая остатки снизу вверх, мы получим готовое двоичное число.
Например, конвертация десятичного числа 148 в двоичную систему счисления:
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 еще раз. Какое наибольшее число, умноженное на 2 (из ряда 1, 2, 4, 8, 16, 32, 64, 128, 256 и т.д.), меньше 148? Ответ: 128.
148 >= 128? Да, поэтому 128-й бит равен 1. 148 − 128 = 20
20 >= 64? Нет, поэтому 64-й бит равен 0.
20 >= 32? Нет, поэтому 32-й бит равен 0.
20 >= 16? Да, поэтому 16-й бит равен 1. 20 − 16 = 4
4 >= 8? Нет, поэтому 8-й бит равен 0.
4 >= 4? Да, поэтому 4-й бит равен 1. 4 − 4 = 0, что означает, что все остальные биты равны 0.
Примечание: Если ответом является «Да», то мы имеем true, что означает 1. Если ответом является «Нет», то мы имеем false, что означает 0. Детально об этом читайте в материалах урока №34.
Результат:
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:
Наибольшее число, умноженное на 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 мы переносим дальше.
Последняя колонка:
11
0110 (6 в десятичной системе) +
0111 (7 в десятичной системе)
----
1101
0 + 0 = 0, но так как есть еще 1, то результат — 1101.
13 (десятичное) = 1101 (двоичное)
Вы спросите: «А как добавить десятичную единицу к любому другому двоичному числу (например, к 1011 0011)?». Точно так же, как мы это делали выше, только числом снизу является двоичная единица. Например:
1 (переносим в следующую колонку)
1011 0011 (двоичное число)
0000 0001 (1 в двоичной системе)
---------
1011 0100
Числа signed и метод «two’s complement»
В примерах, приведенных выше, мы работаем только с целыми числами unsigned, которые могут быть только положительными. Сейчас же мы рассмотрим то, как работать с числами signed, которые могут быть как положительными, так и отрицательными.
С целыми числами signed используется метод «two’s complement». Он означает, что самый левый (самый главный) бит используется в качестве знакового бита. Если значением знакового бита является 0, то число положительное, если 1 — число отрицательное.
Положительные числа signed хранятся так же, как и положительные числа unsigned (с 0 в качестве знакового бита). А вот отрицательные числа signed хранятся в виде обратных положительных чисел + 1. Например, выполним конвертацию -5
из десятичной системы счисления в двоичную, используя метод «two’s complement»:
Сначала выясняем бинарное представление 5: 0000 0101
Затем инвертируем все биты (конвертируем в противоположные): 1111 1010
Затем добавляем к числу единицу: 1111 1011
Конвертация -76
из десятичной системы счисления в двоичную:
Представление положительного 76: 0100 1100
Инвертируем все биты: 1011 0011
Добавляем к числу единицу: 1011 0100
Почему мы добавляем единицу? Рассмотрим это на примере 0 (нуля). Если противоположностью отрицательного числа является его положительная форма, то 0 имеет два представления: 0000 0000 (положительный ноль) и 1111 1111 (отрицательный ноль). При добавлении единицы, в 1111 1111 произойдет переполнение, и значение изменится на 0000 0000. Добавление единицы позволяет избежать наличия двух представлений нуля и упрощает внутреннюю логику, необходимую для выполнения арифметических вычислений с отрицательными числами.
Перед тем, как конвертировать двоичное число (используя метод «two’s complement») обратно в десятичную систему счисления, нужно сначала посмотреть на знаковый бит. Если это 0, то смело используйте способы, приведенные выше, для целых чисел unsigned. Если же знаковым битом является 1, то тогда нужно инвертировать все биты, затем добавить единицу, затем конвертировать в десятичную систему, и уже после этого менять знак десятичного числа на отрицательный (потому что знаковый бит изначально был отрицательным).
Например, выполним конвертацию двоичного 1001 1110 (используя метод «two’s complement») в десятичную систему счисления:
Имеем: 1001 1110
Инвертируем биты: 0110 0001
Добавляем единицу: 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, и, если бы это было стандартное двоичное число unsigned, то вы были бы правы. Однако, если здесь используется метод «two’s complement», то результат будет другой: -76. Также значение еще может быть другое, если оно закодировано каким-то третьим способом.
Так как же язык C++ понимает в какое число конвертировать 1011 0100: в 180 или в -76?
Еще на уроке №28 мы говорили: «Когда вы указываете тип данных переменной, компилятор и процессор заботятся о деталях конвертации этого значения в соответствующую последовательность бит определенного типа данных. Когда вы просите ваше значение обратно, то оно «восстанавливается» из соответствующей последовательности бит в памяти».
Тип переменной используется для конвертации бинарного представления числа обратно в ожидаемую форму. Поэтому, если вы указали целочисленный тип данных unsigned, то компилятор знает, что 1011 0100 — это стандартное двоичное число, а его представление в десятичной системе счисления — 180. Если же типом переменной является целочисленный тип signed, то компилятор знает, что 1011 0100 закодирован с помощью метода «two’s complement» и его представлением в десятичной системе счисления является число -76.
Тест
Задание №1
Конвертируйте двоичное число 0100 1101 в десятичную систему счисления.
Задание №2
Конвертируйте десятичное число 93 в 8-битное двоичное число unsigned.
Задание №3
Конвертируйте десятичное число -93 в 8-битное двоичное число signed (используя метод «two’s complement»).
Задание №4
Конвертируйте двоичное число 1010 0010 в десятичное unsigned.
Задание №5
Конвертируйте двоичное число 1010 0010 в десятичное signed (используя метод «two’s complement»).
Задание №6
Напишите программу, которая просит пользователя ввести число от 0 до 255. Выведите его как 8-битное двоичное число (в парах по 4 цифры). Не используйте побитовые операторы.
Подсказки:
Воспользуйтесь способом конвертации №2. Предполагается, что наименьшим числом для сравнения является 128.
Напишите функцию для проверки входных чисел: являются ли они больше чисел, умноженных на 2 (т.е. чисел 1, 2, 4, 8, 16, 32, 64 и 128). Если это так, то выводится 1, если нет — выводится 0.
Ответы
Ответ №1
Двоичный символ | 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
Используя способ №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.
Ответ: 0101 1101.
Используя способ №2:
Наибольшее число, умноженное на 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 — это 0101 1101
Поэтому инвертируем биты: 1010 0010
И добавляем единицу: 1010 0011
Ответ: 1010 0011.
Ответ №4
Работая справа налево:
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
Инвертируем биты: 0101 1101
Добавляем единицу: 0101 1110
Конвертируем в десятичную систему счисления: 16 + 64 + 8 + 4 + 2 = 94
Так как здесь используется метод «two’s complement», а знаковый бит является отрицательным, то результат: -94
Ответ: -94.
Ответ №6
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <iostream> // x - это число, которое мы будем тестировать. // pow - это множитель 2 (например, 128, 64, 32 и т.д.) int printandDecrementBit(int x, int pow) { // Проверяем, является ли x больше определенного числа, умноженного на 2 и выводим бит if (x >= pow) std::cout << "1"; else std::cout << "0"; // Если x больше, чем число, умноженное на 2, то вычитаем его из значения if (x >= pow) return x - pow; else 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); return 0; } |
Дополнил код для работы с не валидными данными через метку. Теперь когда пользователь введет неккоректное значение — процес не завершиться, а перескочет на метку label и попросит заново ввести значение.
Друзья, вопрос: как Вы считаете насколько правильно использовать метку goto в данном случае?
Имеет ли право на жизнь такой вариант?
Хм, а я решил тем, что давалось в курсе ранее и у меня вот вышло как-то так. Работает вполне отлично
Задача состоит не в использовании синтаксического сахара, а поиск наиболее оптимального решения на основании пройденного материала и рекомендаций автора.
Данный фрагмент кода использует только то, что уже было объяснено в предыдущих уроках:
1. достаточное объявление и реализация функций в одном файле (многофайловые программы ещё не рассматривались, поэтому вынесение реализации и объявления функции в отдельный заголовочный файл и файл реализации нецелесообразно)
2. оператор % в данном случае более, чем достаточен вместо поиска остатка от деления напрямую
3. тип bool при выводе интерпретируется как целое число (true = 1, false = 0). Т.к. у нас по условию стоит диапазон чисел от 0 до 255, то можно безопасно использовать результат деления целых чисел, работать будет точно так же.
4. функция — это маленькая задача (каюсь, в первый раз я сделал одну функцию, которая выполняла всю работу по выводу двоичного представления десятичного числа от 0 до 255. Потом посмотрел у автора и согласился с его решение разбивки программы на функции)
6. циклов ещё не было, поэтому должны использоваться только условия и функции.
P.S. У автора хороший подход к подаче материала.
Совет студентам: представьте что вы играете в Героев. Вам известно только то, что уже было пройдено. Остальное — Terra Incognita. Задания следует выполнять тем инструментарием, который представлен в уроках с 1 по текущий. В этом и заключается обучение — попробовать весь известный на данный момент инструментарий языка программирования.
Но всё же хорошо знать что же получишь после апгрейда… Побитовые операции будут в следующем уроке, но на сколько сократится код при их применении!
использования sizeof(x) не целесообразно, вить так мы узнаём размер переменной (то есть 4байта, или у тебя просто 4).
лучше написать 8(как написано в задании)
нужна критика. пысы:учу с++ неделю по этому многого ещё не знаю.
либо же ещё проще
Мой вариант кода. Есть позаимствования у ребят ниже, но с моими доработками: ввел защиту от некорректного ввода, а также мне не понравилось как сделали ветвление и вывод ответа, поэтому ввел третью контрольную переменную, которая отвечает за "счетчик" вывода битов. Теперь вывод ответа корректен и во всем диапазоне функционирует исправно.
Выполнил задание иначе, т.к решил перебор степеней двойки слишком простым заданием. Использовал рекурсию. Также использовал short int вместо int — какая никакая оптимизация)
Из минусов отмечу дополнительную ветку ветвления для случая с введённым нулём, т.к. мой способ циклится на нём. Также нарушил правило "единой задачи" для функции, но собственно тут это не страшно.
почему не работает с 4 и 2
Код явно не лучший из представленных, за то целиком и полностью свой. Уже заприметил человека, что сделал похожим образом, но это, вероятно, тот случай, когда одинаково мыслят не гении)))
Думаю если так задачу решать, отдельная функция не особо нужна
Сырой код. 0 — не показывает, 1 — не показывает. Для 255 и 256 показывает одинаковый результат.
Спасибо автору и спасибо всем за игры разума.
Решил сделать без циклов
Какой прекрасный код
Код у Вас, Сергей, красивый и элегантный. Но "глотает" нули, если они в конце.
Мудрено конечно, но работает. Результат выдает правильный, но мне кажется, что я неправильно сделал задание.
Попробовал чуть сократить
Всегда с ужасом смотрел на все эти двоичные числа, даже не пытался их понять. Но после этого урока я все понял и даже стал конвертировать числа в уме. Я в диком положительном шоке!!! Огромное спасибо Юрию!!!
Моя программа:
main.cpp:
сделал код на перевод между двумя любыми системами
Вот такой вот каменный цветок у меня получился:
У меня так получилось. Проверил — для чисел от 0 до 256 работает нормально. Впрочем поменяв значение переменной d можно конвертировать числа в любом диапазоне.
Буду рад критике
Вот мой код. Как мне кажется, самый простой и короткий. Прошу указать на ошибки, если они есть, буду рада критике и коррективам.
Получилось как то так, возможность для масштабирования полная (сразу переделал для чисел, занимающих 2 полных байта):
Здравствуйте! Спасибо за уроки) Наверное лучшее, что мне удалось найти для первоначального знакомства с С++.
При написании руководствовался исключительно пройденным материалам до этого урока (хотя ветвление if только вскользь упоминалось, думаю для написания этого задания без него не обойтись).
Буду очень благодарен конструктивной критике.
Использую компилятор G++, решил проверить код с дополнительными флагами (-std=c++17 -Wall -Wextra -Werror -Wpedantic -pedantic-errors) и параллельно сократить его. Исправил все появившиеся предупреждения, компиляция и линкинг прошли успешно, программа работает. Однако при пользовательском вводе числа больше 65535 (максимальное значение типа данных unsigned short) возникает ошибка, консоль выводит сообщение "Ошибка сегментирования (стек памяти сброшен на диск)" (это в Linux, в других ОС аналогично).
Что я делаю не правильно, где может быть ошибка в коде?
Решил переписав функцию ввода числа
Интересно для общего ознакомления. На практике лучше использовать калькулятор программиста и не тратить время. Также сведя ошибки к минимуму.
Сделал так, чтобы пользователь сам выбирал длину числа =)
Я не понимаю, почему в предложенном ответе такой длинный вариант, потому что, мне кажется у меня проще