Представление целых и вещественных чисел в памяти компьютера
1. ХРАНЕНИЕ В ПАМЯТИ ЦЕЛЫХ ЧИСЕЛ БЕЗ ЗНАКА.
Беззнаковые (англ, unsigned) типы данных, т. е. величины, не имеющие отрицательных значений, широко используются в вычислительной технике. Дело в том, что в задачах, решаемых на компьютерах, есть много таких значений: всевозможные счётчики (количество повторений циклов, число параметров в списке или символов в тексте), количество людей или предметов и др.
Чтобы закодировать целое число без знака, достаточно перевести его в двоичную систему счисления и дополнить слева нулями до нужной разрядности. Например, число 28 записывается в 8-разрядную ячейку так:
0001 1100
Это же число в 16-разрядном представлении будет иметь слева ещё 8 нулей. Восьмиразрядные коды некоторых характерных чисел приведены в табл. 4.1.
Таблица 4.1

Минимальное значение для беззнаковых целых чисел всегда равно 0 (все разряды нулевые), а максимальное число Хmах = 2К-1 состоит из всех единиц и определяется разрядностью (количеством бит) К (табл. 4.2).
Таблица 4.2

Теперь рассмотрим числа со знаком (англ, signed). Для того чтобы различать положительные и отрицательные числа, в двоичном коде выделяется один бит для хранения знака числа — знаковый разряд. По традиции для этого используют самый старший бит, причём нулевое значение в нём соответствует знаку «плюс», а единичное — знаку «минус». Ноль формально является положительным числом, так как все его разряды, включая знаковый, нулевые.
Поскольку один бит выделяется для хранения информации о знаке, ровно половина из всех 2К чисел будут отрицательными. Учитывая, что одно значение — нулевое, положительных чисел будет на единицу меньше, т. е. допустимый диапазон значений оказывается несимметричным.
Положительные числа записываются в знаковой форме так же, как и в беззнаковой, но для значения остаётся на один разряд меньше. А как поступить с отрицательными числами? Первое, что приходит в голову, это кодировать отрицательные значения точно так же, как и положительные, только записывать в старший бит единицу. Такой способ кодирования называется прямым кодом. Несмотря на свою простоту и наглядность, он не применяется в компьютерах для представления целых чисел1*. Это неудобно, потому что действия над числами, записанными в прямом коде, выполняются по-разному для разных сочетаний знаков чисел. Поэтому в современных компьютерах отрицательные числа кодируются с помощью другого метода, который менее нагляден, но позволяет выполнять арифметические действия с положительными и отрицательными числами по одному и тому же алгоритму.
Итак, для получения кода целого числа (-Х) нужно:
Алгоритм А1 1) Выполнить инверсию каждого разряда двоичного представления числа X. Такой код называется обратным. 2) К полученному результату прибавить единицу. В результате получается дополнительный код — он дополняет число X до 2К (если сложить его с числом X, мы получим 2К).
Алгоритм А1 приводится в большинстве учебников, но его можно немного изменить так, чтобы облегчить человеку «ручные» вычисления:
Алгоритм А2 1) Вычислить число X - 1 и перевести его в двоичную систему. 2) Выполнить инверсию каждого разряда результата.
Оба алгоритма дают одинаковые результаты, но А2 для человека существенно проще, потому что ему легче вычесть единицу в «родной» десятичной системе, чем прибавлять её в двоичной (при использовании алгоритма А1).
Наконец, оба пункта алгоритма А1 можно объединить, получив ещё один вариант:
Алгоритм АЗ Выполнить инверсию всех старших битов числа, кроме последней (младшей) единицы и тех нулей, которые стоят после неё.
Например, определим дополнительный код числа «-16», которое хранится в 8-разрядной ячейке. Здесь X = 16. Используя алгоритмы А1 и А2, получаем:

Применение алгоритма АЗ к числу 16 = 000100002 сводится к замене первых трёх нулей единицами: 111100002.
Для проверки можно сложить полученный результат с исходным числом и убедиться, что сумма будет равна нулю (перенос из старшего разряда не учитываем). Повторное применение любого из алгоритмов А1-АЗ всегда приводит к восстановлению первоначального числа (убедитесь в этом самостоятельно). Это свойство также удобно использовать для проверки.
В таблице 4.3 показаны шестнадцатеричные и двоичные коды некоторых характерных 8-разрядных чисел.

В начале главы мы отмечали принципиальное различие между вещественными и целыми числами: целые числа дискретны, а вещественные, напротив, непрерывны, а значит, не могут быть полностью корректно перенесены в дискретную по своей природе вычислительную машину. Как же всё-таки кодируются в компьютерах вещественные числа?
В первых ЭВМ использовалось кодирование с фиксированной запятой. Это значит, что положение запятой, отделяющей целую часть от дробной, было жёстко закреплено в разрядной сетке конкретной ЭВМ — раз и навсегда для всех чисел и для всех технических устройств этой машины. Все вычислительные алгоритмы были заранее «настроены» на это фиксированное размещение. Но в задачах, которые решаются на компьютерах, встречаются самые разнообразные по величине числа, от размера атома до астрономических расстояний. Чтобы согласовать их с таким жёстким представлением, программист, подготавливая задачу к решению на ЭВМ, выполнял большую предварительную работу по масштабированию данных: маленькие числа умножались на определённые коэффициенты, а большие, напротив, делились. Масштабы подбирались так, чтобы результаты всех операций, включая промежуточные, не выходили за пределы разрядной сетки и, в то же время обеспечивалась максимально возможная точность (все разряды данных по возможности находились в пределах сетки). Эта работа требовала много времени и часто являлась источником ошибок.
Тем не менее работа с фиксированным размещением запятой не только показала недостатки метода, но и наметила путь их устранения. В самом деле, если наиболее сложным и трудоёмким местом является масштабирование данных, надо его автоматизировать. Иными словами, надо научить машину самостоятельно размещать запятую так, чтобы числа при счёте не выходили за разрядную сетку и по возможности сохранялись с максимальной точностью. Конечно, для этого нужно разрешить компьютеру «перемещать» запятую, а значит, дополнительно как-то сохранять в двоичном коде числа информацию о её текущем положении. В этом и заключается главная идея представления чисел с плавающей запятой.
Удобное представление вещественных чисел не пришлось специально придумывать. В математике уже существовал подходящий способ записи, основанный на том, что любое число А в системе счисления с основанием В можно записать в виде
А = ±Z • BP,
где Z называют значащей частью, а показатель степени Р — порядком числа (рис. 4.19). Для десятичной системы это выглядит привычно, например заряд электрона равен -1,6 • 10—19 кулона, а скорость света в вакууме составляет 3 • 108 м/с.

Однако представление числа с плавающей запятой не единственно. Например, число 23,4 можно записать следующими способами:
2340 • 10-2 — 234 • 10-1 — 23,4 • 10° — 2,34 • 101 — 0,234 • 102 — 0,0234 • 103— …
На первый взгляд выбор очень широкий, однако большинство вариантов обладают серьезными недостатками. В частности, все представления, в которых значащая часть содержит нули непосредственно после запятой (0,0234, 0,00234 и т. п.) или перед ней (2340, 23400 и т. п.), не подходят, поскольку, сохраняя эти незначащие нули, мы напрасно увеличиваем разрядность чисел. Согласно математической теории, для обеспечения максимальной точности при сохранении цифр числа в фиксированном количестве разрядов надо выбирать такой метод, при котором значащие цифры числа следует поместить как можно ближе к запятой. С этой точки зрения оптимальным будет вариант, когда целая часть равна нулю, а первая ненулевая цифра находится сразу после запятой (в нашем примере 0,234). При этом вместо двух частей (целой и дробной) остаётся только дробная, что фактически делает ненужной «разделительную» запятую.
Но взгляните на рис. 4.20, а, изображающий такое число на индикаторе: первый разряд всегда равен нулю, что делает его практически бесполезным. Поэтому с точки зрения экономии разрядов лучше взять другой вариант, в котором значащая часть равна 2,34 (рис. 4.20, б). Именно такой выбор закреплён в стандарте IEEE 754), на котором основана арифметика вещественных чисел в современных компьютерах.

Итак, существует два приблизительно равноценных способа представления чисел с плавающей запятой:
- оптимальный с теоретической точки зрения, в котором целая часть нулевая, а первая цифра дробной части ненулевая (0,234 • 102);
- более удобный с практической точки зрения, в котором целая часть состоит из единственной ненулевой цифры (2,34 • 101). К сожалению, эта «двойственность» порождает некоторую путаницу. В теоретической литературе, как правило, используется первый способ. Все описания конкретных компьютерных систем, напротив, базируются на втором. Причём в обоих случаях обычно используется один и тот же термин — мантисса. Зато в англоязычной компьютерной литературе приняты два разных термина: в первом случае значащая часть называется mantissa (слово «мантисса» для математиков однозначно связано с дробной частью числа), а во втором — significand (значащая часть).









