¿Por qué numeric_limits Machine Epsilon no satisface la condición 1+e>1?
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
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.0
y 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 + epsilon
siempre que el número pequeño esté entre epsilon/2
y 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.0
y 1 + epsilon
se 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.