Представление вещественных чисел

Материал из Викиконспекты
Версия от 07:39, 20 октября 2011; Murtaught (обсуждение | вклад) (Слегка изменил распределение подразделов по разделам.)
Перейти к: навигация, поиск

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

Число с плавающей запятой состоит из набора отдельных двоичных разрядов, условно разделенных на так называемые знак, порядок и мантиссу. В наиболее распространённом формате (стандарт IEEE 754) число с плавающей запятой представляется в виде набора битов, часть из которых кодирует собой мантиссу числа, другая часть — показатель степени, и ещё один бит используется для указания знака числа (0 - если число положительное, 1 - если число отрицательное). При этом порядок записывается как целое число в коде со сдвигом, а мантисса - в нормализованном виде, своей дробной частью в двоичной системе счисления. Вот пример такого числа из 16 двоичных разрядов:

Знак
Порядок Мантисса
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
14 10 9 0

Знак - один бит, указывающий знак всего числа с плавающей точкой. Порядок и мантисса — целые числа, которые вместе со знаком дают представление числа с плавающей запятой в следующем виде:

[math](-1)^s \times M \times B^E[/math], где s — знак, B-основание, E — порядок, а M — мантисса.

Порядок также иногда называют экспонентой или просто показателем степени.

При этом лишь некоторые из вещественных чисел могут быть представлены в памяти компьютера точным значением, в то время как остальные числа представляются приближёнными значениями. Попробуйте, скажем, перевести число [math]0.2[/math] в двоичную систему счисления - получится бесконечная запись [math]0,(0011)[/math]


Нормальная и нормализованная форма

Нормальной формой числа с плавающей запятой называется такая форма, в которой мантисса (без учёта знака) в десятичной системе находится на полуинтервале [0; 1). Такая форма записи имеет недостаток: некоторые числа записываются неоднозначно (например, 0,0001 можно записать в 4 формах — 0,0001×100, 0,001×10−1, 0,01×10−2, 0,1×10−3), поэтому распространена также другая форма записи — нормализованная, в которой мантисса десятичного числа принимает значения от 1 (включительно) до 10 (не включительно), а мантисса двоичного числа принимает значения от 1 (включительно) до 2 (не включительно). То есть в мантиссе слева от запятой до применения порядка находится ровно один знак. В такой форме любое число (кроме 0) записывается единственным образом. Ноль же представить таким образом невозможно, поэтому стандарт предусматривает специальную последовательность битов для задания числа 0 (а заодно и некоторых других полезных чисел, таких как [math]-\infty[/math] и [math]+\infty[/math]). Так как старший двоичный разряд (целая часть) мантиссы вещественного числа в нормализованном виде всегда равен «1», то его можно не записывать, сэкономив таким образом один бит, что и используется в стандарте IEEE 754. В позиционных системах счисления с основанием большим, чем 2 (в троичной, четверичной и др.), этого замечательного свойства нет (ведь целая часть там может быть не только единицей).

Знак
Порядок Мантисса
0 0 0 0 0 0 1, 0 0 0 0 0 0 0 0 0 0
14 10 9 0


Типы чисел с плавающей точкой (по IEEE 754)

Число половинной точности (Binary16, Half precision)

Число́ полови́нной то́чности  — компьютерный формат представления чисел, занимающий в памяти половину машинного слова (в случае 32-битного компьютера — 16 бит или 2 байта). В силу невысокой точности этот формат представления чисел с плавающей запятой обычно используется в видеокартах, где небольшой размер и высокая скорость работы важнее точности вычислений.

Знак
Порядок Мантисса
0 0 0 0 0 0 1, 0 0 0 0 0 0 0 0 0 0
14 10 9 0

Порядок записан со сдвигом -15. Это означает, что если дан порядок 011012 то он на самом деле равен не 13, а -2 (потому как [math]13-15=-2[/math]).

Ограничения точности

  • Целые от нуля до 2048 передаются как есть.
  • Целые от 2049 до 4096 округляются к ближайшему чётному целому.
  • Целые от 4097 до 8192 округляются до ближайшего целого, делящегося нацело на 4.
  • Целые от 8193 до 16384 округляются до ближайшего целого, делящегося на 8.
  • Целые от 16385 до 32768 округляются до ближайшего целого, делящегося на 16.
  • Целые от 32769 до 65535 округляются до ближайшего целого, делящегося на 32.


Число одинарной точности (Binary32, Single precision, float)

Число́ одина́рной то́чности — компьютерный формат представления чисел, занимающий в памяти одно машинное слово (в случае 32-битного компьютера — 32 бита или 4 байта). Используется для работы с вещественными числами везде, где не нужна очень высокая точность.

Знак
Порядок (8 бит) Мантисса (24+1 бита)
0 0 0 0 0 0 0 0 0 1, 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
30 24 23 0

Порядок записан со сдвигом -127.


Число двойной точности (Binary64, Double precision, double)

Число́ двойно́й то́чности — компьютерный формат представления чисел, занимающий в памяти два машинных слова (в случае 32-битного компьютера — 64 бита или 8 байт). Часто используется благодаря своей неплохой точности, даже не смотря на двойной расход памяти и сетевого трафика относительно чисел одинарной точности.

Знак
Порядок
(11 бит)
Мантисса
(52+1 бит)
0 0 0 0 0 0 0 0 0 0 0 0 1, 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
62 52 51 0

Порядок записан со сдвигом -1023.


Число четверной точности (Binary128, Quadruple precision)

Число́ четверно́й то́чности — компьютерный формат представления чисел, занимающий в памяти четыре машинных слова (в случае 32-битного компьютера — 128 бит или 16 байт). Используется в случае необходимости крайне высокой точности.

Знак
Порядок
(15 бит)
Мантисса
(112+1 бит)
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1, 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
126 112 111


Мантисса
(112+1 бит)
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0

Порядок записан со сдвигом -16383.

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


Диапазон значений чисел с плавающей запятой

Диапазон чисел, которые можно записать данным способом, зависит от количества бит, отведённых для представления мантиссы и показателя. Пара значений показателя (когда все разряды нули и когда все разряды единицы) зарезервирована для обеспечения возможности представления специальных чисел. К ним относятся ноль, значения NaN (Not a Number, "не число", получается как результат операций типа деления нуля на ноль) и [math]\pm\infty[/math].

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


Название (IEEE 754)Тип (C)ДиапазонБиты мантиссыБиты
Half precisionНет6,10×10-5..6550410+116
Single precisionfloat3,4×10-38..3,4×103823+132
Double precisiondouble1,7×10-308..1,7×1030852+164
Extended precisionНет, иногда long double3,4×10-4932..3,4×10493264+180

Особые значения чисел с плавающей точкой

Ноль (со знаком)

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

Знак
Порядок Мантисса
0/1 0 0 0 0 0 1, 0 0 0 0 0 0 0 0 0 0  = [math]\pm0[/math]
14 10 9 0

Зачем нужен ноль со знаком?
Знак у нуля был оставлен умышленно, хотя при сравнении согласно стандарту [math]-0 = +0[/math]. Сделано это для того, чтобы получать всегда как можно более корректный результат, даже если считаемое значение выйдет за нижнюю или верхнюю границу точности (обернется в [math]0[/math] или [math]\infty[/math]). Кроме того, такой подход в некоторой мере отражает особенности, возникающие в математическом анализе, где, скажем, функция может стремиться к нулю "сверху" или "снизу". Также ноль со знаком находит применение в статистической механике и некоторых других дисциплинах.


Арифметика нуля со знаком
Арифметика отрицательного нуля аналогична таковой для любого отрицательного числа и понятна интуитивно. Вот несколько примеров:

  • [math]\frac{-0}{ \left| x \right| } = -0\,\![/math] (если [math]x\ne0[/math])
  • [math](-0) \cdot (-0) = +0\,\![/math]
  • [math]\left| x \right| \cdot (-0) = -0\,\![/math]
  • [math]x + (\pm 0) = x\,\![/math]
  • [math](-0) + (-0) = -0\,\![/math]
  • [math](+0) + (+0) = +0\,\![/math]
  • [math]\frac{-0}{-\infty} = +0\,\![/math]
  • [math]\frac{\left|x\right|}{-0} = -\infty\,\![/math] (если [math]x\ne0[/math])


Неопределенность (NaN)

NaN - это аббревиатура от фразы "not a number". Специальное представление, этакое псевдочисло, придуманное для того, чтобы арифметическая операция могла всегда вернуть какое-то не бессмысленное значение. В IEEE 754 NaN представлен как число, в котором все двоичные разряды порядка - единицы, а мантисса не нулевая.

Знак
Порядок Мантисса
0/1 1 1 1 1 1 1, 0/1 0/1 0/1 0/1 0/1 0/1 0/1 0/1 0/1 0/1  = [math]NaN[/math]
14 10 9 0

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

Как можно получить NaN?

  • [math]\infty+(-\infty)= NaN[/math]
  • [math]0\times\infty= NaN[/math]
  • [math]\frac{\pm0}{\pm0}=\frac{\pm\infty}{\pm\infty} = NaN[/math]
  • [math]\sqrt{x} = NaN[/math], где [math]x\lt 0[/math]

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

По определению NaN ≠ NaN, поэтому, для проверки значения переменной нужно просто сравнить ее с собой.


Бесконечности

В число с плавающей запятой можно записать значение [math]+\infty[/math] или [math]-\infty[/math]. Как и нули со знаком, бесконечности позволяют получить хотя бы близкий к правильному результат вычисления в случае переполнения. Согласно стандарту IEEE 754 число с плавающей запятой считается равным бесконечности, если все двоичные разряды его порядка - единицы, а мантисса равна нулю. Знак бесконечности определяется знаковым битом числа.

Знак
Порядок Мантисса
0/1 1 1 1 1 1 1, 0 0 0 0 0 0 0 0 0 0  = [math]\pm\infty[/math]
14 10 9 0

Получить бесконечность можно при переполнении и при делении ненулевого числа на ноль. Бесконечность при делении разработчики определили исходя из существования пределов, когда делимое и делитель стремиться к какому-то числу. Соответственно, [math]\frac{c}{0}=\pm\infty[/math] (например, [math]\frac{7}{0}=+\infty[/math], а [math]\frac{-7}{0}=-\infty[/math]), так как если делимое стремиться к константе, а делитель к нулю, предел равен бесконечности. При [math]\frac{0}{0}[/math] предел не существует, поэтому результатом будет NaN.


Денормализованные числа

Денормализованные числа (denormalized\subnormal numbers) - это способ увеличить количество представимых числом с плавающей запятой значений около нуля, дабы повысить точность вычислений. Каждое значение денормализованного числа меньше самого маленького нормализованного ("обычного") значения числа с плавающей запятой. Согласно стандарту, если порядок равен своему минимальному значению (все его биты - нули, а порядок формально равен своему сдвигу) и все биты мантиссы равны нулю, то это [math]\pm0[/math]. Если же мантисса не равна нулю, то это число с порядком, на единицу большим минимального (все биты порядка, кроме младшего - нули) и данной мантиссой, целая часть которой считается равной нулю, а не единице.

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

  • [math](-1)^s\times1.M\times2^E[/math], если [math]E_{min} \le E \le E_{max}[/math] (нормализованное число)
  • [math](-1)^s\times0.M\times2^{E_{min}}[/math], если [math]E=E_{min}-1[/math] (денормализованное число)

Где [math]s[/math] - бит знака, [math]M[/math] - последовательность битов мантиссы, [math]E[/math] - значение порядка (с учетом сдвига), [math]E_{min}[/math] - минимальное значение порядка, используемое для записи чисел (1-сдвиг) , [math]E_{min}-1[/math] - минимальное значение порядка, которое он в принципе может принять (все биты нули, 0-сдвиг).

Хоть денормализованные числа и позволяют бороться с погрешностями и обрабатывать очень маленькие значения, за эти возможности приходится дорого платить. Ввиду сложности денормализованные числа крайне редко реализуют на аппаратном уровне - вместо этого используются программные реализации, работающие значительно медленнее.
Поскольку в стандартных форматах (одинарной и двойной точности) денормализованные числа получаются действительно очень маленькими и практически никак не влияют на результат некоторых вычислений (при этом заметно замедляя их скорость), то иногда они просто игнорируются. При этом используются два простых механизма, получивших называние Flush-to-zero (FTZ) и Denormals-are-zero (DAZ). Первый механизм заставляет операции возвращать ноль, как только становится ясно, что результат будет денормализованным. Второй механизм заставляет операции рассматривать поступающие на вход денормализованные числа как нули.
Ярким примером подобного "отсечения" денормализованных чисел могут послужить видеокарты, в которых резкое падение скорости вычислений в сотню раз недопустимо. Так же, например, в областях, связанных с обработкой звука, нет нужды в очень маленьких числах, поскольку они представляют столь тихий звук, что его не способно воспринять человеческое ухо.

В версии стандарта IEEE 754-2008 денормализованные числа (denormal или denormalized numbers) были переименованы в subnormal numbers, то есть в числа, меньшие "нормальных". Поэтому их иногда еще называют "субнормальными".


Действия с числами с плавающей запятой

Умножение и деление

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

  e=3;  m=4.734612        (порядок и мантисса первого числа)
× e=5;  m=5.417242        (порядок и мантисса второго числа)
-----------------------
  e=8;  m=25.648538980104 (произведение как оно есть)
  e=8;  m=25.64854        (мантисса после округления)
  e=9;  m=2.564854        (нормализованная форма)

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


Сложение и вычитание

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

  [math]123456.7 = 1.234567 \times 10^5[/math]
[math]101.7654 = 1.017654 \times 10^2 = 0.001017654 \times 10^5[/math]
  Отсюда:
  [math]
   123456.7 + 101.7654 = (1.234567 \times 10^5) + (1.017654 \times 10^2)
                       = (1.234567 \times 10^5) + (0.001017654 \times 10^5)
                       = (1.234567 + 0.001017654) \times 10^5
                       =  1.235584654 × 10^5
   [/math] 

По шагам:

  e=5;  m=1.234567     (123456.7)
+ e=2;  m=1.017654     (101.7654)
  
  e=5;  m=1.234567
+ e=5;  m=0.001017654  (Мантисса второго числа после сдвига)
--------------------
  e=5;  m=1.235584654  

Это настоящий результат. Его придется округлить до семи знаков после запятой и нормализировать, если понадобится.

  e=5;  m=1.235585    (Это наш результат)

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

  e=5;  m=1.234567
+ e=−3; m=9.876543
  
  e=5;  m=1.234567
+ e=5;  m=0.00000009876543 (после сдвига)
----------------------
  e=5;  m=1.23456709876543 (настоящая сумма)
  e=5;  m=1.234567         (после округления и нормализации - равна первому числу)

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


Алгоритм получения представления вещественного числа в памяти ЭВМ

Покажем преобразование действительного числа для представления его в памяти ЭВМ на примере величины типа Double.

Как видно из таблицы, величина это типа занимает в памяти 8 байт. На рисунке ниже показано, как здесь представлены поля мантиссы и порядка (нумерация битов осуществляется справа налево):

SСмещенный порядокМантисса
6362..5251..0

Можно заметить, что старший бит, отведенный под мантиссу, имеет номер 51, т.е. мантисса занимает младшие 52 бита. Черта указывает здесь на положение двоичной запятой. Перед запятой должен стоять бит целой части мантиссы, но поскольку она всегда равна 1, здесь данный бит не требуется и соответствующий разряд отсутствует в памяти (но он подразумевается). Значение порядка хранится здесь не как целое число, представленное в дополнительном коде. Для упрощения вычислений и сравнения действительных чисел значение порядка в ЭВМ хранится в виде смещенного числа, т.е. к настоящему значению порядка перед записью его в память прибавляется смещение. Смещение выбирается так, чтобы минимальному значению порядка соответствовал нуль. Например, для типа Double порядок занимает 11 бит и имеет диапазон от 2-1023 до 21023, поэтому смещение равно 1023(10) = 1111111111(2). Наконец, бит с номером 63 указывает на знак числа.

Таким образом, из вышесказанного вытекает следующий алгоритм для получения представления действительного числа в памяти ЭВМ:

  1. перевести модуль данного числа в двоичную систему счисления;
  2. нормализовать двоичное число, т.е. записать в виде M × 2p, где M — мантисса (ее целая часть равна 1(2)) и p — порядок, записанный в десятичной системе счисления;
  3. прибавить к порядку смещение и перевести смещенный порядок в двоичную систему счисления;
  4. учитывая знак заданного числа (0 — положительное; 1 — отрицательное), выписать его представление в памяти ЭВМ.

Пример. Запишем код числа -312,3125.

  1. Двоичная запись модуля этого числа имеет вид 100111000,0101.
  2. Имеем 100111000,0101 = 1,001110000101 × 28.
  3. Получаем смещенный порядок 8 + 1023 = 1031. Далее имеем 1031(10) = 10000000111(2).
  4. Окончательно
    1100000001110011100001010000000000000000000000000000000000000000
    6362..5251..0

Очевидно, что более компактно полученный код стоит записать следующим образом: C073850000000000(16).

Другой пример иллюстрирует обратный переход от кода действительного числа к самому числу.

Пример. Пусть дан код 3FEC600000000000(16) или

0011111111101100011000000000000000000000000000000000000000000000
6362..5251..0
  1. Прежде всего замечаем, что это код положительного числа, поскольку в разряде с номером 63 записан нуль. Получим порядок этого числа: 01111111110(2) = 1022(10); 1022 - 1023 = -1.
  2. Число имеет вид 1,1100011 × 2-1 или 0,11100011.
  3. Переводом в десятичную систему счисления получаем 0,88671875.

Ссылки