variable usar para long imprimir como gcc long-double

gcc - usar - long double variable



doble largo(especĂ­fico de GCC) y__float128 (4)

Anuncio 1.

Esos tipos están diseñados para trabajar con números con un gran rango dinámico. El doble largo se implementa de forma nativa en la FPU x87. El doble 128b sospecho que se implementaría en modo software en los x86 modernos, ya que no hay hardware para hacer los cálculos en el hardware.

Lo curioso es que es bastante común hacer muchas operaciones de punto flotante en una fila y los resultados intermedios no se almacenan realmente en las variables declaradas, sino que se almacenan en registros de FPU aprovechando la precisión total. Es por eso que la comparación:

double x = sin(0); if (x == sin(0)) printf("Equal!");

No es seguro y no se puede garantizar que funcione (sin interruptores adicionales).

Anuncio. 3.

La velocidad depende de la precisión que uses. Puede cambiar la precisión utilizada de la FPU utilizando:

void set_fpu (unsigned int mode) { asm ("fldcw %0" : : "m" (*&mode)); }

Será más rápido para variables más cortas, más lento por más tiempo. Los dobles de 128 bits probablemente se realizarán en software, por lo que será mucho más lento.

No se trata solo de desperdiciar la memoria RAM, sino de perder el caché. Ir a 80 bit double desde 64b double desperdiciará de 33% (32b) a casi 50% (64b) de la memoria (incluida la caché).

Anuncio 4.

Por otro lado, entiendo que el tipo doble largo es mutuamente exclusivo con -mfpmath = sse, ya que no existe tal cosa como "precisión extendida" en SSE. __float128, por otro lado, debería funcionar perfectamente bien con las matemáticas SSE (aunque en ausencia de instrucciones de precisión quad ciertamente no en una base de instrucciones 1: 1). ¿Estoy en lo cierto bajo estas suposiciones?

Las unidades FPU y SSE están totalmente separadas. Puede escribir código usando FPU al mismo tiempo que SSE. La pregunta es: ¿qué generará el compilador si lo limita a usar solo SSE? ¿Tratará de usar FPU de todos modos? He estado haciendo algo de programación con SSE y GCC generará solo un solo SISD por sí mismo. Tienes que ayudarlo a usar versiones SIMD. __float128 probablemente funcionará en todas las máquinas, incluso en el AVR uC de 8 bits. Solo está jugando con bits después de todo.

La representación hexadecimal de 80 bits es en realidad 20 dígitos hexadecimales. ¿Tal vez los bits que no se usan provienen de alguna operación anterior? En mi máquina, compilé tu código y solo cambio 20 bits en modo largo: 66b4e0d2-ec09c1d5-00007ffe-deadbeef

La versión de 128 bits tiene todos los bits cambiando. Al objdump el objdump , parece que estaba usando una emulación de software, casi no hay instrucciones de FPU.

Además, LDBL_MAX, parece funcionar como + inf tanto para double long como para __float128. Agregar o restar un número como 1.0E100 o 1.0E2000 a / desde LDBL_MAX da como resultado el mismo patrón de bits. Hasta ahora, creía que las constantes foo_MAX tenían el mayor número representable que no es + inf (¿aparentemente que no es el caso?).

Esto parece ser extraño ...

Tampoco estoy muy seguro de cómo un número de 80 bits podría actuar como + inf para un valor de 128 bits ... tal vez estoy demasiado cansado al final del día y he hecho algo mal.

Probablemente se está extendiendo. El patrón que se reconoce que es + inf en 80 bits se traduce a + inf también en flotante de 128 bits.

Estoy buscando información detallada sobre long double y __float128 en GCC / x86 (más por curiosidad que por un problema real).

Pocas personas probablemente las necesiten (solo, por primera vez, realmente necesité un double ), pero creo que todavía es valioso (e interesante) saber qué tienes en tu caja de herramientas y de qué se trata.

En ese sentido, por favor disculpe mis preguntas algo abiertas:

  1. ¿Podría alguien explicar la lógica de implementación y el uso previsto de estos tipos, también en comparación el uno del otro? Por ejemplo, ¿son "implementaciones vergonzosas" porque el estándar permite el tipo, y alguien puede quejarse si solo tienen la misma precisión que el double , o están destinados a ser de primera clase?
  2. Alternativamente, ¿alguien tiene una buena referencia web útil para compartir? Una búsqueda en Google en el "long double" site:gcc.gnu.org/onlinedocs no me dio mucho que sea realmente útil.
  3. Asumiendo que el mantra común "si crees que necesitas duplicar, probablemente no entiendas el punto flotante" no se aplica, es decir, realmente necesitas más precisión que simplemente float , y a uno no le importa si 8 o 16 bytes de memoria se queman ... ¿es razonable esperar que uno también pueda saltar al long double o __float128 lugar de al double sin un impacto significativo en el rendimiento?
  4. La característica de "precisión ampliada" de las CPU Intel ha sido históricamente fuente de desagradables sorpresas cuando los valores se movían entre la memoria y los registros. Si en realidad se almacenan 96 bits, el tipo long double debería eliminar este problema. Por otro lado, entiendo que el tipo long double es mutuamente exclusivo con -mfpmath=sse , ya que no existe tal cosa como "precisión extendida" en SSE. __float128 , por otro lado, debería funcionar perfectamente bien con las matemáticas SSE (aunque en ausencia de instrucciones de precisión quad ciertamente no en una base de instrucciones 1: 1). ¿Estoy en lo cierto en estas suposiciones?

(3. y 4. probablemente se puedan descifrar con un poco de trabajo dedicado a la creación de perfiles y el desmontaje, pero tal vez alguien más haya tenido el mismo pensamiento anteriormente y ya haya hecho ese trabajo ).

Fondo (esta es la parte TL; DR):
Inicialmente tropecé con el long double porque estaba buscando DBL_MAX en <float.h> , y por LDBL_MAX está en la línea siguiente. "Oh, mira, GCC en realidad tiene dobles de 128 bits, no es que los necesite, pero ... genial" fue mi primer pensamiento. Sorpresa, sorpresa: sizeof(long double) devuelve 12 ... espera, ¿quieres decir 16?

Ciertamente, los estándares C y C ++ no dan una definición muy concreta del tipo. C99 (6.2.5 10) dice que los números de double son un subconjunto de long double mientras que C ++ 03 establece (3.9.1 8) que el long double tiene al menos tanta precisión como el double (que es lo mismo, solo redactado de manera diferente). Básicamente, los estándares dejan todo a la implementación, de la misma manera que con long , int y short .

Wikipedia dice que GCC usa "precisión extendida de 80 bits en procesadores x86 independientemente del almacenamiento físico utilizado" .

La documentación de GCC establece, todos en la misma página, que el tamaño del tipo es de 96 bits debido a la ABI i386, pero no hay más de 80 bits de precisión habilitados por ninguna opción (¿eh? ¿Qué?), También Pentium y más nuevos los procesadores quieren que estén alineados como números de 128 bits. Este es el valor predeterminado en 64 bits y se puede habilitar manualmente en 32 bits, lo que da como resultado 32 bits de relleno cero.

Hora de ejecutar una prueba:

#include <stdio.h> #include <cfloat> int main() { #ifdef USE_FLOAT128 typedef __float128 long_double_t; #else typedef long double long_double_t; #endif long_double_t ld; int* i = (int*) &ld; i[0] = i[1] = i[2] = i[3] = 0xdeadbeef; for(ld = 0.0000000000000001; ld < LDBL_MAX; ld *= 1.0000001) printf("%08x-%08x-%08x-%08x/r", i[0], i[1], i[2], i[3]); return 0; }

La salida, cuando se usa el long double , se ve algo así, con los dígitos marcados como constantes, y todos los demás cambian a medida que los números se hacen más y más grandes:

5636666b-c03ef3e0-00223fd8-deadbeef ^^ ^^^^^^^^

Esto sugiere que no es un número de 80 bits. Un número de 80 bits tiene 18 dígitos hexadecimales. Veo cambiar 22 dígitos hexadecimales, que se parece mucho más a un número de 96 bits (24 dígitos hexadecimales). Tampoco es un número de 128 bits ya que 0xdeadbeef no se toca, lo que es consistente con el sizeof devolver 12.

La salida de __int128 parece que realmente es solo un número de 128 bits. Todos los bits finalmente cambian.

La -m128bit-long-double con -m128bit-long-double no alinea el long double a 128 bits con un relleno de cero de 32 bits, como lo indica la documentación. Tampoco usa __int128 , pero de hecho parece alinearse a 128 bits, rellenando con el valor 0x7ffdd000 (?!).

Además, LDBL_MAX , parece funcionar como +inf tanto para long double como para __float128 . Agregar o restar un número como 1.0E100 o 1.0E2000 a / desde LDBL_MAX da LDBL_MAX resultado el mismo patrón de bits.
Hasta ahora, creía que las constantes foo_MAX tenían el mayor número representable que no es +inf (¿aparentemente que no es el caso?). Tampoco estoy muy seguro de cómo un número de 80 bits podría actuar como +inf para un valor de 128 bits ... tal vez estoy demasiado cansado al final del día y he hecho algo mal.


C99 y C ++ 11 agregaron los tipos float_t y double_t que son alias para los tipos incorporados de coma flotante. Aproximadamente, float_t es el tipo de resultado de hacer aritmética entre los valores de tipo float , y double_t es el tipo del resultado de hacer aritmética entre los valores de tipo double .


IEEE-754 definió 32 y 64 representaciones de punto flotante para el almacenamiento eficiente de datos, y una representación de 80 bits para el cálculo eficiente. La intención era que dado float f1,f2; double d1,d2; float f1,f2; double d1,d2; una declaración como d1=f1+f2+d2; se ejecutaría convirtiendo los argumentos a valores de coma flotante de 80 bits, agregándolos y convirtiendo el resultado a un tipo de coma flotante de 64 bits. Esto ofrecería tres ventajas en comparación con realizar operaciones en otros tipos de coma flotante directamente:

  1. Si bien se necesitarían códigos o circuitos separados para las conversiones hacia / desde tipos de 32 bits y tipos de 64 bits, solo sería necesario tener una implementación de "agregar", una implementación de "multiplicación", una implementación de "raíz cuadrada", etc.

  2. Aunque en casos raros, el uso de un tipo computacional de 80 bits podría arrojar resultados que eran ligeramente menos precisos que el uso de otros tipos directamente (el peor error de redondeo es 513 / 1024ulp en los casos en que los cálculos en otros tipos arrojarían un error de 511 / 1024ulp ), los cálculos encadenados con tipos de 80 bits con frecuencia serían más precisos, a veces mucho más precisos, que los cálculos con otros tipos.

  3. En un sistema sin una FPU, separar un double en un exponente separado y mantisa antes de realizar cálculos, normalizar una mantisa y convertir una mantisa y exponente por separado en un double , consume algo de tiempo. Si el resultado de un cálculo se utilizará como entrada para otro y se descarta, el uso de un tipo de 80 bits sin empaquetar permitirá omitir estos pasos.

Sin embargo, para que este enfoque de matemática de punto flotante sea útil, es imperativo que el código pueda almacenar resultados intermedios con la misma precisión que se usaría en el cálculo, de modo que temp = d1+d2; d4=temp+d3; temp = d1+d2; d4=temp+d3; producirá el mismo resultado que d4=d1+d2+d3; . Por lo que puedo decir, el propósito del long double era ser de ese tipo. Desafortunadamente, a pesar de que K & R diseñó C de modo que todos los valores de punto flotante pasen a métodos variados de la misma manera, ANSI C lo rompió. En C como se diseñó originalmente, dado el código float v1,v2; ... printf("%12.6f", v1+v2); float v1,v2; ... printf("%12.6f", v1+v2); , el método printf no tendría que preocuparse de si v1+v2 produciría un float o un double , ya que el resultado se vería forzado a un tipo conocido independientemente. Además, incluso si el tipo de v1 o v2 cambiara al double , la declaración printf no tendría que cambiar.

ANSI C, sin embargo, requiere que el código que llama a printf debe saber qué argumentos son double y cuáles son long double ; una gran cantidad de código, si no la mayoría, de código que usa el long double pero que se escribió en plataformas donde también es double no usa los especificadores de formato correctos para valores long double . En lugar de tener el long double ser un tipo de 80 bits, excepto cuando se pasa como un argumento de método variadic, en cuyo caso se forzaría a 64 bits, muchos compiladores decidieron hacer long double double con el double y no ofrecen ningún medio para almacenar el resultados de cálculos intermedios. Dado que usar un tipo de precisión extendida para el cálculo solo es bueno si el programa está disponible para el programador, mucha gente concluyó que la precisión extendida era malvada, aunque era solo la incapacidad de ANSI C para manejar los argumentos variados lo que la hacía problemática.

PD: El objetivo previsto del long double se habría beneficiado si también hubiera existido una long float que se definió como el tipo al que los argumentos float podrían promoverse de manera más eficiente; en muchas máquinas sin unidades de punto flotante que probablemente serían de tipo 48 bits, pero el tamaño óptimo podría oscilar entre 32 bits (en máquinas con una FPU que hace matemáticas de 32 bits directamente) hasta 80 (en máquinas que usan el diseño previsto por IEEE-754). Demasiado tarde ahora, sin embargo.


Se reduce a la diferencia entre 4.9999999999999999999 y 5.0.

  1. Aunque el rango es la diferencia principal, es la precisión lo que es importante.
  2. Este tipo de datos será necesario en cálculos de gran círculo o matemática coordinada que es probable que se use con sistemas GPS.
  3. Como la precisión es mucho mejor que el doble normal, significa que puede retener típicamente 18 dígitos significativos sin perder precisión en los cálculos.
  4. La precisión extendida creo que usa 80 bits (se usa principalmente en procesadores matemáticos), por lo que 128 bits serán mucho más precisos.