Определяем машинный ноль, машинную бесконечность и машинный эпсилон
Доверять расчёту, сделанному на компьютере, без тени понимания того, как именно выполнен этот расчёт – одна из худший вещей, которые может допустить в своей работе инженер. К сожалению, уже нередки “специалисты”, которых не смущает ненулевой результат, полученный при умножении на ноль, или, напротив, ноль там, где теоретически нуля быть не должно.
Поэтому повторим в этой заметке несколько азбучных истин о представлении вещественных чисел в компьютере и правилах выполнения операций с ними.
В IBM-совместимой ЭВМ для вещественных чисел используется двоичная система счисления и принята форма представления чисел с плавающей точкой вида
x = m*2p
, где мантисса m = ± (g1*2-1 +
,
g2*2-2 + ... +
gt*2-t)
g1, ..., gt
– двоичные цифры, причём, g1=1
, а целое значение p
называется двоичным порядком. Количество цифр t
, которое отводится для записи мантиссы, называется разрядностью мантиссы. Диапазон представления чисел в ЭВМ ограничен конечной разрядностью мантиссы и значением числа p
.
Все представимые на ЭВМ вещественные числа x
удовлетворяют неравенствам
0 < X0 ≤ |x| < X∞
, где
X0 = 2-pmax+1
,
X∞ = 2pmax
, а значение pmax
соответствует разрядности вычислительной системы.
Все числа, по модулю большие X∞
, не представимы на ЭВМ и рассматриваются как машинная бесконечность. Все числа, по модулю меньшие X0
, для компьютера не отличаются от нуля и рассматриваются как машинный ноль. Машинным эпсилон εM
называется относительная точность ЭВМ, то есть граница относительной погрешности представления вещественных чисел. Можно показать, что εM ≈ 2-t
. Пусть
x* = m*2p
. Тогда граница абсолютной погрешности представления этого числа равна Δ(x*) ≈ 2-t-1*2p
. Поскольку 1/2≤m<1
, то величина относительной погрешности представления оценивается как
δ(x*) ≈ Δ(x*) / |x*| ≈ (2-t-1*2p) / (m*2p) = 2-t-1 / m ≤ 2-t-1 / 2-1 = 2-t
.
Машинное эпсилон определяется разрядностью мантиссы и способом округления чисел, реализованным на конкретной ЭВМ.
Примем следующие способы определения приближённых значений искомых величин:
- положим
X∞ = 2n
, гдеn
– первое натуральное число, при котором произошло переполнение; - положим
X0 = 2-m
, гдеm
– первое натуральное число , при котором2-m
совпадает с нулем; - положим
εM = 2-k
, гдеk
– наибольшее натуральное число, при котором сумма вычисленного значения1+2-k
ещё больше1
. Фактически,εM
есть граница относительной погрешности представления числаx* ≈ 1
.
Дальше задаём это в нужной среде (пакете) и подбираем значения параметров, вот пример для моего MathCAD 15:
машинный ноль, машинная бесконечность и машинный эпсилон в MathCAD 15
А вот что вышло в Visual Studio 2010 при использовании проекта Windows Forms, C++/CLI, библиотеки System::Math
и типа данных long double
:
Inf: 1024 Zero: 1075 Eps: 53
Код:
//Функции для подсчёта long double inf (int n) { return Math::Pow(2.,n); } long double zero (int m) { return Math::Pow(2.,-m); } long double eps (int k) { return 1.+Math::Pow(2.,-k); }
//... //Расчёт, сделанный по нажатию кнопки с выводом результатов в метку label1 label1->Text = ""; int n=1,m=1,k=1; long double res; while (1) { res=inf(n); if (res==Double::PositiveInfinity) break; else n++; }; label1->Text += "Inf: " + n + Environment::NewLine; while (1) { res=zero(m); if (res==0.) break; else m++; }; label1->Text += "Zero: " + m + Environment::NewLine; while (1) { res=eps(k); if (res==1.) break; else k++; }; label1->Text += "Eps: " + k + Environment::NewLine;
Ну и пара стандартных напоминаний напоследок:
К вещественным значениям в общем случае неприменима операция
==
(“сравнение”) из-за неточного представления этих значений в памяти компьютера. Поэтому для вещественных переменных отношение видаa==b
обычно заменяется наfabs(a-b)≤eps
, гдеfabs()
– функция вычисления модуля вещественного числа, аeps
– малая величина, определяющая допустимую погрешность.
Допустимую погрешность можно ввести в расчёт также через стандартный метод округления round, например, левый расчёт произведения чисел в MathCAD не даст нуля, а правый – да:
учёт погрешностей через метод round (Mathcad)
27.10.2015, 17:32 [26274 просмотра]
Текущая версия страницы пока не проверялась опытными участниками и может значительно отличаться от версии, проверенной 25 сентября 2019 года; проверки требуют 7 правок.
Представление машинного нуля в числах с плавающей запятой при двузначном порядке
Машинный ноль (Машинный нуль) — числовое значение с таким отрицательным порядком, которое воспринимается машиной как ноль[1].
Машинный эпсилон (англ. Machine epsilon) — числовое значение, меньше которого невозможно задавать относительную точность для любого алгоритма, возвращающего вещественные числа. Абсолютное значение «машинного эпсилон» зависит от разрядности сетки применяемой ЭВМ, типа (разрядности) используемых при расчетах чисел, и от принятой в конкретном трансляторе структуры представления вещественных чисел (количества бит, отводимых на мантиссу и на порядок).[2] Формально машинный эпсилон обычно определяют как минимальное из чисел ε, для которого 1+ε>1 при машинных расчетах с числами данного типа[3]. Альтернативное определение — максимальное ε, для которого справедливо равенство 1+ε=1.
Практическая важность машинного эпсилон связана с тем, что два (отличных от нуля) числа являются одинаковыми с точки зрения машинной арифметики, если их относительная разность по модулю меньше (при определении первого типа) или не превосходит (при определении второго типа) машинного эпсилон.
В языках программирования[править | править код]
Язык Си[править | править код]
В языке Си существуют предельные константы FLT_EPSILON, DBL_EPSILON и LDBL_EPSILON являющиеся «машинными эпсилон», соответствующими первому определению: FLT_EPSILON = 2−23 ≈ 1.19e-07 — это машинный эпсилон для чисел типа float (32 бита), DBL_EPSILON = 2−52 ≈ 2.20e-16 — для типа double (64 бита), и LDBL_EPSILON = 2−63 ≈ 1.08e-19 — для типа long double (80 бит). При альтернативном определении соответствующие машинные эпсилон будут вдвое меньше: 2−24 , 2−53 и 2−64 .В некоторых компиляторах Си (например gcc, Intel’s C/C++ compiler) допускается использование переменных четверной точности (_float128, _Quad). Соответствующие машинные эпсилон равны 2−112 ≈ 1.93e-34 и 2−113 ≈ 9.63e-35.
Пример[править | править код]
Пример вычисления машинного эпсилона (не путать с машинным нулём) на языке Си.
float macheps(void) { float e = 1.0f; while (1.0f + e / 2.0f > 1.0f) e /= 2.0f; return e; }
Пример на языке C++.
# include <iostream> # include <stdint.h> # include <iomanip> template<typename float_t, typename int_t> float_t machine_eps() { union { float_t f; int_t i; } one, one_plus, little, last_little; one.f = 1.0; little.f = 1.0; last_little.f = little.f; while(true) { one_plus.f = one.f; one_plus.f += little.f; if( one.i != one_plus.i ) { last_little.f = little.f; little.f /= 2.0; } else { return last_little.f; } } } int main() { std::cout << "machine epsilon:n"; std::cout << "float: " << std::setprecision(18)<< machine_eps<float, uint32_t>() << std::endl; std::cout << "double: " << std::setprecision(18) << machine_eps<double, uint64_t>() << std::endl; }
Пример на Python
def machineEpsilon(func=float): machine_epsilon = func(1) while func(1)+func(machine_epsilon) != func(1): machine_epsilon_last = machine_epsilon machine_epsilon = func(machine_epsilon) / func(2) return machine_epsilon_last
Вывод может быть таким (с использованием IPython):
In [1]: machineEpsilon(int) Out[1]: 1
In [2]: machineEpsilon(float) Out[2]: 2.2204460492503131e-16
In [3]: machineEpsilon(complex) Out[3]: (2.2204460492503131e-16+0j)
См. также[править | править код]
- −0 (программирование)
Примечания[править | править код]
- ↑ Численные методы. Линейная алгебра и нелинейные уравнения. Учебное пособие. — Directmedia, 2014-05-20. — 432 с. — ISBN 9785445838753. Архивная копия от 7 сентября 2021 на Wayback Machine
- ↑ Подбельский В. В., Фомин С. С. Программирование по на языке Си: Учеб.пособие. Москва: Изд-во Финансы и статистика, 2003.
- ↑ Игорь Юсупович Алибеков. Численные методы, У/П. — МГИУ, 2008-01-01. — 221 с. — ISBN 9785276014623. Архивная копия от 7 сентября 2021 на Wayback Machine
Эту задачу не часто спрашивают на собеседованиях, но знать человеку технической специальности об этом желательно, чтобы при случае поддержать высокоинтеллектуальный разговор.
О чём речь
С точки зрения математики, на любом отрезке находится бесконечное количество рациональных чисел, так как они располагаются всюду плотно (математический термин) на числовой оси. На любом компьютере, ввиду физических ограничений окружающего нас мира и особенностей конструкции вычислительной техники, есть ограничение на возможность представления всех этих чисел. Существует некая граница, все числа меньше которой, для машины неотличимы от 0 – это и есть машинный ноль.
Зачем его искать
На каждой машине он может быть разным и нигде в явном виде не написан. В некоторого типа вычислениях научного толка бывает важно знать значение машинного нуля, чтобы предусмотреть это в расчетах и избежать ошибок. Поэтому задача его поиска может возникнуть как естественным образом, так и поставлена во втором семестре первого года обучения в университете для развития навыков программирования.
Реализация
В интернете можно найти элегантные решения данной задачи, для данной статьи я же напишу такое, которое делал во времена своего студенчества. Просто делим число на 2 до того момента, пока оно не станет равно нулю.
Если смутило найденное число
Возможно раньше с этим не сталкивались, но число обычно настолько маленькое, что возвращается оно в форме экспоненциальной записи, которая как раз и придумана для записи как очень больших, так и очень маленьких числа. Результат, полученный в нашем эксперименте, можно представить как “5 умножить на 10 в минус 324 степени”.
Вот в общем-то и все, как Вы успели убедиться, задача на поиск машинного нуля совсем не сложная.
Программа вычисляет и выводит на экран значение машинного нуля m0 для типов float и double. Машинным нулем называется наименьшее положительное число, представимое в данном формате вещественных чисел. Поиск производить посредством многократного деления единицы до получения нужного значения.
Примечание: Все операции производятся в функции main(). Начиная с этого раздела, во всех дальнейших задачах необходимо самостоятельно обнаруживать ситуации с переполнением, делением на ноль и т.д. Также необходимо проверять корректность ввода. Это включает в себя не только проверку на соответствие типу данных, но и проверку условий исходя из условий задачи. Например, при воде длин сторон треугольника необходимо проверить что вводимые длины на не отрицательны и что сумма любых двух из них больше третьей.
Машинный нуль (англ. computer zero) — числовое значение, меньше которого невозможно задавать точность для любого алгоритма, возвращающего вещественные числа. Абсолютное значение “машинного нуля” зависит от разрядности сетки применяемой ЭВМ, от принятой в конкретном трансляторе точности представления вещественных чисел и от значений, используемых для оценки точности.
В языках программирования
Язык Си
В языке Си существуют предельные константы FLT_EPSILON и DBL_EPSILON называемые “машинными нулями” относительно вещественного значения 1.0. FLT_EPSILON – максимальное значение типа float и имеет значение 1E-5, DBL_EPSILON – максимальное значение типа double и имеет значение 1E-16. Сумма каждого из этих значений со значение 1.0 не отличается от 1.0.
Проблема машинного нуля в том, что два числа считаются одинаковыми, если они отличаются на величину, меньшую по модулю, чем машинный ноль.
При представлении чисел со знаком в обратных двоичных кодах существуют проблема[уточнить] наличия двух обратных кодов числа 0: «положительный нуль» и «отрицательный нуль».
Пример
Пример вычисления машинного эпсилона (не путать с машинным нулём) на языке Си.
#include <stdio.h> int main() { float e,e1; /* e1 - вспомогательная переменная */ int k=0; /* k - счетчик итераций */ e=1.0; do { e=e/2.0; e1=e+1.0; k++; } while (e1>1.0); printf("Число делений на 2: %dn",k); printf("Машинный эпсилон: %en",e); return 0; }
Пример на языке C++.
#include <iostream> #include <stdint.h> #include <iomanip> template<typename float_t, typename int_t> float_t machine_eps() { union { float_t f; int_t i; } one, one_plus, little, last_little; one.f = 1.0; little.f = 1.0; last_little.f = little.f; while(true) { one_plus.f = one.f; one_plus.f += little.f; if( one.i != one_plus.i ) { last_little.f = little.f; little.f /= 2.0; } else { return last_little.f; } } } int main() { std::cout << "machine epsilon:n"; std::cout << "float: " << std::setprecision(18)<< machine_eps<float, uint32_t>() << std::endl; std::cout << "double: " << std::setprecision(18) << machine_eps<double, uint64_t>() << std::endl; }
Пример на Python
def machineEpsilon(func=float): machine_epsilon = func(1) while func(1)+func(machine_epsilon) != func(1): machine_epsilon_last = machine_epsilon machine_epsilon = func(machine_epsilon) / func(2) return machine_epsilon_last
Вывод может быть таким (с использованием IPython):
In [1]: machineEpsilon(int) Out[1]: 1
In [2]: machineEpsilon(float) Out[2]: 2.2204460492503131e-16
In [3]: machineEpsilon(complex) Out[3]: (2.2204460492503131e-16+0j)
См. также
- −0 (программирование)