Me meto en una situación en la que calcular 1.77e-308/10
desencadena una excepción de subdesbordamiento, pero calculando 1.777e-308/10
no. Esto es extraño porque:
El subdesbordamiento ocurre cuando el verdadero resultado de una operación de punto flotante es de menor magnitud (es decir, más cercano a cero) que el valor más pequeño representable como un número de punto flotante normal en el tipo de datos de destino (de Arithmetic Underflow, Wikipedia)
En otras palabras, si calculamos x/y
donde ambos x
y y
están double
entonces debe ocurrir subdesbordamiento si 0 < |x/y| < 2.2251e-308
(el menor positivo normalizado double
es 2.2251e-308
). En teoría, por lo tanto, ambos 1.77e-308/10
y 1.777e-308/10
debería desencadenar una excepción de subdesbordamiento. La teoría contradice lo que he probado con el programa C a continuación.
#include <stdio.h>
#include <fenv.h>
#include <math.h>
int main(){
double x,y;
// x = 1.77e-308 => underflow
// x = 1.777e-308 gives ==> no underflow
x=1.77e-308;
feclearexcept(FE_ALL_EXCEPT);
y=x/10.0;
if (fetestexcept(FE_UNDERFLOW)) {
puts("Underflow\n");
}
else puts("No underflow\n");
}
Para compilar el programa, usé gcc program.c -lm
; También probé Clang, que me dio el mismo resultado. ¿Alguna explicación?
[Edits] He compartido el código anterior a través de este IDE en línea.
Underflow no es solo una cuestión de rango, sino también de precisión/redondeo.
7.12.1 Tratamiento de las condiciones de error
El resultado se desborda si la magnitud del resultado matemático es tan pequeña que el resultado matemático no se puede representar, sin un error de redondeo extraordinario, en un objeto del tipo especificado. C11 §7.12.1 6
1.777e-308, convertido al más cercano binario64 0x1.98e566222bcfcp-1023, tiene un significado (0x198E566222BCFC, 7193376082541820) que es un múltiplo de 10. Por lo tanto, dividir por 10 es exacto. Sin error de redondeo.
Encuentro esto más fácil de demostrar con notación hexadecimal. Tenga en cuenta que dividir por 2 siempre es exacto, excepto por el valor más pequeño.
#include <float.h>
#include <stdio.h>
#include <fenv.h>
#include <math.h>
int uf_test(double x, double denominator){
printf("%.17e %24a ", x, x);
feclearexcept(FE_ALL_EXCEPT);
double y=x/denominator;
int uf = !!fetestexcept(FE_UNDERFLOW);
printf("%-24a %s\n", y, uf ? "Underflow" : "");
return uf;
}
int main(void) {
uf_test(DBL_MIN, 2.0);
uf_test(1.777e-308, 2.0);
uf_test(1.77e-308, 2.0);
uf_test(DBL_TRUE_MIN, 2.0);
uf_test(pow(2.0, -1000), 10.0);
uf_test(DBL_MIN, 10.0);
uf_test(1.777e-308, 10.0);
uf_test(1.77e-308, 10.0);
uf_test(DBL_TRUE_MIN, 10.0);
return 0;
}
Producción
2.22507385850720138e-308 0x1p-1022 0x1p-1023
1.77700000000000015e-308 0x1.98e566222bcfcp-1023 0x1.98e566222bcfcp-1024
1.77000000000000003e-308 0x1.97490d21e478cp-1023 0x1.97490d21e478cp-1024
4.94065645841246544e-324 0x1p-1074 0x0p+0 Underflow
// No underflow as inexact result is not too small
9.33263618503218879e-302 0x1p-1000 0x1.999999999999ap-1004
// Underflow as result is too small and inexact
2.22507385850720138e-308 0x1p-1022 0x1.99999999999ap-1026 Underflow
// No underflow as result is exact
1.77700000000000015e-308 0x1.98e566222bcfcp-1023 0x1.471deb4e8973p-1026
1.77000000000000003e-308 0x1.97490d21e478cp-1023 0x1.45d40a818394p-1026 Underflow
4.94065645841246544e-324 0x1p-1074 0x0p+0 Underflow
Verificar la documentación de la función que llamó, conduce a la definición:
FE_UNDERFLOW el resultado de una operación de punto flotante anterior fue subnormal con una pérdida de precisión
http://en.cppreference.com/w/c/numeric/fenv/FE_exceptions
Has verificado que tu número es subnormal, creo. La prueba también incluye pérdida de precisión. Si imprime más cifras significativas, encontrará que el que informa el desbordamiento parece perder precisión en alrededor de 16 decimales. No tengo claro cuántas cifras significativas esperar en un número subnormal, pero creo que esta debe ser su respuesta.
¿Puedes mostrar tu valor?
– Adán
16 de febrero de 2017 a las 14:53
¿Cómo determinaste el doble normalizado más pequeño en tu máquina?
– Ladrillo
16 de febrero de 2017 a las 14:58
En mi plataforma es todo lo contrario:
1.77e-308
activa un subdesbordamiento mientras que 1.777e-308;` no lo hace.g++ (Debian 4.9.2-10) 4.9.2
– LP
16 de febrero de 2017 a las 15:02
@Brick Determiné el doble normalizado más pequeño a través de std::numeric_limits::min() (con un programa C++ separado).
– entusiasmo
16 de febrero de 2017 a las 15:06
El texto en la pregunta y los comentarios en el código no coinciden en cuanto a cuál da el subdesbordamiento y cuál no. Sospecho que los comentarios del código son correctos, y eso coincidiría con lo que informa @LPs.
– Ladrillo
16 de febrero de 2017 a las 15:20