Дробные числа можно записывать не только в виде десятичных дробей, но и в системах счисления с произвольным основанием. Например, при записи дробных чисел в двоичной системе счисления первая цифра после точки (запятой) соответствует значению \(2^{-1}\), вторая цифра — значению \(2^{-2}\) и т. д. То есть если число в двоичной системе счисления записывается как \(\overline{a_{n-1}a_{n-2}...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\),
в этом случае считается, что число равно \(\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 бита дробной части мантисы.
Стандарт 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 |
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
:
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
в противном случае.