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

Представление дробных чисел в двоичной системе счисления

Дробные числа можно записывать не только в виде десятичных дробей, но и в системах счисления с произвольным основанием. Например, при записи дробных чисел в двоичной системе счисления первая цифра после точки (запятой) соответствует значению \(2^{-1}\), вторая цифра — значению \(2^{-2}\) и т. д. То есть если число в двоичной системе счисления записывается как \(a_{n-1}a_{n-2}\dots a_{0}{.}a_{-1}a_{-2}...a_{-m}\), то есть имеет \(n\) цифр до точки и \(m\) цифр после точки, то значение такого числа равно \(a_{n-1}\cdot 2^{n-1}+a_{n-2}\cdot 2^{n-2}+...+a_{0}\cdot 2^{0}+a_{-1}\cdot 2^{-1}+a_{-2}\cdot 2^{-2}+...a_{-m}\cdot 2^{-m}\).

Например, запись\(0{.}1011_2\) в двоичной системе счисления является представлением числа \(2^{-1}+2^{-3}+2^{-4}\), то есть равно \(\frac{11}{16}\). При этом в виде конечных дробей в двоичной системе счисления можно записать только такие рациональные числа \(\frac{n}{m}\), где знаменатель \(m\) является степенью двойки, другие же рациональные числа представляются в виде бесконечных двоичных дробей.

Например, \(\frac{1}{3}=2^{-2}+2^{-4}+2^{-6}+...=0.010101..._{2}=0.(01)_{2}\), \(\frac{1}{10}=2^{-4}+2^{-5}+2^{-8}+2^{-9}+2^{-12}+2^{-13}+...=0.0001100110011..._2=0.0(0011)_2\). Из-за этого многие рациональные числа (в том числе число \(\frac{1}{10}\)) не могут быть точно представлены в памяти компьютера, поскольку для их точного представления в двоичной системе счисления требуется бесконечной число знаков, так же как и число \(\frac{1}{3}\) не может быть записана в виде десятичной конечной дроби.

Типы данных для представления действительных чисел

Действительные числа в памяти компьютера хранятся в представлении с плавающей точкой: число хранится в виде мантиссы \(b\) и показателя степени \(p\), в этом случае считается, что число равно \(b\cdot 2^p\). Аналогичное представление используется при вводе-выводе чисел, при записи действительных чисел в коде программы. Например, значение постоянной Авогадро, равное \(6.02\cdot10^{23}\) в коде программы или при вводе-выводе следует записать как 6.02e23, где 6.02 — мантиса, 23 — показатель степени, буква «e» при записи действительных чисел в компьютерных программах используется для разделения записи мантисы и показателя.

Но при представлении действительных чисел используется двоичная система счисления, в которой, например, число \(\frac{1}{3}\) в представлении с плавающей точкой можно записать как \(0{.}010101...\cdot 2^{0}\), или \(0{.}0010101...\cdot 2^{1}\), или \(0.10101...\cdot 2^{-1}\) или еще бесконечно большим числом способов. Желательно выбрать из всех таких представлений одно (каноническое), которое будет называться нормализованным, в этом представлении целая часть числа всегда равна 1 (за исключением числа 0). Для числа \(\frac{1}{3}\) нормализованным представлением будет представление \(1.0101...\cdot 2^{-2}\). Поскольку целая часть у нормализованного представления числа всегда (кроме числа 0) равна 1, то целая часть не хранится, а хранится только дробная часть мантиссы. Количество значащих знаков мантиcсы, которое хранится, может быть различным для различных способов представления действительных чисел. Например, в типе данных двойной точности, который в языке Python соответствует типу float, в языке C — типу double, в языке Pascal — типу double, хранится 52 бита дробной части мантиcсы.

Стандарт IEEE754 определяет несколько типов представления действительных чисел, основными из которых являются числа одинарной точности (для их хранения требуется 4 байта), двойной точности (8 байт) и расширенной точности (10 байт).

Точность

Размер (байт)

C, C++

Pascal

Python

Количество знаков мантиссы

Точность, десятичных цифр

Максимальное значение

Минимальное положительное значение

одинарная

4

float

single

-

23

\(\approx 7{,}2\)

\(3{,}4\cdot10^{38}\)

\(1{,}4\cdot10^{-45}\)

двойная

8

double

double

float

52

\(\approx 15{,}9\)

\(1{,}7\cdot10^{308}\)

\(5{,}0\cdot10^{-324}\)

расширенная

10

long double (только в gnu gcc/g++)

extended

-

63

\(\approx 19{,}2\)

\(1{,}1\cdot10^{4932}\)

\(1{,}9\cdot10^{-4951}\)

Неточность при представлении действительных чисел

Рациональные числа, которые не могут быть представлены в виде дроби со знаменателем, являющимся степенью двойки, не могут быть точно представлены в виде конечной двоичной дроби, а, значит, не могут быть в точности представлены в памяти компьютера. Например, вместо числа \(\frac{1}{3}\), которое является бесконечной дробью \(0.010101...\) берется нормализованное представление \(1.0101...01\cdot 2^{-2}\) и хранятся только 52 цифры дробной части мантиссы числа. Дальнейшие цифры отбрасываются, поэтому вместо числа \(\frac{1}{3}\), которое непредставимо в действительных типах данных, хранится другое ближайшее к нему представимое число. В случае с числом \(\frac{1}{3}\) записывается меньшее число, также меньшее число записывается и при записи в переменную двойной точности значений \(\frac{1}{10}\) и \(\frac{2}{10}\), а вот вместо числа \(\frac{3}{10}\) уже будет записано большее представимое число. Поэтому если записать в две переменные значения \(0{.}1\) и \(0{.}2\) (десятичные), а в третью переменную записать их сумму, то результат окажется меньше, чем \(\frac{3}{10}\), в то время как при записи в переменную значения \(\frac{3}{10}\) явно в виде \(0{.}3\), получится результат, больший чем \(\frac{3}{10}\). То есть при вычислении суммы чисел \(0{.}1\) и \(0{.}2\) результат получится отличным от значения \(0{.}3\), записанного в переменную явно, то есть с точки зрения компьютерной арифметики \(0{.}1 + 0{.}2 \ne 0{.}3\). Эту проблему (неточное представление действительных чисел и выполнение арифметических операций с действительными числами) всегда следует иметь в виду.

Использование эпсилон при сравнении действительных чисел

Таким образом, сравнивать действительные числа в компьютерных программах на точное равенство нельзя. Вместо этого если нужно сравнить два числа \(a\) и \(b\) на равенство, правильным будет проверка условия, что эти два числа не сильно различаются, то есть что модуль их разности не превосходит некоторого небольшого значения \(\varepsilon\), то есть что \(|a-b|<\varepsilon\). Значение \(\varepsilon\) как правило определяется для каждой задачи исходя из той точности, с которой необходимо получить результат.

В следующей таблице указано, как нужно проверять различные сравнения действительных чисел с использованием \(\varepsilon\).

Условие

Как нужно проверять

\(a=b\)

\(|a-b|\lt\varepsilon\)

\(a\ne b\)

\(|a-b|\ge \varepsilon\)

\(a\lt b\)

\(a \le b - \varepsilon\)

\(a \le b\)

\(a \lt b + \varepsilon\)

\(a\gt b\)

\(a \ge b + \varepsilon\)

\(a\ge b\)

\(a \gt b - \varepsilon\)

Специальные значения действительных типов

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

Значение inf (от infinity) возникает при переполнении действительной переменной, когда результат становится очень большим. Например, если взять положительное число и умножать его на два в цикле, то довольно скоро получится значение inf. Максимальное значение, которое может быть сохранено до получения переполнения, различается для разных типов действительных чисел.

Значение inf отличается тем, что после получения результата inf из него уже нельзя получить "обычное" действительное число. При прибавлении и вычитании к inf любого действительного (неспециального) числа, умножении и делении inf на положительное число, в результате всегда получается inf. То есть inf считается "очень большим", настолько, что сложение, умножение и деление все равно оставляет результат "очень большим". Значение inf считается больше любого действительного неспециального числа.

Также есть отрицательное значение -inf, которое считается "минус бесконечностью".

При ряде операций со значением inf может возникнуть другое специальное значение nan (от not a number - не число). Примеры операций, дающих в результате nan:

  1. \(inf - inf\)
  2. \(inf \times 0\)
  3. \(inf / inf\)

nan означает, что результат может быть "чем угодно", то есть это непредсказуемая величина и работать с ней невозможно. Любые арифметические операции c nan будут давать в результате nan. Более того, при любом сравнении nan с любым действительным числом при помощи любой из операций ==, <, <=, >, >= в результате будет получаться false. А при сравнении nan с любым действительным числом при помощи операции != всегда будет получаться true. И даже если значения двух переменных a и b оба равны nan, то сравнение a == b будет давать false, а сравнение a != b будет давать true.

В заголовочном файле cmath определены две константы INFINITY и NAN, которые принимают значения inf и nan соответственно, а также две функции bool isinf(x) и bool isnan(x), которые возвращают true, если аргумент x равен inf и nan соответственно или false в противном случае.