¿Qué es un número de coma flotante subnormal?

82

La página de referencia isnormal () dice:

Determina si el número de punto flotante dado arg es normal, es decir, no es cero, subnormal, infinito ni NaN.

Un número que sea cero, infinito o NaN es claro lo que significa. Pero también dice subnormal. ¿Cuándo es un número subnormal?

BЈовић
fuente
2
El primer resultado de Google muestra que es solo un sinónimo de un denormal: en.wikipedia.org/wiki/Denormal_number
tenfour
10
Y, sin embargo, ahora el segundo éxito en Google (buscando "punto flotante subnormal") es esta pregunta en sí.
Slipp D. Thompson
Vea esta pregunta para una discusión en profundidad de los desnormales y cómo lidiar con ellos: stackoverflow.com/questions/9314534/…
fig.

Respuestas:

79

En el estándar IEEE754, los números de coma flotante se representan como notación científica binaria, x  =  M  × 2 e . Aquí M es la mantisa y e es el exponente . Matemáticamente, siempre puede elegir el exponente de modo que 1 ≤  M  <2. * Sin embargo, dado que en la representación por computadora el exponente solo puede tener un rango finito, hay algunos números que son mayores que cero, pero menores que 1.0 × 2 e min . Esos números son los subnormales o desnormales .

Prácticamente, la mantisa se almacena sin el 1 inicial, ya que siempre hay un 1 inicial , excepto para los números subnormales (y cero). Por lo tanto, la interpretación es que si el exponente no es mínimo, hay un 1 a la izquierda implícito, y si el exponente es mínimo, no lo hay y el número es subnormal.

*) De manera más general, 1 ≤  M  <  B   para cualquier notación científica base- B .

Kerrek SB
fuente
¿Estás diciendo isnomales truesi los 8 bits son todos cero, y falsede otra manera?
Pacerier
'almacenado' o interpretado?
Pacerier
@Pacerier: "almacenado": se almacena sin el 1 inicial, por ejemplo 001010, como , y se interpreta como 1.001010.
Kerrek SB
¿Es obvio cuál es el emin mencionado en: `` `e <sub> min </sub>? `` (Espero que mi intento de formateo funcione) ..
Razzle
85

Conceptos básicos de IEEE 754

Primero repasemos los conceptos básicos de los números IEEE 754 organizados.

Nos centraremos en la precisión simple (32 bits), pero todo se puede generalizar inmediatamente a otras precisiones.

El formato es:

  • 1 bit: signo
  • 8 bits: exponente
  • 23 bits: fracción

O si te gustan las fotos:

ingrese la descripción de la imagen aquí

Fuente .

El signo es simple: 0 es positivo y 1 es negativo, final de la historia.

El exponente tiene 8 bits de longitud, por lo que varía de 0 a 255.

El exponente se llama sesgado porque tiene un desplazamiento de -127, por ejemplo:

  0 == special case: zero or subnormal, explained below
  1 == 2 ^ -126
    ...
125 == 2 ^ -2
126 == 2 ^ -1
127 == 2 ^  0
128 == 2 ^  1
129 == 2 ^  2
    ...
254 == 2 ^ 127
255 == special case: infinity and NaN

La convención de bits líder

(Lo que sigue es una narración hipotética ficticia, no basada en ninguna investigación histórica real).

Al diseñar IEEE 754, los ingenieros notaron que todos los números, excepto 0.0, tienen un uno 1en binario como primer dígito. P.ej:

25.0   == (binary) 11001 == 1.1001 * 2^4
 0.625 == (binary) 0.101 == 1.01   * 2^-1

ambos comienzan con esa 1.parte molesta .

Por lo tanto, sería un desperdicio dejar que ese dígito ocupe un bit de precisión en casi todos los números.

Por esta razón, crearon la "convención de bits inicial":

siempre asume que el número comienza con uno

Pero entonces, ¿cómo lidiar con eso 0.0? Bueno, decidieron crear una excepción:

  • si el exponente es 0
  • y la fracción es 0
  • entonces el número representa más o menos 0.0

para que los bytes 00 00 00 00también representen 0.0, lo que se ve bien.

Si solo consideráramos estas reglas, entonces el número más pequeño distinto de cero que se puede representar sería:

  • exponente: 0
  • fracción: 1

que se parece a esto en una fracción hexadecimal debido a la convención de bits inicial:

1.000002 * 2 ^ (-127)

donde .000002es 22 ceros con un 1al final.

No podemos tomar fraction = 0, de lo contrario ese número sería 0.0.

Pero luego los ingenieros, que también tenían un agudo sentido estético, pensaron: ¿no es eso feo? ¿Que saltemos de directo 0.0a algo que ni siquiera es una potencia propia de 2? ¿No podríamos representar números aún más pequeños de alguna manera? (De acuerdo, era un poco más preocupante que "feo": en realidad, la gente estaba obteniendo malos resultados en sus cálculos, consulte "Cómo los subnormales mejoran los cálculos" a continuación).

Números subnormales

Los ingenieros se rascaron la cabeza un rato y volvieron, como de costumbre, con otra buena idea. ¿Qué pasa si creamos una nueva regla?

Si el exponente es 0, entonces:

  • el bit inicial se convierte en 0
  • el exponente se fija en -126 (no -127 como si no tuviéramos esta excepción)

Estos números se denominan números subnormales (o números desnormales que es sinónimo).

Esta regla implica inmediatamente que el número tal que:

  • exponente: 0
  • fracción: 0

es todavía 0.0, lo cual es un poco elegante, ya que significa una regla menos de la que realizar un seguimiento.

¡Así 0.0que en realidad es un número subnormal según nuestra definición!

Entonces, con esta nueva regla, el número no anormal más pequeño es:

  • exponente: 1 (0 sería subnormal)
  • fracción: 0

que representa:

1.0 * 2 ^ (-126)

Entonces, el número subnormal más grande es:

  • exponente: 0
  • fracción: 0x7FFFFF (23 bits 1)

que es igual a:

0.FFFFFE * 2 ^ (-126)

donde .FFFFFEestá una vez más 23 bits uno a la derecha del punto.

Esto está bastante cerca del número no subnormal más pequeño, lo que suena cuerdo.

Y el número subnormal distinto de cero más pequeño es:

  • exponente: 0
  • fracción: 1

que es igual a:

0.000002 * 2 ^ (-126)

que también se parece bastante a 0.0!

Incapaces de encontrar una forma sensata de representar números más pequeños que eso, los ingenieros estaban felices y volvieron a ver imágenes de gatos en línea, o lo que sea que hicieran en los 70.

Como puede ver, los números subnormales hacen un intercambio entre precisión y longitud de representación.

Como ejemplo más extremo, el subnormal distinto de cero más pequeño:

0.000002 * 2 ^ (-126)

tiene esencialmente una precisión de un solo bit en lugar de 32 bits. Por ejemplo, si lo dividimos entre dos:

0.000002 * 2 ^ (-126) / 2

en realidad llegamos 0.0exactamente!

Visualización

Siempre es una buena idea tener una intuición geométrica sobre lo que aprendemos, así que aquí va.

Si trazamos números de coma flotante IEEE 754 en una línea para cada exponente dado, se verá así:

          +---+-------+---------------+-------------------------------+
exponent  |126|  127  |      128      |              129              |
          +---+-------+---------------+-------------------------------+
          |   |       |               |                               |
          v   v       v               v                               v
          -------------------------------------------------------------
floats    ***** * * * *   *   *   *   *       *       *       *       *
          -------------------------------------------------------------
          ^   ^       ^               ^                               ^
          |   |       |               |                               |
          0.5 1.0     2.0             4.0                             8.0

De eso podemos ver que:

  • para cada exponente, no hay superposición entre los números representados
  • para cada exponente, tenemos el mismo número 2 ^ 32 de números (aquí representado por 4 * )
  • dentro de cada exponente, los puntos están igualmente espaciados
  • exponentes más grandes cubren rangos más grandes, pero con puntos más dispersos

Ahora, bajemos eso hasta el exponente 0.

Sin subnormales, hipotéticamente se vería así:

          +---+---+-------+---------------+-------------------------------+
exponent  | ? | 0 |   1   |       2       |               3               |
          +---+---+-------+---------------+-------------------------------+
          |   |   |       |               |                               |
          v   v   v       v               v                               v
          -----------------------------------------------------------------
floats    *    **** * * * *   *   *   *   *       *       *       *       *
          -----------------------------------------------------------------
          ^   ^   ^       ^               ^                               ^
          |   |   |       |               |                               |
          0   |   2^-126  2^-125          2^-124                          2^-123
              |
              2^-127

Con subnormales, se ve así:

          +-------+-------+---------------+-------------------------------+
exponent  |   0   |   1   |       2       |               3               |
          +-------+-------+---------------+-------------------------------+
          |       |       |               |                               |
          v       v       v               v                               v
          -----------------------------------------------------------------
floats    * * * * * * * * *   *   *   *   *       *       *       *       *
          -----------------------------------------------------------------
          ^   ^   ^       ^               ^                               ^
          |   |   |       |               |                               |
          0   |   2^-126  2^-125          2^-124                          2^-123
              |
              2^-127

Al comparar los dos gráficos, vemos que:

  • subnormales duplican la longitud del rango del exponente 0, de [2^-127, 2^-126)a[0, 2^-126)

    El espacio entre flotadores en rango subnormal es el mismo que para [0, 2^-126).

  • el rango [2^-127, 2^-126)tiene la mitad del número de puntos que tendría sin subnormales.

    La mitad de esos puntos se destina a llenar la otra mitad del rango.

  • el rango [0, 2^-127)tiene algunos puntos con subnormales, pero ninguno sin ellos.

    Esta falta de puntos [0, 2^-127)no es muy elegante y es la principal razón por la que existen los subnormales.

  • dado que los puntos están igualmente espaciados:

    • el rango [2^-128, 2^-127)tiene la mitad de puntos que [2^-127, 2^-126) - [2^-129, 2^-128)tiene la mitad de puntos que[2^-128, 2^-127)
    • y así

    Esto es lo que queremos decir cuando decimos que los subnormales son un compromiso entre tamaño y precisión.

Ejemplo de C ejecutable

Ahora juguemos con un código real para verificar nuestra teoría.

En casi todas las máquinas actuales y de escritorio, C floatrepresenta números de punto flotante IEEE 754 de precisión simple.

Este es en particular el caso de mi computadora portátil Ubuntu 18.04 amd64 Lenovo P51.

Con esa suposición, todas las afirmaciones pasan por el siguiente programa:

subnormal.c

#if __STDC_VERSION__ < 201112L
#error C11 required
#endif

#ifndef __STDC_IEC_559__
#error IEEE 754 not implemented
#endif

#include <assert.h>
#include <float.h> /* FLT_HAS_SUBNORM */
#include <inttypes.h>
#include <math.h> /* isnormal */
#include <stdlib.h>
#include <stdio.h>

#if FLT_HAS_SUBNORM != 1
#error float does not have subnormal numbers
#endif

typedef struct {
    uint32_t sign, exponent, fraction;
} Float32;

Float32 float32_from_float(float f) {
    uint32_t bytes;
    Float32 float32;
    bytes = *(uint32_t*)&f;
    float32.fraction = bytes & 0x007FFFFF;
    bytes >>= 23;
    float32.exponent = bytes & 0x000000FF;
    bytes >>= 8;
    float32.sign = bytes & 0x000000001;
    bytes >>= 1;
    return float32;
}

float float_from_bytes(
    uint32_t sign,
    uint32_t exponent,
    uint32_t fraction
) {
    uint32_t bytes;
    bytes = 0;
    bytes |= sign;
    bytes <<= 8;
    bytes |= exponent;
    bytes <<= 23;
    bytes |= fraction;
    return *(float*)&bytes;
}

int float32_equal(
    float f,
    uint32_t sign,
    uint32_t exponent,
    uint32_t fraction
) {
    Float32 float32;
    float32 = float32_from_float(f);
    return
        (float32.sign     == sign) &&
        (float32.exponent == exponent) &&
        (float32.fraction == fraction)
    ;
}

void float32_print(float f) {
    Float32 float32 = float32_from_float(f);
    printf(
        "%" PRIu32 " %" PRIu32 " %" PRIu32 "\n",
        float32.sign, float32.exponent, float32.fraction
    );
}

int main(void) {
    /* Basic examples. */
    assert(float32_equal(0.5f, 0, 126, 0));
    assert(float32_equal(1.0f, 0, 127, 0));
    assert(float32_equal(2.0f, 0, 128, 0));
    assert(isnormal(0.5f));
    assert(isnormal(1.0f));
    assert(isnormal(2.0f));

    /* Quick review of C hex floating point literals. */
    assert(0.5f == 0x1.0p-1f);
    assert(1.0f == 0x1.0p0f);
    assert(2.0f == 0x1.0p1f);

    /* Sign bit. */
    assert(float32_equal(-0.5f, 1, 126, 0));
    assert(float32_equal(-1.0f, 1, 127, 0));
    assert(float32_equal(-2.0f, 1, 128, 0));
    assert(isnormal(-0.5f));
    assert(isnormal(-1.0f));
    assert(isnormal(-2.0f));

    /* The special case of 0.0 and -0.0. */
    assert(float32_equal( 0.0f, 0, 0, 0));
    assert(float32_equal(-0.0f, 1, 0, 0));
    assert(!isnormal( 0.0f));
    assert(!isnormal(-0.0f));
    assert(0.0f == -0.0f);

    /* ANSI C defines FLT_MIN as the smallest non-subnormal number. */
    assert(FLT_MIN == 0x1.0p-126f);
    assert(float32_equal(FLT_MIN, 0, 1, 0));
    assert(isnormal(FLT_MIN));

    /* The largest subnormal number. */
    float largest_subnormal = float_from_bytes(0, 0, 0x7FFFFF);
    assert(largest_subnormal == 0x0.FFFFFEp-126f);
    assert(largest_subnormal < FLT_MIN);
    assert(!isnormal(largest_subnormal));

    /* The smallest non-zero subnormal number. */
    float smallest_subnormal = float_from_bytes(0, 0, 1);
    assert(smallest_subnormal == 0x0.000002p-126f);
    assert(0.0f < smallest_subnormal);
    assert(!isnormal(smallest_subnormal));

    return EXIT_SUCCESS;
}

GitHub aguas arriba .

Compilar y ejecutar con:

gcc -ggdb3 -O0 -std=c11 -Wall -Wextra -Wpedantic -Werror -o subnormal.out subnormal.c
./subnormal.out

C ++

Además de exponer todas las API de C, C ++ también expone algunas funciones extranormales relacionadas que no están tan fácilmente disponibles en C en <limits>, por ejemplo:

  • denorm_min: Devuelve el valor subnormal positivo mínimo del tipo T

En C ++, toda la API está diseñada para cada tipo de punto flotante, y es mucho mejor.

Implementaciones

x86_64 y ARMv8 implementan IEEE 754 directamente en el hardware, al que se traduce el código C.

Los subnormales parecen ser menos rápidos que los normales en ciertas implementaciones: ¿Por qué cambiar 0.1f a 0 ralentiza el rendimiento en 10 veces? Esto se menciona en el manual de ARM, consulte la sección "Detalles de ARMv8" de esta respuesta.

Detalles ARMv8

ARM Architecture Reference Manual ARMv8 DDI 0487C. Un manual A1.5.4 "Flush-to-zero" describe un modo configurable donde los subnormales se redondean a cero para mejorar el rendimiento:

El rendimiento del procesamiento de punto flotante se puede reducir cuando se realizan cálculos con números desnormalizados y excepciones de subdesbordamiento. En muchos algoritmos, este rendimiento se puede recuperar, sin afectar significativamente la precisión del resultado final, reemplazando los operandos desnormalizados y los resultados intermedios con ceros. Para permitir esta optimización, las implementaciones de punto flotante ARM permiten utilizar un modo Flush-to-zero para diferentes formatos de punto flotante de la siguiente manera:

  • Para AArch64:

    • Si FPCR.FZ==1, entonces el modo Flush-to-Zero se usa para todas las entradas y salidas de precisión simple y precisión doble de todas las instrucciones.

    • Si FPCR.FZ16==1, entonces el modo Flush-to-Zero se usa para todas las entradas y salidas de Half-Precision de instrucciones de punto flotante, excepto: —Conversiones entre números de Half-Precision y Single-Precision. — Conversiones entre Half-Precision y Double-Precision números.

A1.5.2 "Normas y terminología de coma flotante" La Tabla A1-3 "Terminología de coma flotante" confirma que los subnormales y los desnormales son sinónimos:

This manual                 IEEE 754-2008
-------------------------   -------------
[...]
Denormal, or denormalized   Subnormal

C5.2.7 "FPCR, registro de control de punto flotante" describe cómo ARMv8 puede generar excepciones o establecer bits de bandera cuando la entrada de una operación de punto flotante es subnormal:

FPCR.IDE, bit [15] Entrada Habilitación de excepción de excepción de coma flotante denormal. Los posibles valores son:

  • 0b0 Manejo de excepciones no envuelto seleccionado. Si se produce la excepción de punto flotante, el bit FPSR.IDC se establece en 1.

  • 0b1 Manejo de excepciones atrapadas seleccionado. Si ocurre la excepción de punto flotante, el PE no actualiza el bit FPSR.IDC. El software de manejo de capturas puede decidir si establecer el bit FPSR.IDC en 1.

D12.2.88 "MVFR1_EL1, AArch32 Media and VFP Feature Register 1" muestra que el soporte desnormal es completamente opcional, de hecho, y ofrece un poco para detectar si hay soporte:

FPFtZ, bits [3: 0]

Enjuague a modo cero. Indica si la implementación de punto flotante proporciona soporte solo para el modo de operación Flush-to-Zero. Los valores definidos son:

  • 0b0000 No implementado o el hardware solo admite el modo de operación Flush-to-Zero.

  • 0b0001 El hardware admite aritmética numérica completamente desnormalizada.

Los demás valores están reservados.

En ARMv8-A, los valores permitidos son 0b0000 y 0b0001.

Esto sugiere que cuando no se implementan los subnormales, las implementaciones simplemente vuelven a cero.

Infinito y NaN

¿Curioso? He escrito algunas cosas en:

Cómo los subnormales mejoran los cálculos

TODO: comprender mejor con más precisión cómo ese salto empeora los resultados del cálculo / cómo los subnormales mejoran los resultados del cálculo.

Historia real

Una entrevista con el anciano de Floating-Point por Charles Severance . (1998) es una breve descripción histórica del mundo real en forma de una entrevista con William Kahan sugerida por John Coleman en los comentarios.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
1
¿Cita para "Al diseñar IEEE 754 .."? O mejor comenzar la oración con 'Supuestamente'
Pacerier
@Pacerier No creo que ese hecho pueda estar equivocado :-) ¿Qué otra razón podría haber para ello? Probablemente esto se sabía antes, pero creo que está bien.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Respuesta impresionante. Me estoy preparando para dar una clase de análisis numérico en la primavera y dirigiré a mis estudiantes a esto (nuestro texto tiene una breve discusión pero omite detalles). En cuanto al fundamento de algunas de las decisiones, encontré lo siguiente esclarecedor: Una entrevista con el anciano de Floating-Point .
John Coleman
@JohnColeman ¡gracias por este enlace! Agregado a la respuesta que lo cita. Sería sorprendente si alguien pudiera agregar, posiblemente en otra respuesta, el ejemplo significativo más corto posible en el que los subnormales hacen que los cálculos resulten mejores (y tal vez un ejemplo artificial donde los resultados empeoren)
Ciro Santilli 郝海东 冠状 病 六四 事件 事件
29

De http://blogs.oracle.com/d/entry/subnormal_numbers :

Hay potencialmente múltiples formas de representar el mismo número, usando decimal como ejemplo, el número 0.1 podría representarse como 1 * 10 -1 o 0.1 * 10 0 o incluso 0.01 * 10. El estándar dicta que los números siempre se almacenan con el primer bit como uno. En decimal que corresponde al ejemplo 1 * 10-1.

Ahora suponga que el exponente más bajo que se puede representar es -100. Entonces, el número más pequeño que se puede representar en forma normal es 1 * 10 -100 . Sin embargo, si relajamos la restricción de que el bit inicial sea uno, entonces podemos representar números más pequeños en el mismo espacio. Tomando un ejemplo decimal, podríamos representar 0.1 * 10 -100 . A esto se le llama un número subnormal. El propósito de tener números subnormales es suavizar la brecha entre el número normal más pequeño y cero.

Es muy importante darse cuenta de que los números subnormales se representan con menos precisión que los números normales. De hecho, están intercambiando precisión reducida por su tamaño más pequeño. Por lo tanto, los cálculos que utilizan números subnormales no tendrán la misma precisión que los cálculos con números normales. Por lo tanto, probablemente valga la pena investigar una aplicación que realiza cálculos significativos sobre números subnormales para ver si el cambio de escala (es decir, multiplicar los números por algún factor de escala) produciría menos subnormales y resultados más precisos.

allwyn.menezes
fuente