Por que a máquina numeric_limits Epsilon não satisfaz a condição 1+e>1?

Dec 24 2020

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

6 JaMiT Dec 24 2020 at 11:46

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.0e 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 + epsilonsempre que o número pequeno estiver entre epsilon/2e 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.0e 1 + epsilonseja 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.