¿Por qué numeric_limits Machine Epsilon no satisface la condición 1+e>1?

Dec 24 2020

Si no me equivoco, la definición de Machine Epsilon es el número más bajo que satisface la condición:

Estaba tratando de probar esto haciendo uso de std::numeric_limits<float>::epsilon()pero el valor no satisface esto, si intenta obtener el número flotante anterior con 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;
}

Esto todavía producetrue https://coliru.stacked-crooked.com/a/841e19dafcf0bf6f.

Después de intentar obtener el número usando std::nextafter, noté que la Máquina Epsilon adecuada debería ser:

std::nextafter(std::numeric_limits<float>::epsilon() / 2.0f, std::numeric_limits<float>::infinity())

Lo probé 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;
}

Esto produce

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

¿Me estoy perdiendo algo aquí?

Respuestas

6 JaMiT Dec 24 2020 at 11:46

Si no me equivoco, la definición de Machine Epsilon es el número más bajo que satisface la condición: [ 1 + epsilon > 1]

Cierra, pero te equivocas en el contexto de C++. (Creo que su definición es correcta en otros contextos más académicos). Según cppreference.com , el épsilon de la máquina es "la diferencia entre 1.0y el siguiente valor representable por el tipo de punto flotante [especificado]". La máquina épsilon satisface 1 + epsilon > 1, pero no es necesario que sea el número más bajo el que lo satisfaga. Sin embargo, es el número más bajo que satisface esa condición en todos los modos de redondeo .

Debido a que la máquina épsilon es mucho más pequeña que 1.0, hay muchos valores representables entre épsilon y 0.0. (Ese es un objetivo básico de las representaciones de coma flotante). Cuando cualquiera de estos se agrega a 1.0, el resultado no se puede representar, por lo que el resultado debe redondearse. Si el modo de redondeo es al valor representable más cercano, entonces esa suma se redondeará 1 + epsilonsiempre que el número pequeño esté entre epsilon/2y 3*epsilon/2. Por otro lado, si el modo de redondeo es siempre hacia cero, obtienes el resultado que esperabas.

Intente agregar #include <cfenv>y la siguiente línea a su código.

fesetround(FE_TOWARDZERO);

Esto hace que cualquier suma estrictamente entre 1.0y 1 + epsilonse redondee a 1.0. Ahora debería ver que la máquina épsilon se comporta como esperaba.

Los otros modos de redondeo garantizados son hacia -infinito y hacia +infinito. Visite cppreference.com para obtener más detalles.