Международный стандарт представления чисел с плавающей точкой в ЭВМ

Материал из MachineLearning.

Перейти к: навигация, поиск

Содержание

Введение

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

В этом отчёте будут рассматриваться те аспекты представления чисел в ЭВМ, которые важны пользователям, желающим активно работать с вещественными величинами. Вначале будут введены общепринятые понятия для дальнейшего изложения материала. Будет достаточно подробно рассмотрен наиболее часто используемый стандарт IEEE 754. В заключение будут приведены способы доступа к основным параметрам представления вещественных чисел в ряде компиляторов (Microsoft Visual Studio (2003,2005), Borland C (3.1), GCC(3.4) ).

Числа с плавающей точкой

Числа с плавающей точкой - общепринятая форма представления действительных чисел в ЭВМ. Основными параметрами такой формы представления является основание степени \beta (base) и точность p (precision). При этом всегда требуется, чтобы основание степени было целым чётным числом. Если $\beta=10$ и $p=3$, то число 0.1 представляется в виде $1.00\times 10^{-1}$. Однако, очевидно, что при определённых параметрах некоторые числа не удастся представить точно. Например, при $\beta=2$ и $p=24$ то же самое число 0.1 представляется приблизительно в виде 1.10011001100110011001101\times 2^{-4} (поскольку в бинарном представлении число 0.1 имеет бесконечный вид).

В общем случае при заданных параметрах запись вида $d_0.d_1d_2 \dots d_{p-1}\times\beta^e$ представляет число

$\pm\left(d_0+d_1\beta^{-1}+d_2\beta^{-2}\dots+d_{p-1}\beta^{p-1}\right)\beta^e,\ ( 0\leq d_i<\beta )$

При этом d_0.d_1d_2 \dots d_{p-1} называется мантиссой числа и состоит из p позиций. В дальнейшем под числом с плавающей точкой мы будем понимать действительные числа точно представимые в смысле данной формы.

Существуют ещё два важных параметра — максимальный и минимальный показатели степени $e_{max}$ и e_{min}. Таким образом, при фиксированных параметрах мы можем представить $2\left(e_{max}-e_{min}+1\right)\beta^p$ разных чисел с учётом знака.

Здесь возникает проблема - что делать с числами, не представимыми точно. Чаще всего такая ситуация возникает при попытке представить числа, имеющие слишком длинное или вообще бесконечное представление (пример с 0.1). В этом случае нужное нам число лежит где-то между двумя числами с плавающей точкой и будет представляться одним из них. Реже встречается попытка использовать числа, меньшие чем 1.0\times\beta^{e_{min}}, или большие чем \beta.0\times\beta^{e_{max}}. Подробнее об этих случаях речь пойдёт в разделе "Стандарт IEEE".

Введём ещё одну договорённость. Пока что представление чисел с плавающей точкой неуникально. Например, при $\beta=10$ и $p=3$ число 0.1 можно представить как 0.01\times 10^{1} и как 1.00\times 10^{-1}. Представление числа, в старшей позиции которого стоит цифра, отличная от нуля \left(d_0\neq 0\right), мы будем называть нормализованным. Использование нормализованных форм решает проблему неединственности представления чисел с плавающей точкой. (Однако, при такой договорённости возникает интересный вопрос — как представлять 0?)

Машинный эпсилон

Как известно, существует 2 вида погрешностей вычисления — абсолютная и относительная (Ошибки вычислений). Под относительной погрешностью понимается отношение

\delta(\tilde a)=\frac{|\tilde a-a|}{a},

где \tilde a – значение, полученное при округлении, а a - точное значение вычислений.

Представим, что результатом округления действительного числа стало число $d.dd\dots d\times\beta^e$. Худшему случаю округления соответствует абсолютная погрешность, равная $0.00 \dots 0\beta'\times\beta^e$, где \beta'=\beta/2. В мантиссе результата округления p позиций , в мантиссе абсолютной погрешности p+1 позиция.

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

\left(1/2\right)\beta^{-p}\leq\delta\leq\left(\beta/2\right)\beta^{-p}.

Величину \eps=\left(\beta/2\right)\beta^{-p} принято называть машинным эпсилоном (machine epsilon). Таким образом можно утверждать, что при округлении дробного числа ближайшим к нему числом с плавающей точкой относительная погрешность округления не превосходит машинного эпсилона.

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

Стандарт IEEE

Существует два разных стандарта IEEE для чисел с плавающей точкой. IEEE 754 - двоичный стандарт и требует, чтобы \beta=2, а p=24 для одинарной точности (single) и p=53 для двойной точности (double). Также в стандарте IEEE 754 точно обговорено использование битов при представлении чисел в одинарной и двойной точностях. В стандарте IEEE 854 \beta может принимать значение 10 или 2. Также ничего не говорится о распределении битов между мантиссой и степенью.

Понятие стандарт IEEE используется для обозначения свойств, присущих обоим из перечисленных стандартов.

Основание степени

Выбор в качестве основания степени 10 не требует особых разъяснений. Десятичная система - система, привычная для человека. В случае двоичной системы стоит обсудить некоторые достоинства, присущие ей. Мы договорились использовать нормализованные формы чисел. Если \beta=2, то в старшей позиции может стоять только 1. Это даёт нам возможность не хранить эту единицу в памяти и тем самым получить дополнительный бит для мантиссы. В этом случае принято говорить, что стандарт использует скрытый бит (hidden bit).

Вернёмся к вопросу о представлении нуля в нормализованной форме. Ноль соответствует нулевой мантиссе и степени e_{min}-1. Таким образом ноль представляется в виде 1.0\times 2^{e_{min}-1}.

Точность представления

Стандарт IEEE предлагает 4 разных точности: одинарная (single), двойная (double), одинарная расширенная (single-extended) и двойная расширенная (double-extended). IEEE 754 жёстко обговаривают число бит одинарной и двойной точности. Это означает, что на всех ЭВМ, поддерживающих IEEE 754, количество бит в представлении чисел с одинарной точностью и с двойной точностью фиксированы. Количество бит расширенных точностей жёстко не фиксируется. Расширенные форматы призваны хоть немного увеличить количество бит на мантиссу и на показатель степени. Ниже представлена таблица параметров различных точностей в стандарте IEEE.

Форматы
Параметры Формат
Single Single-extended Double Double-extended
p 24 32 53 64
e_{max} +127 1023 +1023 >16383
e_{min} -126 \leq-1022 -1022 \leq-16382
Показатель степени 8 бит \leq 11 бит 11 бит 15 бит
Длинна записи 32 бит 43 бит 64 бит 79 бит

Приведём ещё одну таблицу для наглядности.

Single Double
Самое маленькое нормализованное число 2^{-126} (примерно 1.175494351\times10^{-38}) 2^{-1022} (примерно 2.2250738585072020\times10^{-308})
Самое большое нормализованное число (1-2^{-24})\times 2^{128} (примерно 3.4028235\times10^{38}) (1-(1/2)^{53})\times 2^{1024} (примерно 1.7976931348623157\times10^{308})
Машинный эпсилон 2^{-23} 2^{-52}

Показатель степени

Показатель степени может принимать как положительные так и отрицательные значения. Это означает, что нужно определить метод представления показателя степени в бинарном виде.

Существует несколько общепринятых методов. Например, резервировать один бит на знак, как это реализовано в представлении мантиссы. Однако IEEE использует другой метод - метод со смещением. Для одинарного и двойного формата вводятся смещения - 127 и 1023 соответственно. Допустим, k - значение бинарного вида показателя экспоненты в смысле беззнаковой величины. Тогда показатель экспоненты определяется как k-127 для одинарной точности и как k-1023 для двойной.

Из таблицы следует, что для одинарного формата e_{min}=-126 и e_{max}=127. Может возникнуть вопрос: почему \left|e_{min}\right|<\left|e_{max}\right|? Это сделано для того, чтобы величина обратная к минимальному по модулю числу \left(1/2^{e_{min}}\right) не выходила за рамки представимости. (Однако, при этом величина обратная к максимальному числу становится гарантированно меньше минимального представимого числа. Дело в том, что переполнение - куда более серьёзная проблема, чем потеря значимости.)

В 8 бит, выделенные на показатель степени в одинарном формате, уместится 256 различных значений (от 0 до 255). Это соответствует диапазону показателей степени от -127 до 128. Зачем используется показатель -127 (e_{min}-1) мы уже знаем. Об использовании значения 128 (e_{max}+1) речь пойдёт в разделе "Специальные величины".

Операции и округление

В стандарте IEEE основными арифметическими операциями является сложение, вычитание, деление, умножение и извлечение квадратного корня. При вычислении значения выражений применяется чётное округление.

Стандарт IEEE требует, чтобы результаты основных арифметических операций были точно округлены (exactly rounded). Это означает, что сначала должен быть вычислен точный ответ, а потом он должен быть округлён до ближайшего числа с плавающей точкой.

Само понятие округление на самом деле прозрачно — за исключением округления чисел вида 12.5. К какому из чисел 12 и 13 его округлить? Существует два различных подхода к этому вопросу. Первый - разделить множество {0,1,2,3,4,5,6,7,8,9} пополам на {0,1,2,3,4} и {5,6,7,8,9} и первое округлять вниз, а второе — наверх. Второй подход — округлять числа так, чтобы в наименее значащей позиции после округления стояло чётное число. Например, 12.5 округлять в 12, а 10.1235 — в 10.124. Такое округление называется чётным.

В стандарте IEEE используется чётное округление.

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

Ещё одним интересным вопросом является округление значений трансцендентных функций (Функций, которые не могут быть выражены конечным числом суперпозиций стандартных арифметических операций). Стандарт IEEE не гарантирует точного округления значений таких функций. Хорошим примером является округление значений экспоненты. \exp(1.626)=5.0835. К какому значению округлять \exp(1.626) — к 5.083 или к 5.084? Для округления сначала надо получить точное значение функции. При увеличении точности \exp(1.626)=5.08350, затем \exp(1.626)=5.083500, затем \exp(1.626)=5.0835000. Для принятия решения нам нужно определить чему из 5.0835000\dots 00d или 5.0834999\dots 999d равна \exp(1.626). Понятно, что в случае экспоненты мы не гарантированно доберёмся до интересующей нас позиции. Поэтому в случае трансцендентных функций нет смысла требовать, чтобы значение было сначала получено с бесконечной точностью и затем округлено.


Специальные значения

В стандарте IEEE предложено 3 вида специальных значений - NaN, ноль со знаком и бесконечность со знаком.

В таблице ниже приведены представления этих специальных значений в стандарте IEEE.

Специальные значения
Показатель степени Дробная часть мантиссы Обозначение
e=e_{min}-1 f=0 \pm 0
e=e_{min} f\neq 0 0.f\times2^{e_{min}}
e_{min}\leq e\leq e_{max} 1.f\times2^e
e=e_{max}+1 f=0 Inf
e=e_{max}+1 f\neq0 NaN


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

Именно для таких случаев в стандарте IEEE существуют специальные значения.

NaN

NaN (Not a number) используется стандартом IEEE для обработки ситуаций вида извлечения корня из отрицательного числа.

Приведём небольшую таблицу, демонстрирующую случаи возвращения NaN.

Операции, возвращающие NaN
Операция Случай, когда возникает NaN
+ Inf + \left(-Inf\right)
\times 0\times Inf
/ 0/0, Inf/Inf
REM x REM 0,\ Inf REM y
\sqrt{x} \sqrt{x}\ \left(x<0\right)

В битовом представлении NaN не оговаривается, какое значение должна принимать мантисса. Единственное ограничение — она не должна быть нулевой. Этот факт используется для передачи определённой системной информации через мантиссу. Таким образом существует много различных значений вида NaN — каждое для своего случая.

Предположим, что мы вычисляем значение некоторого выражения, например \left(a+b+c\right)/d. Допустим, a принимает значение NaN. В этом случае итоговое значение выражения также равно NaN. Если рассматривается некоторое длинное выражение, в ходе вычисления которого возникает значение NaN, то системная информация в мантиссе должна соответствовать тому значению NaN, которое возникло первым. Исключением является, например, сложение двух NaN. В этом случае мантисса будет соответствовать одному из них, не обязательно первому.

Приведём хороший пример, демонстрирующий важность этого специального значения. Предположим, что у нас есть программа, вычисляющая нули функции. Мы хотим реализовать её так, чтобы она не требовала интервал поиска в качестве входного аргумента. Такая реализация может вызвать проблему — что, если программа попытается вычислить значение функции вне области её определения? В частности, программа может попытаться взять квадратный корень от -4. Если бы программа выдала сообщение об ошибке и остановила свою работу она была бы для нас бесполезна. Существование NaN даёт нам возможность вернуть его в качестве результата взятия квадратного корня и продолжить работу.

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

Бесконечность существует для решения таких ситуаций, как переполнение и деление на ноль. При переполнении можно было бы возвращать максимально представимое число с плавающей точкой. Однако такое решение не всегда оправдано. Гораздо выгоднее знать, что произошло переполнение, и принять надлежащие меры, получив в качестве результата Inf. Существует положительная и отрицательные бесконечности. При получении Inf существует способ определить по какой причине произошла ошибка — из-за переполнения или из-за деления на ноль. Эта информация доступна в флагах состояния.

Арифметика с использованием Inf интуитивна ясна: при делении конечной величины на Inf результат будет нулём; при сложении или вычитании из конечной величины Inf результат будет Inf или -Inf соответственно.

Приведём небольшой пример бесконечной арифметики.

Допустим, мы работаем с функцией

x/\left(x^2+1\right)

Данная запись функции не очень хороша. При x>sqrt{\beta}\beta^{e_{max}/2} в знаменателе будет вызвано переполнение и результатом будет 0. Это некорректно, так как возвращаемым результатом должна быть величина порядка 1/x. Перепишем формулу в другом виде.

1/\left(x+x^{-1}\right)

Эта формула не будет так скоро вызывать переполнения. В нуле она также будет принимать корректное значение, так как Inf^{-1} округлится нулём.

Этот пример показывает основное достоинство использования бесконечной арифметики: использование Inf часто позволяет обойти проверки многочисленных крайних случаев.

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

Ноль соответствует нулевой мантиссе и показателю степени e_{min}-1. В зависимости от значения знакового бита может быть +0 и -0. Обоснуем ввод этих специальных значений.

Для начала сделаем интересное замечание: в стандарте IEEE определено, что +0=-0. Это сделано для того, чтобы при условных операторах вида if(x==0) не возникало неопределённости. Но это не означает, что стандарт не различает двух этих значений. При умножении чисел на нули со знаками или при делении нулей со знаками на числа знаки определяются в соответствие с обычной знаковой арифметикой. Например, (+0)\times 3=+0 и (-0)/10=-0.

Предположим, что у нуля не было бы знака. Тогда при x=\pm Inf возникла бы неопределённость с вычислением значения выражения 1/(1/x). Неопределённость заключается в том, что и 1/{+Inf} и 1/{-Inf} принимали бы значение 0. В тоже время 1/0 принимало бы логически верное значение +Inf. Другими словами,

1/(1/x)\neq x.

Учитывая существование бесконечностей со знаками единственно верным решением рассматриваемой проблемы является ввод нулей со знаками.

Приведём ещё один пример.

Рассмотрим функцию \log(x). Предположим, что x — очень маленькое отрицательное число. Если бы оно округлялось нулём, то результатом бы была -Inf, что, конечно, неправильно. В стандарте IEEE не возникает такой проблемы. В этом случае x округляется -0 и возвращается значение NaN.

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

Последним специальным значением стандарта IEEE являются ненормализованные числа.

Рассмотрим пример. Пусть p=3, \beta=10 и e_{min}=-98. Числа 6.87\times 10^{-97} и 6.81\times 10^{-97} — числа с плавающей точкой, отличающиеся от минимально представимого числа больше чем в 10 раз. У этих чисел есть интересная особенность : их разность равна нулю, хотя они не равны друг другу. Это происходит потому, что x-y=6.0\times 10^{-99} округляется нулём. Нам бы хотелось, чтобы привычное нам тождество

x=y <=> x-y=0

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

Ввод ненормализованных чисел решает эту проблему.

Суть состоит в том, что при показателе степени равном e_{min} мантисса не должна быть нормализованной. Таким образом при p=3, \beta=10 и e_{min}=-98 1.0\times 10^{-98} уже не минимально представимое число. 0.98\times 10^{-98} также является корректным числом с плавающей точкой.

Доступ к формату в различных компиляторах

Visual Studio C++ (2003, 2005)

В папке include установочной директории VC существует стандартный хидер "float.h", в котором описаны основные параметры форматов float, double и long double. Также существует хидер "limits.h", в котором описан ряд функций по работе со специальными значениями.

Borland C compiler (3.1)

В той же папке include находится хидер "float.h"

GCC compiler (3.4)

В папке установки (usr/lib/gcc/...) есть всё таже папка include с хидерами. В ней есть файл "float.h".

Список литературы

  • David Goldberg.  What Every Computer Scientist Should Know About Floating-Point Arithmetic. ACM Computing Surveys, Vol. 23, No. 1 (March 1991), pages 5--48.

См. также

Личные инструменты