Все мы в детстве программировали на Pascal-е. Или по крайней мере слышали об этом замечательном языке программирования. Тут, конечно, возникает вопрос "а чем это он, интересно, такой замечательный?", но я позволю себе оставить объяснение за скобками.
Речь пойдет о немного более приземлённых вещах, а именно о том, как в паскалевских типах данных представлены числа.
С натуральными числами более или менее ясно - где-то в глубине машины привычная конструкция
var x : uint64;
...
x := 3462;
превращается в набор нулей и единиц
00000000 00000000 00000000 00000000 00000000 00000000 00001101 10000110
и спокойно хранится в ожидании, когда это значение кому-нибудь понадобится (не будем портить идиллию мыслями о little/big-endian, дополнительном коде и т.п.). А как быть с вещественными числами? Например, вдруг кому-нибудь придет в голову запихнуть в компьютер пресловутое число π?
Тут начинаются сложности. Поскольку память компьютера - штука конечная, то рациональные, алгебраические, а тем более трансцендентные числа приходится как-то урезать. И если с рациональными числами (то есть, представимыми в виде a/b, где a и b - натуральные) ещё можно было бы извернуться и хранить их в виде пары натуральных чисел a и b, то что делать с числом π, совершенно непонятно.
Однако тут на помощь людям приходят свойства вещественных чисел. Оказывается, множество вещественных чисел сепарабельно, то есть, в нём существует счётное всюду плотное множество. Таким счётным всюду плотным множеством являются вышеупомянутые рациональные числа. Счётность их - факт тривиальный, а по поводу загадочной "всюду плотности" требуется пояснение.
"Всюду плотность" означает, что любое вещественное число можно приблизить рациональными с любой наперед заданной точностью.
Действительно, пусть, например, мы хотим приблизить π рациональным числом с точностью до третьего знака после запятой:
| π − a/b | < 0.001
Возьмём b = 1000 и решим простенькое неравенство для определения a:
−0.001 < π − a/1000 < 0.001
−1 < π · 1000 − a < 1
−1 < 3141.59... − a < 1
a = 3141
Получили приближение: π ≈ 3141 / 1000
Тот же самый фокус можно проделать, разумеется, и с представлением числа в двоичной позиционной системе счисления. В этой системе число π имеет такой симпатичный вид:
π = 11.00100100001111110110101010001000100001011010001100001...
Если мы хотим опять точность до третьего знака после запятой, то возьмём снова b = 1000 (только на этот раз это не тысяча, а число в двоичном формате!) и получим:
−0.001 < π − a/1000 < 0.001
−1 < π · 1000 − a < 1
−1 < 11001.001... − a < 1
a = 11001
Получили приближение: π ≈ 11001 / 1000 (напоминаю, всё это - двоичные числа!) или в более привычном десятичном виде: π ≈ 25 / 8. Хуже, конечно, чем в предыдущем случае, но что мы хотели - точность 0.001 в двоичном формате - это всего-навсего 0.125 в десятичном...
Отметим, что оба этих представления, и π ≈ 3141 / 1000, и π ≈ 11001 / 1000 можно записать в чуть боле компактном и единообразном виде:
π ≈ 3141 / 10³, и π ≈ 11001 / 10³
только в первой записи 10 - это обычная десятка, а во второй - "десятка" двоичная, то есть, число 2.
Подобное представление вещественного числа в виде какого-то числа a, умноженного (или поделенного) на основание системы счисления в какой-то степени v, называется экспоненциальной записью вещественного числа. Числа a и v имеют свои названия - "мантисса" и "порядок". То есть, грубо говоря:
экспоненциальная запись = мантисса × (основание в степени порядок)
или, в более компактном виде:
экспоненциальная запись = мантисса E порядок
например, запись 1.23E2 означает 1.23 · 10², то есть, число 123.
Очевидно, что сама по себе такая запись - вещь неоднозначная. Например, число 3462 можно представить и в виде 346.2 · 10¹, и в виде 34.62 · 10². Поэтому общепринятой является так называемая нормализованная запись, в которой мантисса по модулю не превосходит основания:
3462 = 3.462 · 10³
то есть, тут | 3.462 | < 10
И, наконец, разобравшись с терминологией (или окончательно запутавшись), мы подходим к идее представления вещественных чисел в компьютере. Оказывается, они там так и хранятся: в виде своего рационального приближения a/b, где a - некоторое число, b - соответствующая степень двойки.
Вот, например, как выглядит наше число π, засунутое в тип данных Extended:
00110101 11000010 01101000 00100001 10100010 11011010 00001111 11001001 00000000 01000000 (**)
Понять этот частокол из нулей и единиц просто. Число в формате x86 Extended занимает 10 байтов, из которых 64 бита отводятся для хранения мантиссы, т.е., числа a, 15 битов - для хранения порядка, т.е., значения v степени, в которую надо возвести двойку, чтобы получить число b, и один бит - для хранения знака (0 - число положительное, 1 - отрицательное). Получаем:
a → 11001001 00001111 11011010 10100010 00100001 01101000 11000010 00110101
v → 01000000 00000000
(подчеркнутый бит к v отношения не имеет, это - для хранения знака)
Тут возникает небольшая загадка. Если запись числа a совпадает с ранее приведенным представлением π в двоичном виде, то порядок для получения числа b вместо ожидаемого значения 62 содержит какое-то загадочное число 16384. Разгадка проста: разработчики стандарта поделили доступный 15-битный диапазон возможных значений v пополам. Все порядки v, большие 16383, соответствуют числам b - двойкам в степени 63−(v−16383). А все порядки, меньшие 16383, определяют b как двойку в степени 63+(16383−v).
В данном случае, поскольку у нас v - из верхней половины диапазона, то соответствующее число b равно 2 в степени 63−(v−16383) = 62.
Таким образом, значение π, хранящееся в Extended - это рациональное приближение вида a/b, где a = 14488038916154245685 - значение мантиссы соответствующей экспоненциальной записи числа, b = 4611686018427387904 - степень двойки, соответствующая порядку экспоненциальной записи этого числа.
❏❏❏❏❏❏❏❏❏❏❏❏❏
(**) - получить эту цепочку битов легко. Соответствующий код выглядит примерно так:
var
p : Extended;
b : ^Byte;
byte_no, bit_pos : Byte;
...
p := pi;
b := @p;
for byte_no := 0 to 9 do
for bit_pos := 7 downto 0 do
if ((b + byte_no)^ and (1 shl bit_pos)) <> 0 then write(1) else write(0);
writeln;