Простые типы данных. Операции над простыми данными

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

Попытаюсь заполнить этот пробел. Основной упор будет сделан на форматы данных использующиеся в процессорах Intel (AMD), т.е. в IBM совместимых компьютерах. При этом предполагается, что читатель знаком, по крайней мере, с системами счисления. Десятичной (ну это все знают), двоичной и шестнадцатиричной.

Машинные форматы данных.

Бит.

Бит - это наименьшая представимая порция информаци. Столько ее содержится в ответе "ДА" или "НЕТ". Бит может иметь только два значения: 0 или 1. Бит не может быть знаковым или беззнаковым. Однако можно выполнить любые вычисления оперируя только битами. Так работают последовательные процессоры. Так выполняются вычисления с большой точностью. На обработке битов основаны методы вычислений, получившие обобщенное название "цифра за цифрой". И, разумеется, из битов состоят все остальные типы данных.

Операции с битами. С битами можно выполнять не так уж и много операций:

· Установка (SET) и сброс (RESET). Фактически это операции присваивания. Установка присваивает биту значение 1, сброс - значение 0.

· Проверка (TEST). По другому, чтение бита, или получение значения. Выделена мной потому, что биты часто используются для представления условий в программе. Отсюда и такое странное, на первый взгляд, название операции.

· Операция НЕ (NOT). Меняет значение бита на противоположное, т.е.

NOT =
NOT =

· Операция И (AND). Выполняется над двумя битами.

AND =
AND =
AND =
AND =

· Операция ИЛИ (OR). Выполняется над двумя битами.

OR =
OR =
OR =
OR =

· Операция ИСКЛЮЧАЮЩЕЕ ИЛИ (XOR). Выполняется над двумя битами. Эту операцию часто используют для контроля четности и вычисления контрольных сумм.

XOR =
XOR =
XOR =
XOR =

· Операция ВЗЯТИЕ ПО МОДУЛЮ 2 (M2). Выполняется над двумя битами. Является инверсией операции ИСКЛЮЧАЮЩЕЕ ИЛИ. Эту операцию так же часто используют для контроля четности и вычисления контрольных сумм. Обратите внимание, результат операции ИСКЛЮЧАЮЩЕЕ ИЛИ равен единице, когда биты имеют разные значения. Результат операции ВЗЯТИЕ ПО МОДУЛЮ 2 равен единице, когда биты имеют одинаковые значения. Это очень важное отличие.

M2 =
M2 =
M2 =
M2 =

Байт.

Байт, собственно это набор бит, обычно это восемь бит. Однако может быть и 5 бит, и 6, и 7, и даже 9. Не надо этому удивляться. Изначально, термин байт возник как название набора бит, достаточных для представления одного символа, или кванта информации, обрабатываемой процессором за один такт. В телеграфии обмен идет пяти-битными символами, в этом может убедиться каждый посмотрев на перфоленту телеграфного аппарата (телетайпа). Следовательно этот байт состоит из 5 бит. В оперативной памяти компьютеров часто используется хранение информации в виде, позволяющем обнаруживать и исправлять ошибки. Для этого просто добавляют вспомогательные разряды. Например, одного разряда достаточно для обнаружения одиночной ошибки. Получаем, что байт в процессоре, состоящий из восьми бит, в памяти состоит из девяти. При записи информации на жесткие диски добавляют не один, а несколько бит. Это делается для восстановления информации при возможных сбоях. Так что не следует считать, что байт всегда 8 бит. Хотя, для наших целей, мы примем размер байта именно 8 бит. Нас ведь интересуют не способы контроля и восстановления информации.

Формат представления байта показан на рисунке 1. Старший значащий бит еще называют MSB, а младший LSB. Рисунок знакового байта может вызвать вопросы. Дело в том, что старшим значащим битом, в этом случае, все равно является самый левый бит, а у нас показан второй слева. Это с точки зрения процессора, с точки зрения программиста, в знаковом байте, для представления числа можно использовать не 8, а только 7 разрядов. Поэтому, с точки зрения программиста, старшим значащим битом является именно показаный. Восьмой же бит является знаковым, т.е. индицирует знак хранимого в байте числа. Единичное значение этого разряда, обычно, соответствует отрицательному числу, а нулевое - положительному.

Для представления отрицательных чисел может использоваться два метода. Первый - знаковый бит используется для индикации знака, остальные рязряды для представления числа в прямом коде. Второй - знаковый бит используется для индикации знака, остальные разряды для представления числа в дополнительном коде. На первый взгляд, различие не большое. Однако давайте рассмотрим два числа, 00000000 и 10000000. Если отрицательные числа хранятся в прямом коде, то оба эти числа являются 0. Но! Первое это положительный 0, второе отрицательный! Эту проблему вы наверно уже встречали, по крайней мере в старых книгах по вычислительной технике. Но это не единственная проблема прямого представления отрицательных чисел. Попробуем сложить плюс 1 и минус 1, 00000001 + 10000001. Простое сложение даст 10000010, а это отнюдь не 0. Значит нужна коррекция результата, а это удорожает оборудование. Поэтому в современных процессорах, за редким исключением, отрицательные числа представлены в дополнительном коде. В дополнительном коде отрицательное число представляется как число, которое надо прибавить к хранящемуся для получения в результате 0. Звучит туманно? На самом деле все просто, возьмем число 11111111, для получения 0, к этому числу надо прибавить 1. При этом произойдет перенос из старшего разряда и получится 00000000. Следовательно, 11111111 это -1. Вообще смена знака чисела в дополнительном коде выполняется так: исходное число инвертируется и к результату прибавляется единица. Нет никаких сложностей и со сложением, 11111111 + 00000001 = 00000000. Не требуется усложнение оборудования.

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

Теперь о диапазоне значений чисел, хранящихся в байтах. Беззнаковый байт позволяет хранить числа от 0 до 255. Байт со знаком позволяет хранить числа от -128 до +127.

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

· Операция ИНВЕРСИЯ БАЙТА (NOT). По другому это называется операция НЕ. Над каждым битом байта выполняется операция НЕ. Следует заметить, что эта операция не является операцией смены знака числа, хотя и меняет значение знакового разряда. Знак числа (знакового байта) меняется операцией двоичного дополнения. Если вы не можете уловить разницу, прочтите еще раз повнимательней параграф о форматах представления отрицательных чисел.

NOT 01100100 = 10011011

· Операция ДВОИЧНОЕ ДОПОЛНЕНИЕ (NEG) меняет знак числа. Делается это просто: выполняется инверсия байта, а затем к байту прибавляется единица. Еще раз обращаю Ваше внимание на различие этих двух операций, дополнения и инверсии. Как показывает практика, это является камнем преткновения для многих. Инверсия имеет смысл для беззнаковых байтов, а дополнение меняет знак числа хранящегося в знаковом байте.

NEG 01100100 = 10011100

· Операция ПОРАЗРЯДНОГО ЛОГИЧЕСКОГО И (AND) выполняется над двумя байтами. Результатом операции является байт, в котором каждый бит является результатом операции И соответствующих бит исходных байтов. Операция имеет смысл для беззнаковых байтов. Точнее, процессору все равно, знаковый или беззнаковый байт участвует в операции, но Вам, как программистам, это явно не все равно.

01100100 AND 10011101 = 00000100

· Операция ПОРАЗРЯДНОГО ЛОГИЧЕСКОГО ИЛИ (OR) выполняется над двумя байтами. Аналогична операции И, за исключением того, что над битами выполняется операция ИЛИ. Имеет смысл для беззнаковых байтов.

01100100 OR 10011101 = 11111101

· Операция ПОРАЗРЯДНОГО ИСКЛЮЧАЮЩЕГО ИЛИ (XOR) выполняется над двумя байтами. Аналогична операции И, за исключением того, что над битами выполняется операция ИСКЛЮЧАЮЩЕГО ИЛИ. Имеет смысл для беззнаковых байтов.

01100100 XOR 10011101 = 11111001

· Операция ПОРАЗРЯДНОГО ВЗЯТИЯ ПО МОДУЛЮ 2 (M2) выполняется над двумя байтами. Аналогична операции И, за исключением того, что над битами выполняется операция ВЗЯТИЯ ПО МОДУЛЮ 2. Имеет смысл для беззнаковых байтов.

01100100 XOR 10011101 = 00000110

· Операция СЛОЖЕНИЯ (ADD) выполняется над двумя байтами. Сложение выполняется точно так же, как мы складываем десятичные числа, начиная с младшего разряда и заканчивая старшим. Точно так же выполняются и междуразрядные переносы. Правила сложения бит следующие:

+ =
+ =
+ =
+ =

· Однако, в отличии от привычной нам всем математики, где количество цифр в получившейся сумме нам, в общем и целом, безразлично, при сложении байтов дело обстоит не так просто. Речь идет о ситуации, когда число разрядов в результате операции превышает размер байта. В нашем случае, это более 8 бит для байта без знака, и более 7 бит для байта со знаком. Тут то и появляются термины перенос и переполнение. Для индикации этих ситуаций, в слове состояния процессора, обычно предусматриваются специальные биты. Бит индицирующий перенос, обычно, обозначается С или CF, а переполнение О или OF. Попробуем разобраться поподробнее. Рассмотрим пример сложения двух беззнаковых байт:

· Как видно, результат занимает 9 бит. Этот девятый бит, не помещающийся в байте, и называется переносом. Считать ли данную ситуацию ошибочной? И да и нет. Если результат должен помещаться в байт, то это безусловно ошибка. Если же это часть операции сложения многобайтных чисел, то ошибки нет. Перенос, как результат сложения, имеет точно такое же значение, что и поразрядные переносы при выполнении сложения. Он не является признаком ошибки и используется при организации сложения многобайтных чисел. Итак, термин перенос имеет смысл для беззнаковых байт. Он не является ошибкой при сложении двух байт, являющихся частью многобайтных чисел. В противном случае он сигнализирует об ошибке.

· Теперь разберемся с переполнением. Рассмотрим пример сложения знаковых байт.

· На первый взгляд все в порядке. В результате 8 разрядов, следовательно он помещается в байт. Но вспомним, мы складываем знаковые байты. У нас же, при сложении двух положительных чисел, получился отрицательный результат! Переполнение возникает, когда при сложении двух знаковых байт, имеющих одинаковый знак, результат сложения имеет другой знак. Иногда переполнение можно игнорировать, но в большинстве случаев это свидетельствует об ошибке. По научному, условие формирования переполнения звучит так: количество переносов в знаковый разряд не равно количеству переносов из знакового разряда. Итак, термин переполнение имеет смысл для знаковых байт. В большинстве случаев это свидетельствует об ошибке.

· Операция ВЫЧИТАНИЯ (SUB) выполняется над двумя байтами. Как и операция сложения, операция вычитания очень похожа на вычитание обычных десятичных чисел. Точно так же, как и при обычном вычитании, формируются междуразрядные заемы. Как и в операции сложения, могут быть сформированы признаки заема, как результата операции, и переполнения. Научная формулировка условия формирования переполнения звучит точно так же, как и для сложения. А вот как сказать это более человеческим языком, я оставляю Вам в качестве упражнения.

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

· Операция ИНКРЕМЕНТА (INC). Просто увеличение байта на 1. Это настолько частая операция, что во всех современных процессорах для нее имеется специальная команда.

· Операция ДЕКРЕМЕНТА (DEC). Просто уменьшение байта на 1. Это настолько частая операция, что во всех современных процессорах для нее имеется специальная команда.

· Операция УМНОЖЕНИЯ (MUL) выполняется над двумя байтами. Аналогична операции умножения десятичных чисел. Правила умножения бит следующие:

х =
х =
х =
х =

· Умножение, как и деление впрочем, имеет несколько особенностей. Первая, это то, что умножение (и деление) бывает знаковым и беззнаковым. Как Вы уже наверно догадались, при выполнении знакового умножения знаковые биты перемножаемых байт не участвуют в операции собственно умножения, а определяют знак результата. В операции беззнакового умножения участвуют, на равных, все биты перемножаемых байт. Второе отличие - результат умножения имеет в два раза больше бит, чем сомножители. То есть при умножении двух восьмибитных байт результат будет иметь 16 бит. Приведу пример:

· Самый левый бит, пятнадцатый, в нашем случае равен 0. Может возникнуть вопрос, зачем тогда его показывать? Вспомним сложение. Этот бит может запросто оказаться не нулевым в результате переноса. Итак, в результате умножения двух однобайтных сомножителей мы получили два байта, которые можно назвать словом или полусловом. Но эти термины мы обсудим позже. А сейчас попытаемся разобраться с переполнением и переносом при умножении.

· При обсуждении переноса и переполнения при умножении я должен сделать одну очень важную оговорку. Речь будет идти о процессорах INTEL 80х86. Дело в том, что установка признаков результата, и даже количество бит в результате (8 или 16), очень сильно зависит от конкретного процессора. В процессорах Intel 80х86, при умножении двух байт, будет получаться 16-ти разрядный результат, который состоит из двух половин, младшей и старшей. Для нашего примера младшая половина 01110010, а старшая 00110110. Для беззнакового умножения признаки CF и OF будут устанавливаться, если старшая половина результата не нулевая. Для знакового умножения признаки CF и OF будут установлены, если старшая половина результата не равна 00000000 или 11111111. Почему такое странное условие для знакового умножения? Наберитесь терпения, это будет описано в следующем разделе. Считать ли установку признаков переноса и переполнения ошибкой? Да, если результат должен помещаться в байте. В противном случае это просто говорит о том, что старшая половина результата содержит значащие разряды.

· Операция ДЕЛЕНИЯ (DIV) выполняется над двумя байтами. Как и все ранее рассмотренные операции, деление байт аналогично привычному делению десятичных чисел. Основное отличие состоит в том, что делимое должно занимать два байта, а не один. Что же делать, если нужно разделить именно байт? Если делимое беззнаковый байт, то его размещают на месте младшей половины делимого, а в старшую заносят 0. Если делимое знаковый байт, то выполняют расширение знака. Для этого в старшую половину делимого заносят 00000000, если делимое положительно, или 11111111, если делимое отрицательно. То есть, устанавливают все биты старшей половины делимого равными значению знакового бита младшей половины делимого. Вспомните условие формирования признаков CF и OF при выполнении знакового умножения, теперь оно должно быть понятным для Вас. Как и умножение, деление может быть знаковым и беззнаковым. Приведу пример:

Обратите внимание, что результат деления занимает 9 бит. Правда девятый бит нулевой, и это важно. При реализации операции деления разработчики процессоров Intel 80х86 отошли от привычной схемы формирования признаков результата. После выполнения деления ни призак CF, ни признак OF не имеют смысла и могут принимать любое значение. Если результат деления двух байт не помещается в байт, то есть, если в результате есть ненулевые биты, кроме младших восьми, то выполняется прерывание. Прерывания будут описаны в отдельной статье, сейчас Вам просто надо запомнить, что после операции деления признаки результата не определены, что вызывает дополнительные сложности.

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

· Операции ЛОГИЧЕСКОГО СДВИГА ВПРАВО (SHR) и ЛОГИЧЕСКОГО СДВИГА ВЛЕВО (SHL). Имеют смысл для беззнаковых байт. При этом сдвиг влево, фактически, выполняет умножение на 2, а сдвиг вправо, деление на 2 (с отбрасыванием остатка).

Так из байта 01101001 (десятичное 105), в результате логического сдвига вправо, получится байт 00110100 (десятичное 52), а в результате сдвига влево 11010010 (десятичное 210). Обратите внимание на необычное использование признака CF, он содержит значение последнего выдвигаемого бита. Именно последнего, поскольку сдвиг может выполняться не на один бит, или по другому разряд, а сразу на несколько. Кстати, признаки результата, в частности CF, по другому называются флажками.

· Операции АРИФМЕТИЧЕСКОГО СДВИГА ВПРАВО (SAR) и АРИФМЕТИЧЕСКОГО СДВИГА ВЛЕВО (SAL). Имеют смысл для знаковых байт. Аналогичны операциям логических сдвигов, за исключением сохранения знака байта при сдвиге вправо. Логический и арифметический сдвиги влево полностью идентичны.

Обратите внимание, что при сдвиге вправо знаковый бит дублируется. Это позволяет сохранять знак байта. Как в операциях логических сдвигов флажек CF принимает значение последнего выдвинутого байта.

· Операции ЦИКЛИЧЕСКОГО СДВИГА ВПРАВО (ROR), ЦИКЛИЧЕСКОГО СДВИГА ВЛЕВО (ROL), ЦИКЛИЧЕСКОГО СДВИГА ВПРАВО ЧЕРЕЗ ПЕРЕНОС (RCR), ЦИКЛИЧЕСКОГО СДВИГА ВЛЕВО ЧЕРЕЗ ПЕРЕНОС (RCL). Имеют смысл для беззнаковых байт.

Особенно коментировать эти операции не буду. Обращаю Ваше внимание только на использование флажка CF в операциях циклических сдвигов через перенос.

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

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