Я хотел рассчитать на машине Эпсилон, наименьшее возможное число e
это дает 1 + e > 1
используя разные типы данных C ++: float
, double
а также long double
,
Вот мой код:
#include <cstdio>
template<typename T>
T machineeps() {
T epsilon = 1;
T expression;
do {
epsilon = epsilon / 2;
expression = 1 + epsilon;
} while(expression > 1);
return epsilon;
}
int main() {
auto epsf = machineeps<float>();
auto epsd = machineeps<double>();
auto epsld = machineeps<long double>();
std::printf("epsilon float: %22.17e\nepsilon double: %22.17e\nepsilon long double: %Le\n", epsf, epsd, epsld);
return 0;
}
Но я получаю этот странный вывод:
epsilon float: 5.96046447753906250e-008
epsilon double: 1.11022302462515650e-016
epsilon long double: -0.000000e+000
Значения для float
а также double
это то, что я ожидал, но я не могу объяснить long double
поведение.
Может кто-нибудь сказать мне, что пошло не так?
Я не могу воспроизвести ваши результаты. Я получил:
эпсилон длинный двойной: 5.421011e-20
В любом случае, по логике, код должен выглядеть примерно так:
template<typename T>
T machineeps() {
T epsilon = 1, prev;
T expression;
do {
prev = epsilon;
epsilon = epsilon / 2;
expression = 1 + epsilon;
} while (expression > 1);
return prev; // <-- `1+prev` yields a result different from one
}
На моей платформе это производит значения, подобные std::numeric_limits::epsilon
:
epsilon float: 1.19209289550781250e-07
двойной эпсилон: 2.22044604925031308e-16
эпсилон длинный двойной: 1.084202e-19
(обратите внимание на другой порядок величины)
Здесь происходит несколько вещей.
Во-первых, математика с плавающей точкой часто выполняется с максимально доступной точностью, независимо от фактического объявленного типа переменной с плавающей точкой. Так, например, арифметика на float
s обычно выполняется с точностью 80 бит на оборудовании Intel (Java изначально запретил это, требуя, чтобы вся математика с плавающей точкой выполнялась с точной точностью типа; это убивало производительность с плавающей точкой, и они быстро отказались от этого правила). Предполагается, что при сохранении результата вычисления с плавающей запятой значение будет усечено до соответствующего типа, но по умолчанию большинство компиляторов игнорируют это. Вы можете сказать своему компилятору не допускать этого; переключатель для этого зависит от компилятора. Как и вы, вы не можете рассчитывать на результат, который рассчитывается здесь.
Во-вторых, цикл в коде заканчивается, когда значение 1 + epsilon
является не больше 1, поэтому возвращаемое значение будет меньше, чем истинное значение эпсилона.
В-третьих, в сочетании со вторым некоторые реализации с плавающей запятой не имеют субнормальных значений; как только показатель степени станет меньше, чем наименьшее из представленных значений, значение будет равно 0. Это может быть тем, что вы видите здесь с long double
значение. IEEE с плавающей точкой обрабатывает нули менее резко — как только вы достигнете минимального показателя, меньшие значения постепенно теряют точность. Существует довольно много значений между наименьшим нормированным значением и 0.