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?
c++
c++11
floating-point
ieee-754
BЈовић
fuente
fuente
Respuestas:
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 .
fuente
isnomal
estrue
si los 8 bits son todos cero, yfalse
de otra manera?001010
, como , y se interpreta como1.001010
.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:
O si te gustan las fotos:
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 uno1
en 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":
Pero entonces, ¿cómo lidiar con eso
0.0
? Bueno, decidieron crear una excepción:0.0
para que los bytes
00 00 00 00
también representen0.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:
que se parece a esto en una fracción hexadecimal debido a la convención de bits inicial:
1.000002 * 2 ^ (-127)
donde
.000002
es 22 ceros con un1
al final.No podemos tomar
fraction = 0
, de lo contrario ese número sería0.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.0
a 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?
Esta regla implica inmediatamente que el número tal que:
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.0
que 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:
que representa:
1.0 * 2 ^ (-126)
Entonces, el número subnormal más grande es:
que es igual a:
0.FFFFFE * 2 ^ (-126)
donde
.FFFFFE
está 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:
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.0
exactamente!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:
*
)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:
[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)
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
float
representa 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 TEn 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:
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:
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:
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:
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.
fuente
De http://blogs.oracle.com/d/entry/subnormal_numbers :
fuente