6922 subscribers

О представлении чисел в ЭВМ. "Приборные" форматы целых чисел

<100 full reads

ЭВМ это не вещь "в себе", она обрабатывает информацию из окружающего мира и для окружающего. Просто эта информация представлена в виде чисел. Что именно скрывается за этими числами определяет человек - мы с вами.

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

Кстати, с точки зрения ЭВМ человек наверняка тоже выглядит своеобразным "прибором" 😊

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

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

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

Зачем нужны "приборные" форматы представления чисел?

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

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

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

Преобразование числа из одной системы счисления в другую требует использования операций умножения и деления, которые есть не в каждом процессоре. А если и есть, то выполняются не всегда быстро, если в процессоре нет аппаратного перемножителя/делителя. Да, в этом мире существуют не только процессоры Intel x86.

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

Поэтому может оказаться гораздо проще выполнять простые арифметические операции непосредственно с числами представленными в "противоестественном" для ЭВМ виде. Например, когда требуется просто сложить несколько чисел и вывести результат. И это актуально для простых встраиваемых ЭВМ и калькуляторов.

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

Конечно, без всего этого можно обойтись. Но иметь, хотя бы, представление о том, как это работает, я считаю весьма полезным.

Символьное представление

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

Такое представление, и не только цифр, обычно определяется таблицей кодировки. Она задает соответствие символа и его числового представления. В распространенных сегодня кодовых таблицах цифры, в их печатном виде, представлены в виде шестнадцатеричных чисел от 30h (0х30) до 39h (0х39). Так символу, цифре, <0> соответствует число 30h, а символу <5> число 35h. И эти числа умещаются в байт.

И мы можем представить любое целое число в виде строки символов, последовательности байт с кодами символов

Представление целого числа как последовательности символов, чисел соответствующих кодам символов. Иллюстрация моя
Представление целого числа как последовательности символов, чисел соответствующих кодам символов. Иллюстрация моя

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

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

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

Сложение двух цифр-символов. Иллюстрация моя
Сложение двух цифр-символов. Иллюстрация моя

Во первых, некоторые биты участвующих в операции сложения бит байт будут неизменными, для любой операции сложения. Я показал их зеленым цветом. Старшая тетрада байта слагаемых всегда равна 3. А вот старшая тетрада результата сложения может быть равна 6 или 7. Впрочем, старшая тетрада нас не сильно интересует, с ней разберемся чуть позже.

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

Например, результат сложения 5 и 7, как на иллюстрации, равен 12, что не помещается в одну десятичную цифру. Но никакого переноса, что бы учесть это при операции с последующими цифрами, не появляется. А нам этот перенос, назовем его десятичным, очень нужен.

Поэтому, после сложения двух цифр-символов нам потребует дополнительная операция - коррекция результата. В коррекции участвует не младшая тетрада, а младшие 5 бит результата. Так как результат сложения двух цифр, чисел, от 0 до 9 равен числу от 0 до 18 (десятичное). Что соответствует двоичному числу от 00000 до 10010.

Операция, машинная команда, коррекции проверяет младшие 5 бит результата. Если их значение превышает 9, то выполняется операция вычитания из них числа 10 (десятичное), что дает нам верное содержимое младшей тетрады результата. При этом устанавливается и флаг переноса в слове состояния процессора. Если в слове состояния процессора есть отдельный флаг десятичного переноса, то устанавливается именно он, а не общий флаг переноса. Если значение младших 5 бит результата не превышает 9, то коррекция результата не выполняется, а флаг переноса (десятичного переноса) сбрасывается.

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

Почему используется дополнительная команда коррекции, а не специальная команда символьного сложения? Все очень просто, команды коррекции может и не быть, коррекция может выполняться программистом с помощью обычных универсальных команд. А значит, усложнения схемотехники процессора не потребуется. Но специальная команда символьного сложения может и существовать. Все определяет разработчик процессора.

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

Но чаще символьные арифметические операции выполняются не над символьным представлением цифр, а над их упрощенным представлением, которое мы сейчас и рассмотрим.

Неупакованные двоично-десятичные числа

Двоично-десятичные числа часто сокращенно называют BCD - Binary Coded Decimal. Для двоично-десятичного представления одной цифры нужно ровно 4 бита - тетрада. И символьное представление, которое мы рассматривали ранее, можно свести к двоично-десятичному если отбросить старшую тетраду числового представления символа. Все равно она содержит неизменное значение 3.

Двоично-десятичное представление распространено в цифровой технике давно. Так двоично десятичные-счетчики имеют выходы, которые обозначают 1-2-4-8

Двоично-десятичные счетчики 155ИЕ2
Двоично-десятичные счетчики 155ИЕ2

Состояние таких счетчиков выводилось на индикацию с помощью дешифраторов. Один счетчик - одна цифра.

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

Поскольку значащими и в символьном, и в двоично-десятичном представлении, являются только младшие 4 бита (5 младших бит результата), отдельно рассматривать операции с неупакованными BCD числами мы не будем. Все точно так же. Только команда коррекции результата будет обнулять старшую тетраду, а не заносить в нее 3.

Упакованные двоично-десятичные числа

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

Такой формат представления двоично-десятичных чисел называется упакованным, две двоично-десятичные цифры упакованы в один байт

Упакованное двоично-десятичное число 5371. Иллюстрация моя
Упакованное двоично-десятичное число 5371. Иллюстрация моя

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

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

Пример сложения двух упакованных двоично-десятичных чисел. Иллюстрация моя
Пример сложения двух упакованных двоично-десятичных чисел. Иллюстрация моя

Мы опять использовали обычную операцию сложения (двоичного). И у нас получился неверный результат. Должно было получиться <64>. Поэтому нам снова требуется операция коррекции. Для данного примера младшая тетрада результата больше 9. Поэтому, точно так же, как делали раньше, вычитаем из нее 10 (десятичное). Получаем E-A=4 (14-10=4). И это правильная цифра результата.

Осталось разобраться с переносом. Теперь нам не нужно устанавливать флаг переноса в слове состояния процессора, так как перенос для нашей пары цифр (числа) является внутренним. Но нам нужно увеличить на 1 старшую тетраду результата для обработки переноса между цифрами результата. И мы получим правильный результат <64>.

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

Однако, с переносами, да и с коррекцией, все немного сложнее. Просто пример был выбран "легкий". Давайте рассмотрим пример посложнее

Сложение двух двоично-десятичный чисел с возникновением межтетрадного переноса. Иллюстрация моя
Сложение двух двоично-десятичный чисел с возникновением межтетрадного переноса. Иллюстрация моя

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

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

И операция коррекции результата для сложения упакованных двоично-десятичных чисел будет такой:

  1. Если результат в младшей тетраде превышает 9, необходимо выполнить коррекцию младшей тетрады вычитанием из нее 10 (десятичное). Дополнительно устанавливается флаг вспомогательного переноса. Перейти к шагу 3
  2. Если установлен флаг вспомогательного переноса, необходимо выполнить коррекцию младшей тетрады вычитанием из нее 10 (десятичное). После чего флаг вспомогательного переноса сбрасывается.
  3. Если установлен флаг вспомогательного переноса, старшая тетрада результата увеличивается на 1.
  4. Если результат в старшей тетраде больше 9 или установлен флаг переноса, необходимо выполнить коррекцию старшей тетрады вычитанием из нее 10 (десятичное). Флаг переноса при этом устанавливается.

Еще раз обращаю внимание, что вспомогательный перенос соответствует переносу между тетрадами, из младшей в старшую. Он устанавливается как результат операции сложения, если произошло переполнение тетрады. Или как результат операции коррекции, если произошло не двоичное, а десятичное переполнение (тетрада больше 9).

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

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

0010 - 1010 = 1000 = 8 (десятичное)

И мы получаем верный результат <68>.

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

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

А что со знаком числа?

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

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

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

Заключение

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

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

В следующей статье мы наконец то отойдем от целых чисел. Впереди еще много интересного.

Приглашаю посетить дружественный канал

До новых встреч!