Нормализованным экспоненциальным представлением числа в системе счисления по основанию b является его запись в виде d0.d1d2...dk * be, где di суть цифры, причем d0 > 0. Десятичное число 123.456 в нормализованном виде представляется как 1.23456 * 102.
Числом с плавающей точкой назвают действительное число, которое может быть представлено в указанном виде.
Число с плавающей точкой определяется тремя целыми числами: знаком (S), экспонентой (E) и мантиссой (M).
+---+-----------+---------------------+ | S | exp | mantissa | +---+-----------+---------------------+ 1бит w бит p битЗначение экспоненты может быть как положительным, так и отрицательным. Существует несколько способов кодирования знака: выделение отдельного бита, дополненное представление и смещение. При представлении чисел с плавающей точкой экспонента записывается со смещением. Для чисел одинарной точности экспонента содержит 8 бит и смещение равно 127. Это означает, что если последовательность бит в поле exp образует число k (если эта последовательность рассматривается как двоичное представлеие беззнакового целого числа), то значением экспоненты E является k-127.
Целые числа S, E и M опеределяют десятичное число (-1)S * bE * m.
Ошибки округления могут приводить к ситуациям, когда (a + b) - a - b оказывается отличным от нуля. Например, при a=1.0, b=0.1 значение оказывается равным 2.2351742 10-8 при вычислениях с однократной точностью, и 8.326672684688674 10-17 при вычислении с двойной точностью. Поэтому сравнение числ с плавающей точкой на точное равенство с помощью оператора == не надежно.
Предположим, что мы хотим определить функцию
int almostEqual(double a, double b);
которая возвращает ненулевое значение, если числа a и b можно считать равными. Рассмотрим возможные варианты реализации такой функции.
Сравнение модуля разности с константой. Самым простым методом сравнения является следующий:
#define EPSILON 1e-7
int almostEqual(double a, double b) {
return fabs(a-b) < EPSILON;
}
Равными считаются числа, разность которых по модулю не превосходит заданной константы. В данном примере 10-7.
Данный метод работает, если значения a
и b
близки к 1. Если абсолютные значения велики,
то a
и b
могут быть соседними представимыми числами, но разность меджду ними будет больше, чем EPSILON.
Сравнение модуля разности с динамическим значением EPSILON. Второе решение состоит в динамическом изменении точности.
#define EPSILON 1e-7
int almostEqual(double a, double b) {
double largest = (fabs(a) > fabs(b) ? fabs(a) : fabs(b));
return fabs(a-b) < (largest * EPSILON);
}
В данном случае при сравнении больших чисел значение "константы" будет
увеличено. Этот метод лучше, но что будет, если a
и b
существенно меньше 1?
Сравнение модуля разности с динамическим значением EPSILON. Второе решение состоит в динамическом изменении точности.
#define EPSILON 1e-7
#define MAX(a, b) (fabs(a) > fabs(b) ? fabs(a) : fabs(b))
#define MAX3(a, b, c) (MAX((a), MAX((b), (c))))
int almostEqual(double a, double b) {
return fabs(a-b) < MAX3(a, b, 1.0) * EPSILON;
}
Этот метод достаточно надежен: при сравнении больших чисел
использцется пропорционально увеличенное значение EPSILON, а при
сравнении маленьких чисе – обычное значение.
Стандарт IEEE-754 определяет специальные значения чисел с плавающей
точкой: +Inf
(положительная
бесконечность), -Inf
(отрицательная бесконечность)
и NaN
("не число", Not a Number). Эти значения могут
появиться в процессе выполнения арифметических операций. Например:
a/0 = +Inf
, если a > 0
; a/0 = -Inf
, если a < 0
; 0/0 = Inf - Inf = Inf*0 = NaN
.
-Inf
меньше любого "нормального" числа
и +Inf
.
Значение NaN
не сравнимо с другими значеними и
собой. Выражение NaN != NaN
истинно,
а значения всех остальных сравнений, затрагивающих NaN, ложно.
Если переменные могут принимать специальные значения, то следующие два оператора не эквивалентны!
if (a > b) do_something(); else do_something_else();
if (b <= a) do_something_else(); else do_something();
Для проверки того, что значением переменной является NaN или бесконечность, можно
использовать функции isnan
и isinf
соответсвенно, которые объявлены в
заголовочном файле <math.h>
.