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