Кодировка чисел в памяти ЭВМ
Целые числа
К настоящему моменту почти любая ЭВМ использует память, подразделяющуюся на байты — восьмёрки бит. Каждый бит может принимать одно из двух значений (будем
их обозначать 0 и 1). Соответственно, каждый байт может принимать одно из двухсот пятидесяти шести значений. Для записи этих значений обычно используют пары шестнадцатиричных цифр: 00
, 01
, 02
, … ff
.
Соответственно, для записи целых чисел в память ЭВМ обычно используют 256-ричную систему счисления. При этом различают несколько форматов записи. У каждого формата есть:
- порядок цифр (байт): Little Endian (от младших цифр к старшим) и Big Endian (от старших цифр к младшим)
- количество байт: обычно 1, 2, 4, 8 или 16
- диапазон: «без знака» (от \(0\) до \(N-1\)) или «со знаком» (от \(-N/2\) до \(N/2 - 1\)), где \(N\) — общее количество комбинаций используемых байт
При этом для записи чисел диапазона «без знака» используется обычная 256-ричная система счисления, а числа диапазона «со знаком» записываются так же, как их остатки по модулю \(N\) в формате «без знака».
Например, в 2-байтовом LE-формате без знака число \(1234\) имеет вид d2 04
.
А в 1-байтовом формате со знаком число \((-123)\) записывается так же,
как и \(256-123=133\) без знака: 85
.
Числа с плавающей точкой
Для записи рациональных чисел обычно используется формат с плавающей точкой. Устроен он так: все биты кода (который обычно имеет ширину 16 бит, 32 бита или же 64 бита) делятся на три части:
- самый старший бит называется знаком
- следующие несколько бит называются экспонентой (или степенью)
- оставшиеся биты называются мантиссой (или значащей частью)
Для стандартных форматов используются следующие размеры экспонент:
- 16 бит («половинная» точность) — 5-битная экспонента
- 32 бита («одинарная» точность) — 8-битная экспонента
- 64 бита («двойная» точность) — 11-битная экспонента
Знак задаёт луч, на котором находится число. Знак 0 — неотрицательные числа, знак 1 — неположительные.
Луч (будем все его точки обозначать расстоянием до нуля) разбивается на отрезки:
- нулевой — от \(0\) до \(2^{-N}\) для некоторого \(N\)
- первый — от \(2^{-N}\) до \(2^{-N+1}\)
- каждый следующий получается из предыдущего умножением на два
При этом отрезок от \(1\) до \(2\) имеет номер вида \(01\ldots1\) (здесь 4, 7 или 10 единиц).
Экспонента (кроме экспоненты \(1\ldots 1\), которая зарезервирована) — (записанный двоично) номер такого отрезка. Числа с нулевой экспонентой называются денормализованными.
Далее каждый отрезок разбивается на одинаковые части (количеством, равным количеству всевозможных значений мантиссы). Мантисса — порядковый номер левого деления такой части.
Итого, если знак равен \(s\), экспонента равна \(e\) и отлична от крайних значений, мантисса равна \(m\), ширина экспоненты равна \(E\) бит, а ширина мантиссы равна \(M\), то эти данные кодируют число
\[ (-1)^s \cdot 2^{1 + e - 2^{E-1}} \cdot (1 + m\cdot 2^{-M}) \]
В конце концов биты разбиваются на байты и записываются в LE- или BE-порядке.
Пример
Чтобы закодировать число \((-23.75\) в LE-формат половинной точности, проведём следующие действия.
Знак равен 1 (т.к. число отрицательное).
Подберём экспоненту:
- 01111 соответствует отрезку от 1 до 2
- 10000 соответствует отрезку от 2 до 4
- 10001 соответствует отрезку от 4 до 8
- 10010 соответствует отрезку от 8 до 16
- 10011 соответствует отрезку от 16 до 32
Последний отрезок нам и нужен.
Теперь будем определять мантиссу:
- мантиссы с началом 0 кодируют числа отрезка от 16 до 24
- мантиссы с началом 01 кодируют числа отрезка от 20 до 24
- мантиссы с началом 011 кодируют числа отрезка от 22 до 24
- мантиссы с началом 0111 кодируют числа отрезка от 23 до 24
- мантиссы с началом 01111 кодируют числа отрезка от 23.5 до 24
- мантиссы с началом 011111 кодируют числа отрезка от 23.75 до 24
Нам как раз нужно начальное число последнего отрезка. Поэтому наша мантисса имеет вид 0111110000.
Итого биты нашего числа: 1 10011 0111110000. Их 256-ричная запись: cd f0
.
Осталось записать эти два байта в формате LE: f0 cd
.