resueltos representación reales punto programacion números numeros numero normalizado normalizacion informatica flotante ejercicios ejemplos gcc floating-point clang c99 fenv

gcc - representación - Sumando dos números de punto flotante



punto flotante informatica (1)

No pude encontrar ninguna opción de línea de comando que hiciera lo que quisieras. Sin embargo, encontré una forma de volver a escribir su código para que, incluso con las optimizaciones máximas (incluso las optimizaciones arquitectónicas), ni GCC ni Clang calculen el valor en el momento de la compilación. En su lugar, esto los obliga a generar código que calculará el valor en tiempo de ejecución.

DO:

#include <fenv.h> #include <stdio.h> #pragma STDC FENV_ACCESS ON // add with rounding up double __attribute__ ((noinline)) addrup (double x, double y) { int round = fegetround (); fesetround (FE_UPWARD); double r = x + y; fesetround (round); // restore old rounding mode return r; } int main(int c, char *v[]){ printf("%a/n", addrup (0x1.0p0, 0x1.0p-80)); }

Esto da como resultado estos resultados de GCC y Clang, incluso cuando se utilizan optimizaciones máximas y arquitectónicas:

gcc -S -xc -march=corei7 -O3 ( Godbolt GCC ):

addrup: push rbx sub rsp, 16 movsd QWORD PTR [rsp+8], xmm0 movsd QWORD PTR [rsp], xmm1 call fegetround mov edi, 2048 mov ebx, eax call fesetround movsd xmm1, QWORD PTR [rsp] mov edi, ebx movsd xmm0, QWORD PTR [rsp+8] addsd xmm0, xmm1 movsd QWORD PTR [rsp], xmm0 call fesetround movsd xmm0, QWORD PTR [rsp] add rsp, 16 pop rbx ret .LC2: .string "%a/n" main: sub rsp, 8 movsd xmm1, QWORD PTR .LC0[rip] movsd xmm0, QWORD PTR .LC1[rip] call addrup mov edi, OFFSET FLAT:.LC2 mov eax, 1 call printf xor eax, eax add rsp, 8 ret .LC0: .long 0 .long 988807168 .LC1: .long 0 .long 1072693248

clang -S -xc -march=corei7 -O3 ( Godbolt GCC ):

addrup: # @addrup push rbx sub rsp, 16 movsd qword ptr [rsp], xmm1 # 8-byte Spill movsd qword ptr [rsp + 8], xmm0 # 8-byte Spill call fegetround mov ebx, eax mov edi, 2048 call fesetround movsd xmm0, qword ptr [rsp + 8] # 8-byte Reload addsd xmm0, qword ptr [rsp] # 8-byte Folded Reload movsd qword ptr [rsp + 8], xmm0 # 8-byte Spill mov edi, ebx call fesetround movsd xmm0, qword ptr [rsp + 8] # 8-byte Reload add rsp, 16 pop rbx ret .LCPI1_0: .quad 4607182418800017408 # double 1 .LCPI1_1: .quad 4246894448610377728 # double 8.2718061255302767E-25 main: # @main push rax movsd xmm0, qword ptr [rip + .LCPI1_0] # xmm0 = mem[0],zero movsd xmm1, qword ptr [rip + .LCPI1_1] # xmm1 = mem[0],zero call addrup mov edi, .L.str mov al, 1 call printf xor eax, eax pop rcx ret .L.str: .asciz "%a/n"

Ahora, para la parte más interesante: ¿por qué funciona eso?

Bueno, cuando ellos (GCC y / o Clang) compilan código, intentan encontrar y reemplazar valores que pueden calcularse en tiempo de ejecución. Esto se conoce como propagación constante . Si simplemente hubiera escrito otra función, la propagación constante dejaría de ocurrir, ya que se supone que no debe cruzar funciones.

Sin embargo, si ven una función a la que podrían, en teoría, sustituir el código de en lugar de la llamada de función, pueden hacerlo. Esto se conoce como función en línea . Si la función inlining funcionará en una función, decimos que esa función es (sorpresa) inlinable .

Si una función siempre devuelve los mismos resultados para un conjunto dado de entradas, entonces se considera pura . También decimos que no tiene efectos secundarios (lo que significa que no hace cambios en el medio ambiente).

Ahora, si una función es completamente inlinable (lo que significa que no hace ninguna llamada a bibliotecas externas excluyendo algunos valores predeterminados incluidos en GCC y Clang - libc , libm , etc.) y es pura, entonces aplicarán una propagación constante al función.

En otras palabras, si no queremos que se propaguen constantes a través de una llamada de función, podemos hacer una de dos cosas:

  • Haz que la función parezca impura:
    • Utilizar el sistema de archivos
    • Haz un poco de magia de mierda con alguna entrada aleatoria de algún lugar
    • Usa la red
    • Utilice algún syscall de algún tipo
    • Llame a algo de una biblioteca externa desconocida para GCC y / o Clang
  • Hacer que la función no sea completamente inlinable.
    • Llame a algo de una biblioteca externa desconocida para GCC y / o Clang
    • Utilice __attribute__ ((noinline))

Ahora, este último es el más fácil. Como puede haber __attribute__ ((noinline)) , __attribute__ ((noinline)) marca la función como no inlinable. Ya que podemos aprovechar esto, todo lo que tenemos que hacer es hacer otra función que haga cualquier cálculo que queramos, marcarlo con __attribute__ ((noinline)) , y luego llamarlo.

Cuando se compila, no violarán las reglas de alineación y, por extensión, de propagación constante, y, por lo tanto, el valor se computará en tiempo de ejecución con el modo de redondeo apropiado establecido.

Me gustaría calcular la suma, redondeada, de dos números binarios 64 de IEEE 754. Para ello escribí el programa C99 a continuación:

#include <stdio.h> #include <fenv.h> #pragma STDC FENV_ACCESS ON int main(int c, char *v[]){ fesetround(FE_UPWARD); printf("%a/n", 0x1.0p0 + 0x1.0p-80); }

Sin embargo, si compilo y ejecuto mi programa con varios compiladores:

$ gcc -v … gcc version 4.2.1 (Apple Inc. build 5664) $ gcc -Wall -std=c99 add.c && ./a.out add.c:3: warning: ignoring #pragma STDC FENV_ACCESS 0x1p+0 $ clang -v Apple clang version 1.5 (tags/Apple/clang-60) Target: x86_64-apple-darwin10 Thread model: posix $ clang -Wall -std=c99 add.c && ./a.out add.c:3:14: warning: pragma STDC FENV_ACCESS ON is not supported, ignoring pragma [-Wunknown-pragmas] #pragma STDC FENV_ACCESS ON ^ 1 warning generated. 0x1p+0

¡No funciona! (Esperaba el resultado 0x1.0000000000001p0 ).

De hecho, el cálculo se realizó en tiempo de compilación en el modo predeterminado de redondeo al más cercano:

$ clang -Wall -std=c99 -S add.c && cat add.s add.c:3:14: warning: pragma STDC FENV_ACCESS ON is not supported, ignoring pragma [-Wunknown-pragmas] #pragma STDC FENV_ACCESS ON ^ 1 warning generated. … LCPI1_0: .quad 4607182418800017408 … callq _fesetround movb $1, %cl movsd LCPI1_0(%rip), %xmm0 leaq L_.str(%rip), %rdx movq %rdx, %rdi movb %cl, %al callq _printf … L_.str: .asciz "%a/n"

Sí, vi la advertencia emitida por cada compilador. Entiendo que activar o desactivar las optimizaciones aplicables en la escala de la línea puede ser complicado. Todavía me gustaría, si fuera posible, desactivarlos en la escala del archivo, lo cual sería suficiente para resolver mi pregunta.

Mi pregunta es: ¿qué opción (s) de línea de comandos debo usar con GCC o Clang para compilar una unidad de compilación C99 que contenga código destinado a ejecutarse con un modo de redondeo de FPU diferente al predeterminado?

Digresión

Mientras investigaba esta pregunta, encontré esta página de cumplimiento de GCC C99 , que contiene la siguiente entrada, que simplemente dejaré aquí en caso de que alguien más lo encuentre divertido. Grrrr.

floating-point | | environment access | N/A | Library feature, no compiler support required. in <fenv.h> | |