¿Cuándo ocurre el subdesbordamiento?

4 minutos de lectura

avatar de usuario
zell

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 doubleentonces 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.

  • ¿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

avatar de usuario
chux – Reincorporar a Monica

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

  • Excelente explicación! Gracias.

    – entusiasmo

    16 de febrero de 2017 a las 16:48

  • Tal vez me estoy perdiendo algo obvio, pero ¿por qué la doble negación delante de fetestexcept()?

    – ad absurdum

    16 de febrero de 2017 a las 16:58

  • @DavidBowling fetestexcept() devuelve un int: “el valor del OR bit a bit”. Desde uf_test() solo quería devolver 0 o 1, así que usé !!, pero luego no usó ese resultado en este código publicado. Mejor código habría usado bool uf = fetestexcept(FE_UNDERFLOW); para lograr el mismo propósito.

    – chux – Reincorporar a Monica

    16 de febrero de 2017 a las 17:48


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.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad