Как найти машинный ноль

Определяем машинный ноль, машинную бесконечность и машинный эпсилон

Доверять расчёту, сделанному на компьютере, без тени понимания того, как именно выполнен этот расчёт – одна из худший вещей, которые может допустить в своей работе инженер. К сожалению, уже нередки “специалисты”, которых не смущает ненулевой результат, полученный при умножении на ноль, или, напротив, ноль там, где теоретически нуля быть не должно.

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

В 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

машинный ноль, машинная бесконечность и машинный эпсилон в 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)

учёт погрешностей через метод 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 (программирование)

Примечания[править | править код]

  1. Численные методы. Линейная алгебра и нелинейные уравнения. Учебное пособие. — Directmedia, 2014-05-20. — 432 с. — ISBN 9785445838753. Архивная копия от 7 сентября 2021 на Wayback Machine
  2. Подбельский В. В., Фомин С. С. Программирование по на языке Си: Учеб.пособие. Москва: Изд-во Финансы и статистика, 2003.
  3. Игорь Юсупович Алибеков. Численные методы, У/П. — МГИУ, 2008-01-01. — 221 с. — ISBN 9785276014623. Архивная копия от 7 сентября 2021 на Wayback Machine

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

самооценка, после того как нашёл машинный ноль
самооценка, после того как нашёл машинный ноль

О чём речь

С точки зрения математики, на любом отрезке находится бесконечное количество рациональных чисел, так как они располагаются всюду плотно (математический термин) на числовой оси. На любом компьютере, ввиду физических ограничений окружающего нас мира и особенностей конструкции вычислительной техники, есть ограничение на возможность представления всех этих чисел. Существует некая граница, все числа меньше которой, для машины неотличимы от 0 – это и есть машинный ноль.

числа тут для примера, у всех разные значения
числа тут для примера, у всех разные значения

Зачем его искать

На каждой машине он может быть разным и нигде в явном виде не написан. В некоторого типа вычислениях научного толка бывает важно знать значение машинного нуля, чтобы предусмотреть это в расчетах и избежать ошибок. Поэтому задача его поиска может возникнуть как естественным образом, так и поставлена во втором семестре первого года обучения в университете для развития навыков программирования.

Реализация

В интернете можно найти элегантные решения данной задачи, для данной статьи я же напишу такое, которое делал во времена своего студенчества. Просто делим число на 2 до того момента, пока оно не станет равно нулю.

элементарная реализация на Python приблизительного поиска машинного нуля
элементарная реализация на Python приблизительного поиска машинного нуля

Если смутило найденное число

Возможно раньше с этим не сталкивались, но число обычно настолько маленькое, что возвращается оно в форме экспоненциальной записи, которая как раз и придумана для записи как очень больших, так и очень маленьких числа. Результат, полученный в нашем эксперименте, можно представить как “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 (программирование)

Добавить комментарий