Представление вещественных чисел. Формат с плавающей запятой. Особенности арифметических операций.

Нормализованная запись числа

  • Любое действительное число можно записать в экспоненциальной форме
Экспоненциальная форма
Представление числа в виде \(a=\pm m \times P^q\), где \(P\) – основание системы счисления, \(m\) называется мантиссой числа, а \(q\)порядком или экспонентой.
  • Существует бесконечно много представлений одного и того же числа в экспоненциальной форме.
  • Нас интересует возможность представления действительного числа единственным образом (ну, или приближенно к единственному)
  • Для этого используется нормализованная форма.
Нормализованная форма
Представление числа в экспоненциальной форме, в которой мантисса числа имеет одну цифру перед запятой, т.е. \(1 \le m < P\).
  • ноль невозможно представить в нормализованной записи.
  • Для однозначного представления нуля существуют специальные соглашения.
  • Числа с плавающей запятой хранятся в нормализованном виде.

Погрешности

  • ограниченное число разрядов
  • неизбежна потеря некоторых значащих цифр
  • погрешность округления.
  • два типа погрешностей
Абсолютная погрешность
это модуль разности \(|a - a'|\) между числом \(a\) и его представлением \(a'\)
Относительная погрешность
это отношение абсолютной погрешности к величине \(\left| \frac{a-a'}{a} \right|\)

Формат с фиксированной запятой

  • Почти не отличается от целых цифр
  • Гарантирует конечную абсолютную погрешность.
  • В памяти хранится в виде мантиссы числа, порядок задаётся неявно.

Формат с плавающей запятой

  • Гарантирует конечную относительную погрешность.
  • стандарт IEEE 754
  • хранится в виде знака, мантиссы и порядка.
  • двоичный код
  • в нормализованной форме мантисса \(1\le m < 2\),
  • цифра перед запятой всегда \(1\)
  • нет смысла её хранить
  • хранится только дробная часть мантиссы.
  • Порядок хранится как целое число со сдвигом \(b = 2^{n-1}-1\)
  • \(n\) – число бит порядка
  • То есть, порядок вычисляется как \(q=q' - b\)
  • \(q'\) – представление числа в памяти (в виде беззнакового целого).

Максимально и минимально представимые значения q:

  • \(q'_ \mathrm{min} = 0\)
  • \(q'_ \mathrm{max} = 2^{n}-1\)
  • \(q^\mathrm{theor}_ \mathrm{min} = 0 - b = 1-2^{n-1}\)
  • \(q^\mathrm{theor}_ \mathrm{max} = 2^{n}-1 -b = 2^{n-1}\)
  • На практике, значения \(q=q^\mathrm{theor}_ \mathrm{min}\) и \(q=q^\mathrm{theor}_ \mathrm{max}\) отводятся под особые нужды
  • фактические пределы на 1 меньше с обеих сторон:
  • \(q_ \mathrm{min} = 2-2^{n-1}\)
  • \(q_ \mathrm{max} = 2^{n-1}-1\)
  • мантисса, в нормализованном представлении
  • \(m_ \mathrm{min} = 1,\)
  • \(m_ \mathrm{max} = 1+\frac{2^r-1}{2^r} = 2 - 2^{-r},\)
  • \(r\) – количество бит, отводимых под дробную часть мантиссы.
  • С учетом денормализованного представления, \(m_ \mathrm{min}^\mathrm{denorm} = 2^{-r}\)
  • Минимально и максимально представимые числа
  • \(a_ \mathrm{max/min} = \pm m_ \mathrm{max} \cdot 2^{q_ \mathrm{max}}\)
  • $= (1-) 2{2{n-1}} $
  • Минимально представимое положительное число
  • \(a_ \mathrm{min}^+ = m_ \mathrm{min}^\mathrm{denorm} \cdot 2^{q_ \mathrm{min}}\)
  • \(= 2^{2-2^{n-1}-r}\)
\(n\) \(r\) Тип \(q_{\mathrm{max}}\) \(q_{\mathrm{min}}\) \(a_ \mathrm{min}^+\) \(a_ \mathrm{max/min}\)
5 10 Нет 15 -14 5.96e-8 ±6.55e4
8 23 float 127 -126 1.40e-45 ±3.4e38
11 52 double 1023 -1022 4.94e-324 ±1.8e308
15 631 long double2 16383 -16382 3.65e-4951 ±1.2e4932
15 112 Нет3 16383 -16382 6.47e-4966 ±1.2e4932

Хранение в памяти

В общем случае, формат с плавающей запятой хранится следующим образом:

\(s\) \(q_{n-1}\) \(\ldots\) \(q_0\) \(m_{r-1}\) \(\ldots\) \(m_0\)
  • \(s\) – знак числа (\(0\) для \(+\) и \(1\) для \(-\))
  • \(q_{n-1} \ldots q_0\) – порядок
  • \(m_{r-1} \ldots m_0\) – дробная часть мантиссы.
  • Число можно вычислить как \[a = (-1)^s \left(1+\sum_{i=0}^{r-1} m_i 2^{i-r} \right) \cdot 2^{\left(\sum_{j=0}^{n-1} q_i 2^i\right) - 2^{n-1} + 1}\]

Денормализованное представление

  • Для представления очень малых величин
  • денормализованное или субнормальное представление.
  • Если все биты порядка установлены в ноль
  • целая часть мантиссы (цифра перед запятой) считается равной нулю
  • порядок считается минимальным (не нулевым!)
  • позволяет представлять величины меньшие минимального порядка, включая ноль (при нулевой мантиссе).
Представление в памяти денормализованных значений
\(s\) \(0\) \(\ldots\) \(0\) \(m_{r-1}\) \(\ldots\) \(m_0\)
  • \(s\) – знак числа (\(0\) для \(+\) и \(1\) для \(-\))
  • \(m_{r-1} \ldots m_0\) – дробная часть мантиссы.
  • Число можно вычислить как \[a = (-1)^s \left(\sum_{i=0}^{r-1} m_i 2^{i-r} \right) \cdot 2^{2-2^{n-1}}\]
  • Следует заметить, что “ноль” может иметь знак: “+0” и “-0”
  • очень малые числа ввиду погрешности округления имеют нулевую мантиссу
  • полезно по крайней мере сохранять информацию о знаке.

-Другие специальные значения включают “бесконечность” и “не число”.

  • Бесконечность получается, если все биты порядка равны 1, а биты мантиссы – 0.
\(s\) \(1\) \(\ldots\) \(1\) \(0\) \(\ldots\) \(0\)
  • “Не число” (NaN) получается, если все биты порядка равны 1, а биты мантиссы не все равны 0.
\(\exists i\in[0;\;r-1]: m_i\neq 0\)
\(s\) \(1\) \(\ldots\) \(1\) \(m_{r-1}\) \(\ldots\) \(m_0\)

Арифметические операции

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

  1. Знаковые биты складываются
  2. Порядки складываются/вычитаются
  3. Мантиссы умножаются/делятся
  4. Результат нормализуется

Пример

\(10_2 \times 1010_2\)

  • В нормализованном виде \(1\cdot2^1 \times 1,010\cdot 2^3\)
  • Порядки складываются, а мантиссы умножаются:
  • \((1 \times 1,010)\cdot 2^{3+1}\)
  • \(1,010\cdot 2^4\)
Число \(10_2\)
s q₄ q₃ q₂ q₁ q₀ m₉ m₈ m₇ m₆ m₅ m₄ m₃ m₂ m₁ m₀
0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Число \(1010_2\)
s q₄ q₃ q₂ q₁ q₀ m₉ m₈ m₇ m₆ m₅ m₄ m₃ m₂ m₁ m₀
0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 0
  • Напрямую складывать сдвинутые представления порядков нельзя, поскольку тогда сдвиг войдет в сумму дважды
  • Можно, преобразовать представление со сдвигом к дополнительным кодам. Для этого старший разряд инвертируется, и прибавляется 1:
Число \(10_2\)
s i₄ i₃ i₂ i₁ i₀ m₉ m₈ m₇ m₆ m₅ m₄ m₃ m₂ m₁ m₀
0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0
Число \(1010_2\)
s i₄ i₃ i₂ i₁ i₀ m₉ m₈ m₇ m₆ m₅ m₄ m₃ m₂ m₁ m₀
0 0 0 0 1 1 0 1 0 0 0 0 0 0 0 0
  • Суммируя и преобразуя обратно (вычитая 1 и инвертируя старший бит):
s i₄ i₃ i₂ i₁ i₀ m₉ m₈ m₇ m₆ m₅ m₄ m₃ m₂ m₁ m₀
0 0 0 0 1
0 0 0 1 1
0 0 1 0 0
s q₄ q₃ q₂ q₁ q₀ m₉ m₈ m₇ m₆ m₅ m₄ m₃ m₂ m₁ m₀
1 0 0 1 1
  • Теперь перемножаем мантиссы, не забывая про неявный бит:
(m₁₀) m₉ m₈ m₇ m₆ m₅ m₄ m₃ m₂ m₁ m₀
1 0 0 0 0 0 0 0 0 0 0
× 1 0 1 0 0 0 0 0 0 0 0
(m₁₀) m₉ m₈ m₇ m₆ m₅ m₄ m₃ m₂ m₁ m₀
1 0 0 0 0 0 0 0 0 0 0
+ 1 0 0 0 0 0 0 0 0 0 0
m₉ m₈ m₇ m₆ m₅ m₄ m₃ m₂ m₁ m₀
0 1 0 0 0 0 0 0 0 0

Результат:

s i₄ i₃ i₂ i₁ i₀ m₉ m₈ m₇ m₆ m₅ m₄ m₃ m₂ m₁ m₀
0 1 0 0 1 1 0 1 0 0 0 0 0 0 0 0

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

  1. Порядки приводятся к наибольшему
  2. Мантиссы складываются/вычитаются
  3. Результат нормализуется
  • Приведение порядков работает следующим образом:

  • Допустим, мы складываем \(10_2\) и \(1010_2\).

  • В нормализованном виде \(1\cdot2^1 + 1,010\cdot 2^3\)

  • храним только дробную часть, приведём число с меньшим порядком \(1\cdot2^1\) к большему порядку. Для этого, перенесём запятую влево:

  • \(0,01\cdot2^3 + 1,010\cdot 2^3\)

  • \((0,01 + 1,010)\cdot 2^3\)

  • \((1,100)\cdot 2^3\)

  • Рассмотрим происходящее в двоичном коде. Воспользуемся 16-битным форматом:
Число \(10_2\)
s q₄ q₃ q₂ q₁ q₀ m₉ m₈ m₇ m₆ m₅ m₄ m₃ m₂ m₁ m₀
0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Число \(1010_2\)
s q₄ q₃ q₂ q₁ q₀ m₉ m₈ m₇ m₆ m₅ m₄ m₃ m₂ m₁ m₀
0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 0
  • При приведении порядков, мантисса числа с меньшим порядком смещается вправо на разность порядков:
s q₄ q₃ q₂ q₁ q₀ m₉ m₈ m₇ m₆ m₅ m₄ m₃ m₂ m₁ m₀
0 0 1 0 0 0 0 0 0 0 0
0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 0
  • Затем мантиссы складываются:
s q₄ q₃ q₂ q₁ q₀ m₉ m₈ m₇ m₆ m₅ m₄ m₃ m₂ m₁ m₀
0 1 0 0 1 0 1 0 0 0 0 0 0 0 0 0

Особые операции

Операция Результат
x / ±∞ ±0
±∞ × ±∞ ±∞
±x / 0 ±∞
∞ + ∞
±0 / ±0 NaN
∞ - ∞ NaN
±∞ / ±∞ NaN
±∞ × 0 NaN

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

  • конечное количество представимых в формате с плавающей запятой чисел
  • В отличие от целых или чисел с фиксированной запятой, распределение представимых чисел неравномерно.
  • Общее количество представимых чисел посчитать сравнительно несложно
  • Это количество возможных значений мантиссы \(2^r\), где \(r\) – битность мантиссы
  • умноженное на количество возможных значений порядка, за исключением “не числа” и “бесконечностей” \(2^n-1\)
  • умноженное на количество возможных значений знака \(2\)
  • минус один, поскольку ноль имеет два в общем-то эквивалентных представления.

Итого,

\[ N = 2\cdot 2^r \cdot (2^n - 1) -1 \]

  • Например, для формата одинарной точности количество представимых чисел равно \[ N_ \mathrm{float} = 2\cdot2^{23} \cdot (2^8-1) - 1 = 4\,278\,190\,079 \]
  • распределение по числовой оси, неравномерно:
  • \(2^r\) представимых чисел находятся в диапазоне \([0; 2^{q_ \mathrm{min}})\),
  • \(2^r\) – в диапазоне \([2^{q_ \mathrm{min}}; 2^{q_ \mathrm{min}+1})\),
  • и так далее.
  • При увеличении порядка на 1, диапазон увеличивается вдвое: \(2^{q}-2^{q-1} = 0.5*2^q = 2^{q-1}\),
  • однако количество представимых чисел в диапазоне не меняется.
  • Оценка для плотности в области числа \(x\) \[\rho(x) = 2^{r - \log_2 x}\]
Плотность чисел с плавающей запятой на числовой оси

(линейный масштаб)

(логарифмический масштаб)

Зависимость плотности от величины числа для формата одинарной точности

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

Машинный эпсилон
это такое минимальное число \(\varepsilon > 0\), что, при вычислениях с плавающей запятой, \(1+\varepsilon \neq 1\)

это такое максимальное число \(\varepsilon>0\), что, при вычислениях с плавающей запятой, \(1+\varepsilon = 1\)

  • эквивалентны в предельном смысле, то есть при неограниченной точности, числа по обоим определениям сходятся в одной точке.
  • на практике несколько отличаются.
  • будем полагаться на первое определение, поскольку оно закреплено в стандарте языка C.
  • Теоретические значения достаточно легко получить
  • такое число, при приведении которого к нулевому порядку получается минимально представимая мантисса.
  • Рассмотрим число с мантиссой \(m=1\) и порядком \(q = -r\)
  • При приведении этого числа к нулевому порядку, необходимо сдвинуть биты мантиссы на \(r\) разрядов вправо
  • При этом получаем \(m=2^{-r}\). Это минимальная денормализованная мантисса.
  • Рассмотрим теперь число, меньшее этого \(m = m_ \mathrm{max}\), \(q= -r-1\).
  • Сдвигая на \(r+1\) разрядов вправо, получаем \(0\).
  • Таким образом, \(\varepsilon = 2^{-r}\).

Приведем типичные значения \(\varepsilon\):

бит \(r\) Тип \(\varepsilon\approx\)
16 10 Нет 9.766e-4
32 23 float 1.192e-7
64 52 double 2.220e-16
80 63 long double 1.084e-19
128 112 Нет 1.926e-34

Так же можно написать несложную функцию для вычисления машинного эпсилон:

template<typename T>
T epsilon() {
    T f = 1;
    while(1+f>1)
        f/=2;
    return f*2;
}
  • Машинный эпсилон интересен тем, что относительная погрешность округления ограничена им сверху:

  • \[ \left| \frac{a-a'}{a} \right| < \varepsilon \]

  • Или, если используется корректное округление

  • \[ \left| \frac{a-a'}{a} \right| < \frac{1}{2}\varepsilon \]

Правила округления чисел с плавающей запятой

  • IEEE 754 определяет правила округления
  1. Если следующий за последним (младшим) значащим битом мантиссы разряд = 0, то ничего не делать.
  2. Если следующий за последним (младшим) значащим битом мантиссы разряд = 1, но все последующие равны нулю, то:
    1. Если последний значащий бит мантиссы = 1, прибавить к нему 1.
    2. Иначе, ничего не делать
  3. Если следующий за последним (младшим) значащим битом мантиссы разряд = 1, и есть еще хотя бы один последующий разряд = 1, то прибавить к последнему значащему биту мантиссы 1.
  • “половина” округляется к ближайшему чётному.

  • Например, для 10-битной мантиссы:

  • \(1,0000000001(00000\ldots)\) округляется до \(1,0000000001\)

  • \(1,0000000001(10000\ldots)\) округляется до \(1,0000000010\)

  • \(1,0000000000(10000\ldots)\) округляется до \(1,0000000000\)

  • \(1,0000000010(10000\ldots)\) округляется до \(1,0000000010\)

  • \(1,0000000010(11000\ldots)\) округляется до \(1,0000000011\)

  • \(1,0000000010(10000\ldots 1\ldots)\) округляется до \(1,0000000011\)

  • \(1,0000000001(10000\ldots 1\ldots)\) округляется до \(1,0000000010\)

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

Погрешность округления
Речь идет о погрешностях, связанных с конечностью представимых чисел. Так, например, число \(0.0001\) непредставимо в виде конечной двоичной дроби, и суммирование 10000 раз \(0.0001\) в итоге не даст ровно 1.

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

Вычитание близких значений
При вычитании близких значений, относительная погрешность резко возрастает, поскольку абсолютная погрешность остается примерно на постоянном уровне, а величина резко уменьшается. В результате, результат вычитания имеет гораздо меньшую точность, чем исходные числа.
Переполнение
Происходит при попытке представить слишком большое или слишком малое число. В первом случае получается ∞, а во втором – 0.
  • нужно всегда помнить, что любое представление \(a'\) имеет бесконечно много соответствующих ему действительных чисел, по сути представляет некий интервал \([a'(1-\varepsilon/2); a'(1+\varepsilon/2)]\)
  • В результате вычислений, этот интервал имеет тенденцию расширятся.

Элементы теории погрешностей

  • Пусть \(y(x)\) – функция одного аргумента
  • аргумент \(x\) задан приближенным значением \(x'\) с абсолютной погрешностью \(\Delta x\)
  • Рассматривая интервал значений \([y-\Delta y/2;y+\Delta y/2]\), получаемый в результате вычисления с приближенным значением \(x'\)
  • заменяя приращение функции \(\frac{\Delta y}{\Delta x}\) на её производную в точке (теорема Лагранжа), получаем
  • \[\Delta y = \Delta x \left|\left.\frac{\mathrm{d}f}{\mathrm{d}x}\right|_ {x=x'}\right|\]
  • легко обобщается на случай функции многих переменных:
  • \[ \Delta y = \sum_i \Delta x_i \left|\left.\frac{\partial f}{\partial x_i}\right|_ {x_1=x_1',\,x_2=x_2',\,\ldots}\right|\]
  • например, для суммы или разности \(f(x_1,x_2) = x_1+x_2\), абсолютная погрешность будет равна \[\Delta (x_1+x_2) = \Delta x_1 + \Delta x_2 \]
  • Относительная погрешность
  • \[\delta (x_1+x_2) = \left|\frac{\Delta x_1 + \Delta x_2}{x_1+x_2}\right|\]
  • \[= \left|\frac{|x_1| \delta x_1 + |x_2| \delta x_2}{x_1+x_2}\right|\]
  • Для суммы, если относительные погрешности слагаемых равны \(\delta x_1 = \delta x_2 = \varepsilon\), то
  • \[\delta (x_1+x_2) = \varepsilon\]
Вывод 1
при суммировании, относительная погрешность сохраняется
  • При вычитании это, вообще говоря, неверно
  • Более того, при вычитании близких величин, относительная погрешность может быть сколь угодно велика.
  • Погрешность умножения \(f(x_1,x_2) = x_1 x_2\)
  • \[\Delta (x_1 x_2) = |x_2| \Delta x_1 + |x_1| \Delta x_2 \]
  • \[\delta (x_1 x_2) = \left|\frac{|x_1| |x_2| \delta x_1 + |x_2| |x_1| \delta x_2}{x_1x_2}\right|\]
  • \[= \delta x_1 + \delta x_2\]
Вывод 2
При умножении, относительные погрешности складываются. При одинаковых относительных погрешностях \(\forall i :\:\delta x_i = \varepsilon\), каждая операция умножения увеличивает погрешность на \(\varepsilon\).
  • Погрешность деления \(f(x_1,x_2) = x_1/ x_2\)
  • \[\Delta (x_1 / x_2) = \left|\frac{1}{x_2}\right| \Delta x_1 + \left|\frac{x_1}{x_2^2}\right| \Delta x_2 \]
  • \[\delta (x_1 / x_2) = \left|\frac{\left|\frac{1}{x_2}\right| |x_1| \delta x_1 + \left|\frac{x_1}{x_2^2}\right| |x_2| \delta x_2}{x_1/x_2}\right|\]
  • \[= \delta x_1 + \delta x_2\]
Вывод 3
При делении, относительные погрешности складываются. При одинаковых относительных погрешностях \(\forall i :\:\delta x_i = \varepsilon\), каждая операция деления увеличивает погрешность на \(\varepsilon\).

  1. 63+15+1 = 79. Причина в том, что один бит в этом формате отводится под целую часть мантиссы. Причины у этого исторические.↩︎

  2. Реально формат long double зависит от компилятора. MSVC использует его как синоним для double, ICC требует специального ключа.↩︎

  3. На самом деле, тоже зависит от компилятора – например, gcc предоставляет расширение __float128↩︎