Por que a máquina numeric_limits Epsilon não satisfaz a condição 1+e>1?
Se eu não estiver errado a definição de Machine Epsilon é o menor número que satisfaz a condição:
Eu estava tentando testar isso usando o std::numeric_limits<float>::epsilon()
mas o valor não satisfaz isso, se você tentar pegar o número float anterior com std::nextafter
:
#include <cmath>
#include <iostream>
#include <limits>
int main() {
float e = std::numeric_limits<float>::epsilon();
float previous = std::nextafter(e, -std::numeric_limits<float>::infinity());
std::cout << std::boolalpha << ((1.0f + previous) > 1.0f) << std::endl;
return 0;
}
Isso ainda produz saídastrue
https://coliru.stacked-crooked.com/a/841e19dafcf0bf6f.
Depois de tentar obter o número usando std::nextafter
, notei que o Machine Epsilon adequado deveria ser:
std::nextafter(std::numeric_limits<float>::epsilon() / 2.0f, std::numeric_limits<float>::infinity())
Eu testei usando este código:
#include <cmath>
#include <iostream>
#include <limits>
bool verify(float e) {
return ((1.0f + e) > 1.0f);
}
int main() {
std::cout.precision(std::numeric_limits<float>::digits);
std::cout << std::boolalpha << std::fixed;
float epsilon = std::numeric_limits<float>::epsilon();
float last = epsilon;
while (true) {
last = std::nextafter(last, -std::numeric_limits<float>::infinity());
if ((1.0f + last) > 1.0f) {
epsilon = last;
} else {
break;
}
}
// Does not satisfy condition
std::cout << "last: " << verify(last) << " " << last << std::endl;
// Satisfy condition
std::cout << "epsilon: " << verify(epsilon) << " " << epsilon << std::endl;
float half_epsilon = std::numeric_limits<float>::epsilon() / 2.0f;
float actual_epsilon = std::nextafter(half_epsilon, std::numeric_limits<float>::infinity());
// Same as 'last' at this point
std::cout << "half_epsilon: " << verify(half_epsilon) << " " << half_epsilon << std::endl;
// Same as 'epsilon' at this point
std::cout << "actual_epsilon: " << verify(actual_epsilon) << " " << actual_epsilon << std::endl;
return 0;
}
Isso produz
last: false 0.000000059604644775390625
epsilon: true 0.000000059604651880817983
half_epsilon: false 0.000000059604644775390625
actual_epsilon: true 0.000000059604651880817983
https://coliru.stacked-crooked.com/a/3c66a2144e80a91b
Estou perdendo alguma coisa aqui?
Respostas
Se não estou errado a definição da Máquina Epsilon é o menor número que satisfaça a condição: [
1 + epsilon > 1
]
Perto, mas você está errado no contexto de C++. (Acredito que sua definição esteja correta em outros contextos mais acadêmicos.) De acordo com cppreference.com , a máquina epsilon é "a diferença entre 1.0
e o próximo valor representável pelo tipo de ponto flutuante [especificado]". O épsilon da máquina satisfaz 1 + epsilon > 1
, mas não precisa ser o número mais baixo que satisfaça isso. É, no entanto, o número mais baixo que satisfaz essa condição em todos os modos de arredondamento .
Como o epsilon da máquina é muito menor que 1.0
, há muitos valores representáveis entre o epsilon e 0.0
. (Esse é um objetivo básico das representações de ponto flutuante.) Quando qualquer um deles é adicionado a 1.0
, o resultado não é representável, portanto, o resultado precisa ser arredondado. Se o modo de arredondamento for para o valor representável mais próximo, essa soma será arredondada para 1 + epsilon
sempre que o número pequeno estiver entre epsilon/2
e 3*epsilon/2
. Por outro lado, se o modo de arredondamento for sempre em direção a zero, você obterá o resultado que esperava.
Tente adicionar #include <cfenv>
e a seguinte linha ao seu código.
fesetround(FE_TOWARDZERO);
Isso faz com que qualquer soma estritamente entre 1.0
e 1 + epsilon
seja arredondada para 1.0
. Agora você deve ver a máquina epsilon se comportando como você esperava.
Os outros modos de arredondamento garantidos são para -infinito e para +infinito. Consulte cppreference.com para obter detalhes.