Двоичная, шестнадцатеричная и восьмеричная системы счисления

Практический курс по C/C++: https://stepik.org/course/193691

Прежде чем дальше погружаться в язык Си разберемся, как в вычислительной технике происходит хранение целых чисел. А начнем с привычной нам десятичной системы счисления.

Если вспомнить школу, начальные классы, то любое число в десятичной записи можно разложить на единицы, десятки, сотни и так далее:

Причем, возрастание степени числа 10 идет справа-налево, а не слева-направо, как мы привыкли писать. Это обусловлено тем, что форма записи арабских чисел, принятая большинством человечества, пришла к нам с востока. А на востоке, как вы знаете, во многих странах пишут именно справа-налево. Поэтому мы видим справа единицы, а затем, все более и более значимые цифры числа при движении влево.

Приведенная система записи числа называется десятичной, так как здесь каждый следующий разряд числа в 10 раз больше предыдущего. Сами же разряды обозначаются арабскими цифрами:

0 1 2 3 4 5 6 7 8 9

Обратите внимание, цифры – это лишь обозначение (изображение) чисел. А вот числа – это уже характеристика количества чего-либо. Например, цифра 2 – это обозначение числа 2. А число 2 – это, например, два камушка. И мы понимаем у себя в голове, сколько это два камушка. То же число два можно было бы обозначить, например, римской цифрой II. Суть числа от этого не меняется. Оно по-прежнему характеризует два количества: два камушка или две чашки или две машины и так далее. Поэтому само число – это нечто общее и объективное, а цифры – это всего лишь изображение чисел.

Двоичная система счисления

Итак, с десятичной записью, я думаю, все понятно. Давайте теперь перейдем на уровень бит, из которых состоят ячейки памяти:

Будем полагать, что каждая ячейка состоит из восьми бит и образует один байт информации. Именно в таком виде представляется вся информация в вычислительной технике. Здесь каждый бит может иметь только два состояния: 0 – бит выключен; 1 – бит включен, то есть, общее количество цифр равно двум:

0 1

Спрашивается, как используя всего две цифры описывать множество различных чисел? Для ответа на этот вопрос давайте представим, что набор из восьми бит организован как счетчик, например, электроэнергии или воды или газа. То есть, там стоят как бы колесики (шестеренки) и они отсчитывают количество чего-либо:

Пусть изначально все биты равны нулю. Так мы будем кодировать число 0:

0 0 0 0 0 0 0 0 = 0

Затем, самая правая шестеренка под номером 0 прокручивается и в младшем бите появляется цифра 1. Это следующая комбинация. Запишем ее десятичным числом 1:

0 0 0 0 0 0 0 1 = 1

Далее, шестеренка под номером 0 снова прокручивается. По идее мы привыкли видеть цифру 2 после цифры 1, но у нас всего два обозначения: 0 и 1. Значит, после 1 снова увидим 0, но зато следующая шестеренка прокрутится и на ней мы увидим значение 1:

0 0 0 0 0 0 1 0 = 2

То есть все работает, как в обычных счетчиках, только в нашем распоряжении не десять цифр, а всего две. Продолжая аналогичным образом, получаем последовательности:

0 0 0 0 0 0 1 1 = 3
0 0 0 0 0 1 0 0 = 4
0 0 0 0 0 1 0 1 = 5
0 0 0 0 0 1 1 0 = 6
0 0 0 0 0 1 1 1 = 7

1 1 1 1 1 1 1 1 = 255

Всего 256 вариантов: от 0 до 255. То есть, в восьми битах можно закодировать любое целое неотрицательное число в диапазоне [0; 255]. А представление чисел в виде последовательности нулей и единиц получило название двоичной формой записи числа. Что соответствует двоичной системе счисления.

Показанный алгоритм формирования двоичных комбинаций и их нумерация десятичными числами от 0 до 255 имеет достаточно простое математическое выражение. Предположим, что перед нами записано некоторое число в двоичной форме:

Спрашивается, чему будет равно его десятичное представление? Все очень просто. Так как мы имеем двоичную систему счисления, то каждый разряд числа – это два в соответствующей степени, а именно:

Или так. Если все биты числа равны 1, то получаем десятичное число:

А что будет, если мы захотим занести в эти восемь бит следующее число:

256 = 255 + 1

Это эквивалентно прокручиванию самой правой шестеренки на одну позицию и тогда согласно нашему алгоритму, все ячейки должны обнулиться, а следующий старший разряд установиться в единицу:

Но у нас всего восемь бит, а не девять. Поэтому бит со значением 1 просто пропадет, а в оставшихся восьми битах остаются нули. Следовательно, при попытке записать число 256 в один байт, мы получим переполнение и, как результат, значение 0. И, наоборот, если из нуля вычесть один, то получим значение 255:

0 – 1 = 255

Все эти эффекты переполнения следует учитывать, при работе с целыми числами.

Представление отрицательных целых чисел

Хорошо, мы с вами только что увидели, как можно кодировать целые положительные числа в восьми битах. А что если нам необходимо представлять и положительные и отрицательные значения? Как это можно было бы сделать? На самом деле подход очень простой и вытекает из только что установленного факта: если из нуля вычесть один, то получим 255. Но ничто нам не мешает вместо 255 кодировать значение -1:

0 – 1 = -1

Соответственно, если крайнюю правую шестеренку провернуть назад еще один раз, то получим уже значение -2:

Поворачивая еще раз назад, будем получать следующие отрицательные числа -3, -4 и так далее:

1 1 1 1 1 1 1 1 = -1
1 1 1 1 1 1 1 0 = -2
1 1 1 1 1 1 0 1 = -3
1 1 1 1 1 1 0 0 = -4

Спрашивается, на каком наименьшем отрицательном следует остановиться? В практике программирования поступают следующим образом. Самый старший бит числа (в данном случае 8-й бит) отводят под знак. Если он равен 1 – число отрицательное, если 0 – число положительное. Следовательно, по нашей схеме формирования отрицательных чисел мы дойдем до:

1 0 0 0 0 0 0 0 = -128

Соответственно, максимальное положительное число равно:

0 1 1 1 1 1 1 1 = 127

Отсюда получаем диапазон для представления отрицательных и положительных чисел в одном байте от -128 до 127.

Конечно, это совсем небольшой диапазон значений и для большинства практических задач его недостаточно. Как быть? Опять же все просто. Можно под хранение целых чисел отвести не один, а два подряд идущих байта:

Тогда диапазон в целых числах составит от 0 до , а для чисел со знаком [-32768; 32767]:

от: 10000000000000000 = -32768
до: 01111111111111111 = 32767

Если и этого диапазона значений будет недостаточно, то можно взять 4 байта, затем, 8 байтов. Обычно дальше не идут и 8 байтов почти для всех задач программирования оказывается достаточно. В качестве самостоятельного задания рассчитайте диапазоны значений для 4-байтных и 8-байтных целых чисел.

Шестнадцатеричная система счисления

Давайте снова вернемся к одному байту, состоящего из восьми бит:

Можно заметить, что каждая его половинка состоит из четырех бит. И мы легко можем рассчитать число возможных вариантов, которые кодируются этими четырьмя битами:

Их всего шестнадцать. А раз так, то почему бы каждую такую половинку не представлять одной цифрой. Только это будет цифра не десятичного, а шестнадцатеричного числа, то есть, числа, записанного в шестнадцатеричной системе счисления. Давайте это сделаем! Первое, что нам понадобится – это 16 различных цифр. Первые десять логично взять из уже знакомой нам десятичной системы, а именно в виде следующих арабских обозначений:

0 1 2 3 4 5 6 7 8 9

Не хватает еще шести. Недолго думая, решили в их качестве воспользоваться первыми шестью буквами латинского алфавита:

A, B, C, D, E, F

В результате, цифра A обозначает число 10, B – число 11 и так далее до F – число 15. С помощью этих шестнадцати цифр мы имеем возможность описать любую комбинацию из четырех бит:

0 0 0 0 = 0

0 1 0 0 = 4

1 0 0 0 = 8

1 1 0 0 = C

0 0 0 1 = 1

0 1 0 1 = 5

1 0 0 1 = 9

1 1 0 1 = D

0 0 1 0 = 2

0 1 1 0 = 6

1 0 1 0 = A

1 1 1 0 = E

0 0 1 1 = 3

0 1 1 1 = 7

1 0 1 1 = B

1 1 1 1 = F

Опытные программисты эту таблицу знают наизусть. Поэтому, когда они видят какое-либо шестнадцатеричное число, например, 1A, то сразу понимают его двоичное представление на уровне байта:

1A = 00011010

Видите, как это просто и удобно. Мы легко можем переходить из двоичной записи в шестнадцатеричную:

10110001 = B1

И, наоборот, из шестнадцатеричной в двоичную. Именно в этом главное достоинство шестнадцатеричной формы записи для представления данных в вычислительной технике.

Конечно, при необходимости, любое шестнадцатеричное число можно перевести в десятичный вид. Делается это очень просто. Пусть дано некоторое значение, например:

5FC

Представим его вначале как сумму степеней 16-ти:

А, затем, вместо F подставим эквивалент из десятичной системы 15, а вместо C – значение 12, получим выражение:

Восьмеричная система счисления

В практике программирования можно встретить еще одну систему счисления – восьмеричную. На раннем этапе развития вычислительной техники она была довольно распространена. Однако сейчас практически полностью вытеснена шестнадцатеричной. Тем не менее, язык Си позволяет записывать числа в восьмеричной системе. Кроме того в ОС Unix такая система счисления используется до сих пор для настройки прав доступа и некоторых других задачах, где требуется представление группы из трех бит.

Здесь я лишь формально рассмотрю восьмеричную форму записи чисел, исключительно для полноты картины. В целом, она подобна десятичной и шестнадцатеричной записи. Цифрами восьмеричной системы выступают обозначения:

0 1 2 3 4 5 6 7

А разряды расписываются через произведения восьмерок с разными степенями. Например, число:

В целом, это такая же система счисления, как и многие другие, только основание у нее восемь.

Конечно, как бы мы ни записывали числа в программе на языке Си, какую бы форму записи не использовали, на уровне машинных кодов, для процессора, они все представляются в виде последовательностей нулей и единиц, то есть, в битовом виде – двоичной форме. Любой другой вид чисел служит лишь для нашего удобства и не более того. Если вы привыкли работать с десятичными числами, то и работайте с ними. На скорость работы программы это никак не повлияет. А в другие системы счисления следует переходить только при необходимости, когда они действительно дают большее удобство по сравнению с десятичной.

И в заключение еще одно замечание. Мы с вами на этом занятии рассматривали представление исключительно целых чисел на уровне бит и в разных системах счисления. Вещественные (дробные) числа через те же биты кодируются в другом формате. И это отдельная тема, которую я не стану затрагивать. Скажу лишь, что любая информация представленная в виде целых чисел, как правило, обрабатывается быстрее, чем на уровне вещественных чисел. Поэтому без особой необходимости, в программе не следует использовать вещественные числовые значения, если все можно сделать с использованием целых.

Практический курс по C/C++: https://stepik.org/course/193691

Видео по теме