Бредисловие
Одним из базовых объектов арифметики
является ЧИСЛО. Что такое число? А Бог его знает...
"Предметы я могу лишь называть. Их
представляют знаки. Я могу лишь говорить о них,
выговорить их я не могу" (DZ Людвиг Витгенштейн).
Это вопрос философский, того же разряда, что и
"какова цель жизни". Здесь философию мы
трогать не будем, отметим только, что число - вещь
абстрактная, которую "пощупать нельзя", но
которую можно "выразить", изобразить, а уж
образы доступны для манипуляций, с чем мы и будем
разбираться.
Почему число? Потому что цифровые компьютеры
- устройства вычислительные, манипулирующие
именно числами, и для своей работы требующие
выражения задач в числах. Соответственно,
результат тоже представляется в числах, которые
уже могут преобразовываться во что-то, более
приемлемое для человека: звук, графики, тексты и
т.п.
Заметьте: речь идёт о цифровых компьютерах.
Дело в том, что существуют и другие типы
компьютеров (аналоговые компьютеры, нейросети),
функционирующие на иных законах, нежели
дискретная математика. Соответственно, понятие
числа для них также не так уж и важно.
1. Системы счисления
Таким образом, мы разделяем число и его
представления, называемые также системами
счисления. Система счисления (СС) - это
совокупность символов и правил обозначения
чисел. Все СС разделяются на позиционные и
непозиционные. Наиболее древние известные СС
непозиционные и ныне они практически полностью
вытеснены позиционными системами, хотя кое-какие
рудименты древних СС используются до сих пор. Это
и шестидесятиричная шумерско-ассирийская
система для секунд и минут, и римская (латинская)
запись.
В позиционных системах счисления (ПСС) числа
представляются в виде последовательности цифр, в
которой значение каждой цифры зависит от её
места.
Примечание: используемая ныне римская
система является модификацией оригинальной
системы. Например, в числе 4 (IV) мы видим, что
размещение единицы перед пятёркой меняет знак
единицы, в то время как в оригинальной системе 4
обозначалась бы четырьмя единицами (IIII).
Разновидностей ПСС много, но наиболее
употребимы системы с постоянным основанием, где
значение цифры домножается на основание в
степени, заданной позицией цифры (её разрядом).
Например, в числе 1234 по десятичному основанию
двойка задаёт "две сотни", "двести".
Таким образом, основание СС, с одной стороны,
задаёт количество различных цифр, допустимое для
данной СС, а с другой - число, показывающее, во
сколько раз вес цифры данного разряда меньше
веса цифры соседнего старшего разряда.
Наиболее удобной для реализации в
компьютерах оказалась двоичная система
счисления (ДСС), где каждая цифра, принимающая
значение 0 или 1, называется битом (bit, BInary digiT). Это
не означает, что эта система единственная,
используемая в компьютерах - есть системы и с
другими основаниями. Например, троичная (цифры -
триты), десятичная (на декатронах),
логарифмическая (основание - число e=2.7...) и т.п.
Помимо простоты реализации двоичных элементов и
их высокой надёжности ДСС характеризуется
простотой выполнения арифметических и
логических операций.
Примеры чисел в ДСС:
12 = 110
101012 = 16+4+1 = 2110
112 = 2+1 = 310
111111112 = 25510
11111111111111112 = 3276710
К неудобствам ДСС относятся её
громоздкость в записи (сравните запись 32767 в
десятичной и двоичной системах) и необходимость
перевода десятичных чисел в двоичные и обратно.
2. Нецелые (дробные) числа
При записи "на бумаге" в ПСС нецелых
чисел целая часть отделяется от дробной точкой
(запятой), при этом отсчёт позиций идёт в обратном
направлении от младшего разряда целой части. Так,
в числе 1234.567 по десятичному основанию пятёрка
означает "пять сотых".
Примечание: в Европе и России целая часть
отделяется от дробной запятой, в Америке - точкой.
К сожалению, компьютеры - это ещё одна область, в
которой Америка "диктует моду".
Большие числа записываются с явно
указанной степенью, показывающей, на сколько
разрядов сдвинута точка относительно истинного
положения (12.34*10^-5=0.0001234). Поскольку здесь
положение точки зависит от значения степени,
такие числа называются числами с плавающей
точкой или экспоненциальным форматом, в
противоположность числам с фиксированной
точкой.
В компьютерах дробные числа также
представляются в форме с фиксированной и
плавающей точкой. В первом случае несколько бит
всегда отводится под дробную часть (целое число -
это число с фиксированной точкой с пустой
дробной частью), во втором случае число
представляется в виде двух чисел - мантиссы
(значение числа) и порядка (степени числа). При
переводе числа с плавающей точкой в символьную
форму (об этом ниже) степень обычно отделяется от
мантиссы знаком `e' или `d' (например, 1.2e-3).
Следует отметить, что в силу ограниченного
количества бит, отводимого под числа в
компьютере, точно можно представить только
ограниченное множество действительных чисел.
Все остальные числа можно представить только с
некоторой степенью погрешности. Поэтому всегда
важно при работе с действительными числами в
компьютере учитывать погрешности (абсолютную и
относительную) и применять правильные стратегии
округления.
В качестве забавного факта можно отметить,
что число, у которого бесконечное число девяток в
дробной части при десятичной системе счисления
(т.е. девять в периоде), равно следующему целому
числу: 1.(9)=2.
3. Перевод чисел из одной системы счисления
в другую
Для перевода целого числа, представленного в
СС с основанием p, в СС с основанием q необходимо
данное число делить на основание q (по правилам в
СС с основанием p) до тех пор, пока частное от
деления не окажется меньше q. Последовательность
остатков формирует число по основанию q начиная с
наименьшей цифры, а последнее частное даёт
старшую цифру. Например, переведём в двоичную
систему десятичные числа 11, 10 и 8, для чего будем
делить их на 2:
11/2 = 5, остаток 1
5/2 = 2, остаток 1
2/2 = 1, остаток 0 -> результат 10112
10/2 = 5, остаток 0
5/2 = 2, остаток 1
2/2 = 1, остаток 0 -> результат 10102
8/2 = 4, остаток 0
4/2 = 2, остаток 0
2/2 = 1, остаток 0 -> результат 10002
Перевод из двоичной в десятичную систему
выполняется несколько проще: просто
просуммируем степени двойки в десятичной
системе. Так, число 1011 в двоичной системе в
десятичной запишется как 2^0+2^1+2^3=1+2+8=11. Для этого
рекомендуется заучить как "отче наш"
таблицу степеней двойки:
20 = 1 21 = 2 22 = 4 23 = 8
24 = 16 25 = 32 26 = 64 27 = 128
28 = 256 29 = 512 210 = 1024 ...
Обратите внимание на 210: это значение
очень близко к 103. Отсюда и идут двоичные
кило- (210, тысяча), мега- (220, миллион),
гига- (230, миллиард), тера- (240) и т.п.
Таблица степеней может облегчить и
преобразование десятичных чисел в двоичные: для
этого десятичные числа нужно мысленно
представлять как суммы меньших чисел, близких к
степеням двойки (а потому легче переводимых в
ДСС). Например, 10 - это сумма 8 и 2, 20=16+4, 55=32+23=32+16+7,
при этом 7=23-1, а число, меньшее степени на
единицу, состоит из "девяток" (в данном
случае из всех единиц). Формальных правил такого
перевода нет, это чисто практические навыки.
Правила перевода дробных чисел мы опустим,
как достаточно сложные и в реальной жизни
практически не требующиеся. Пусть эту работу за
нас делает сам компьютер. "Ибо это недостойно
совершенства человеческого - подобно рабам
тратить часы на вычисления" (Лейбниц).
4. Шестнадцатиричная система счисления и
языки программирования
С необходимость перевода ДСС в другие СС
ничего сделать нельзя, а вот с громоздкостью
справляться научились. Для это используют так
называемые двоично-кодированные системы, когда
основание СС является целой степенью двойки: 23
для восьмеричной и 24 для
шестнадцатиричной. Достоинствами этих систем
является большая компактность (в 3 и 4 раза
соответственно короче, чем в ДСС) и простота
преобразования в ДСС и обратно. Действительно,
для перевода в 8-ричную и 16-ричную системы
достаточно сгруппировать двоичные разряды,
начиная с младшего, по три (триады) или по четыре
(тетрады):
010 = 02 = 08 = 016
110 = 12 = 18 = 116
210 = 102 = 28 = 216
310 = 112 = 38 = 316
410 = 1002 = 48 = 416
510 = 1012 = 58 = 516
610 = 1102 = 68 = 616
710 = 1112 = 78 = 716
810 = 10002 = 108 = 816
910 = 10012 = 118 = 916
1010 = 10102 = 128 = A16
1110 = 10112 = 138 = B16
1210 = 11002 = 148 = C16
1310 = 11012 = 158 = D16
1410 = 11102 = 168 = E16
1510 = 11112 = 178 = F16
1610 = 100002 = 208 = 1016
1710 = 100012 = 218 = 1116
1810 = 100102 = 228 = 1216
...
Отсюда видно, что перевод из 16-ричной в
десятичную систему несколько сложнее, но при
определённом навыке не составляет особого труда.
Обратите внимание на ещё одну особенность.
Классических цифр всего десять, а в 16-ричной
системе их должно быть 16. Недолго думая,
программисты просто использовали для остальных
цифр первые буквы латинского алфавита - почему
нет?
Практически все языки программирования
используют для представления чисел описанные
символы и правила их использования. Трудность за
малым: как определить, в какой системе счисления
записано конкретное число? В разных языках
программирования эту проблему решают по разному,
хотя есть общая договорённость, что числа по
умолчанию считаются десятичными.
В ассемблерах СС обычно указывается
суффиксами: `d' для десятичной системы, `b' для
двоичной, `h' для 16-ричной и `o' для 8-ричной.
Соответственно, 16-ричным числам, начинающимся с
буквы, должен предшествовать ноль. В Паскале
16-ричные числа начинаются со знака `$'. В C и C++
8-ричные числа всегда начинаются с нуля (т.е. 010 -
это десятичное 8; экое безобразие), а 16-ричные с
префикса `0x'. Наконец, наиболее универсально и
симпатично представление чисел в Аде. Любое
число в Аде можно записать как число с основанием
(при этом можно использовать подчёркивания):
2#1010_0000_1111.00010101#e5
16#abc012#
36#a0b1yz#
5. Байты и слова
Для удобства компьютеры манипулируют
группами бит некоторой фиксированной длины -
даже если старшие разряды нулевые, они всё равно
представлены. Обычно это байт (byte), "слово"
(word) и "двойное слово" (double word). Байт - это
обычно минимальная адресуемая (имеющая
порядковый номер) единица оперативной памяти
компьютеров. Ранее байт содержал 7 бит, в
современных микропроцессорах в байте 8 бит (ещё
одно удобство 16-ричной системы - в байте ровно две
тетрады). Слово обычно соответствует разрядности
процессора (16, 32 или даже 128 бит) и состоит из
соответствующего числа байт. Наконец, двойное
слово используется для высокоточных вычислений.
Между прочим, 8 бит в байте - это не догма.
Суперкомпьютеры, например, могут иметь
разрядность байта равной разрядности слова.
Наконец, тетрада (полубайт) тоже имеет своё
название (нибл) и некоторые процессоры
поддерживают этот тип.
Представление чисел с плавающей точкой в
современных компьютерах (включая построенных на
процессорах фирмы Intel) соответствует стандартам
IEEE-754. Различают следующие виды таких чисел:
одинарной (32 бита - 23 бита мантиссы, бит знака и 8
бит порядка) и двойной (64 бит - 52 бита мантиссы и 11
порядка) точности. Также, в процессорах Intel
реализован формат расширенной двойной точности
(80 бит - 64 бита мантиссы и 15 бит порядка). Ниже
представлена таблица, в которой показаны
характеристики типичных числовых объектов,
реализуемых в современных компьютерах:
|
размер (бит) |
диапазон значений |
точность (десятичных цифр) |
byte (байт) |
8 |
0..255 |
- |
word (слово) |
16 |
0 .. 65535 |
- |
double word (двойное слово) |
32 |
0 .. 4294967295 |
- |
single (одинарной точности) |
32 |
1.5e-45 .. 3.4e38 |
6-7 |
double (двойной точности) |
64 |
5.0e-324 .. 1.7e308 |
15-16 |
extended (увеличенной точности) |
80 |
3.4e-4932 .. 1.1e4932 |
19-20 |
6. Двоично-десятичная система
Ещё один недостаток двоичной системы -
невозможность однозначного перевода из
десятичной системы и обратно всех дробных чисел,
даже тех, которые можно точно представить
конечной последовательностью цифр. Например, 0.5
можно точно представить в двоичном компьютере, а
0.7 уже нельзя. На заре развития компьютеров
некоторые нечестные программисты пользовались
этим в своих целях: в бухгалтерских программах
ошибки округления они трактовали в пользу своих,
заранее зарезервированных счетов. Надо заметить,
набегали немалые суммы, при том, что все операции
"вроде бы" выполнялись честно.
Байки это или нет (мне не известен ни один
документально подтверждённый случай), но для
борьбы с такими погрешностями иногда
используется ещё одна двоично-кодированная
система - двоично-десятичная (Binary Coded Decimal, BCD), где
каждая двоичная тетрада считается десятичной
цифрой (разумеется, остальные шесть значений,
которые может принимать тетрада, незаконны).
Поддержка операций над числами в системе BCD
имеется далеко не во всех языках, но, по крайней
мере, машинный язык процессоров Intel x86 такие
операции имеет. Более того, эти операции
позволяют применять некоторые программистские
трюки, особенно облегчающие перевод из двоичной
системы в символьную (об этом ниже).
7. Числа со знаком и знаковое расширение
До сих пор мы обсуждали только представление
положительных чисел, но ведь в арифметике
используются и отрицательные числа. При
написании на бумаге мы используем для
отрицательных чисел знак `-' (минус) перед числом,
а для положительных знак `+' (плюс) или никакого
знака. В компьютере же под знак обычно отводится
старший разряд (бит). Нуль в знаковом разряде
означает положительное число, единица -
отрицательное.
Но это не всё. Придумано по меньшей мере три
разных представления знаковых чисел в
компьютере: прямой, обратный и дополнительный
коды. При прямом коде число представляется в виде
абсолютного значения и кода знака (совсем как в
случае написания на бумаге). Особенность этого
кода - в нём существует два нуля, положительный и
отрицательный. В обратном коде отрицательные
числа "инвертируются", то есть для каждой
цифры берётся разность между наибольшей цифрой (2
для двоичной системы) и данной. В этом коде нуль
также имеет два представления. Наконец, в
дополнительном коде отрицательные числа
инвертируются и увеличиваются на единицу (т.е.
число заменяется на его дополнение до 2n,
где n - количество разрядов в числе, отсюда и
название).Особенность дополнительного кода -
операции сложения и вычитания для знаковых и
беззнаковых чисел идентичны, поэтому именно он
получил наибольшее распространение. Именно
поэтому мы и будем рассматривать в основном его.
Примеры чисел в дополнительном коде для
4-битных чисел:
010 = +00002 = 00002 510 = +01012 =
01012
-110 = -00012 = 11112 -210 = -00102
= 11102
Обратите внимание: -1+1=11112+00012=100002=24,
как это определено выше. Следует отметить, что
положительные числа в беззнаковом и знаковых
кодах представляются идентично, разница только
для отрицательных значений. Дополнительный код
асимметричен - отрицательных значений в нём
больше. Например, в 4 битах можно представить
числа от -8 до 7 (в то время как для беззнаковых
чисел здесь можно представить значения от 0 до 15).
Другая особенность дополнительного кода:
отрицательные нечётные числа имеют единицу в
младшем разряде, как и положительные.
Обратите внимание! Кодирование заключается в
том, что знаковые числа "маскируются" под
беззнаковые - и единственно на совести
программиста лежит интерпретация двоичных чисел
в компьютере как знаковых или как беззнаковых.
Здесь также важно всегда указывать количество
разрядов числа, поскольку именно старший разряд
указывает для знаковых чисел - положительное оно
или отрицательное. Если необходимо перейти к
другой разрядности (например, от байта к слову),
то для дополнительного кода все новые старшие
разряды должны принять значение, равное
знаковому биту. Это называется знаковым
расширением.
Выше была приведена таблица, показывающая
диапазоны значений, которые могут быть
представлены в типичных компьютерных типах при
интерпретации их как беззнаковых. Ниже показано,
какие диапазоны могут быть представлены в этих
же типах, если интерпретировать их как знаковые
числа в дополнительном коде (это относится
только к целым числам, поскольку числа с
плавающей запятой обычно представлены в прямом
коде - с положительной мантиссой и отдельным
битом знака):
|
размер (бит) |
диапазон значений |
byte (байт) |
8 |
-128 .. 127 |
word (слово) |
16 |
-32768 .. 32767 |
double word (двойное слово) |
32 |
-2147483648 .. 2147483647 |
8. Кодирование символов и другой
информации
По тому же принципу, что и знаковые числа, в
компьютере кодируется и другая информация. Так,
для символов (букв, знаков) это делается
следующим образом: берётся некоторый набор
знаков и каждому ставится в соответствие число -
порядковый номер в наборе (это также задаёт
"отношение порядка" на множестве знаков,
когда некоторые знаки "меньше" других).
За время существования компьютеров
разработано несколько символьных наборов. Из
известных - устаревший EBCDIC (Extended Binary Coded Decimal
Interchange Code) и поныне используемый ASCII (American Standard Code
for Interchange Information). Оба набора содержат символы
букв, знаков препинания, цифр и прочие символы (в
ASCII буква `R' имеет код 82, а `r' - 114). Оба содержат по 128
символов, что требует 7 бит на код.
Поскольку ASCII содержит только латиницу,
разработано множество 8-битных (256 знаков)
"кодовых страниц" (code page), называемых Extended
ASCII, как для европейских языков, так и для
кириллицы: KOI7, KOI8, ISO8859-5, CP866, Windows 1251 и т.д. Сейчас
популярность начинает приобретать Unicode, 16-битный
набор (65536 знаков), включающий одновременно и ASCII,
и кириллицу, и иероглифы, и математические
символы.
Обратите внимание! Следует чётко различать
знак (такую же абстрактную сущность, как и число -
например, "точка"), его "начертание" и
его код. Часто, когда говорят о символе в
контексте использования в компьютере,
подразумевают именно его код, но на самом деле
это не одно и то же.
Обратите внимание! Представление числа на
бумаге или экране не то же самое, что и в
компьютере. На бумаге мы используем знаки-цифры,
которые, в свою очередь, не обязаны иметь код,
совпадающий со значением цифры. Собственно, в ASCII
цифра `0' имеет 7- или 8-битный код 48. В компьютере же
каждый бит занимает один разряд и
представляется, например, уровнями напряжения.
Поэтому, говоря о преобразовании из одной
системы счисления в другую, всегда следует иметь
в виду также и преобразование в "символьной"
форме.
В языках программирования знаки и строки
(последовательности знаков) представляются как...
знаки и последовательности знаков. Только, чтобы
отделить их от текста программы, используются
разделители-скобки: обычно апострофы для знаков
и кавычки для строк.
А что же другая информация? А она тоже
представляется числами и их суперпозициями
(структура, запись, массив и т.п.). Например,
звуковые волны дискретизируются (т.е.
представляется массивом значений волны в
заданные моменты времени), а графика
растеризуется (т.е. представляется в виде матрицы
пикселов, каждый из которых представляется,
например, тремя значениями красного, зелёного и
голубого цветов) или векторизуется (это уже
геометрия).
Следует затронуть ещё один важный аспект. В
языках C и C++ для знаков введён тип char, который по
стандарту языка может иметь знаковую реализацию.
Это ещё одна дыра в этих и без того проблемных
языках: вторая половина кодов (обычно включающая
кириллицу), с единичным старшим битом,
интерпретируется как отрицательные значения, и
поэтому без "кастинга" к беззнаковым типам
использовать переменные типа char как индексы
массивов нельзя. Внимательный читатель может
спросить - почему? Ведь выше сказано, что значение
числа интерпретируется только программистом?
Дело в том, что для индексации язык производит
знаковое расширение байта до слова, а
беззнаковое число 0xFFFF вовсе не равно
беззнаковому 0xFF. С другой стороны, эта проблема
характерна только для половины кодов - при
знаковом расширении положительных чисел старшие
биты обнуляются, а беззнаковое 0x0020 равно
беззнаковому 0x20.
9. Zаключение
В данной статье рассмотрены важные
особенности функционирования компьютеров,
знание которых необходимо любому программисту и
тем более программисту на ассемблерах.
Упомянуты, но не рассмотрены следующие темы,
имеющие собственную ценность:
- представление информации в аналоговых
компьютерах и нейросетях;
- непозиционные системы счисления (например, код
Грея - особенность этого кода в том, что два
соседних числа отличаются всегда только в одном
бите);
- позиционные системы счисления с непостоянным
основанием (пример - время в виде дней, часов,
минут и т.п.);
- позиционные системы с нецелым основанием;
- виды погрешностей при манипуляциях с
действительными числами и стратегии округления
(вычислительная математика);
- перевод представления действительного числа
между разными основаниями;
- методы кодирования помимо прямого, обратного и
дополнительного кода;
- избыточные коды (коды Хэмминга и циклические
коды) для обнаружения и коррекция ошибок при
хранении и передаче информации (теория
информации);
- компьютерная арифметика и алгебра логики;
- криптография;
- суперпозиция и структуры данных;
- алгоритмы.
Литература
- Н.П.Сергеев, Н.П.Вашкевич "Основы
вычислительной техники", М.: Высш.шк., 1988
- В.К.Злобин, В.Л.Григорьев
"Программирование арифметических операций в
микропроцессорах", М.: Высш.шк., 1991. ISBN 5-06-002052-5
- Д.Э.Кнут "Искусство программирования",
тт.1-3, 3-е изд.: Пер. с англ. - М.: Издательский дом
"Вильямс", 2000. ISBN 5-8459-0080-8 (рус.)
Хемуль Советикус. Утомлённый чаем
любитель сладкого, в девичестве Бильбо
Ленивчатый... |