que - ¿Por qué GDB evalúa la aritmética de punto flotante de forma diferente a C++?
que es coma flotante en informatica (3)
El sistema de evaluación de expresiones de tiempo de ejecución de GDB ciertamente no garantiza la ejecución del mismo código de máquina efectivo para sus operaciones de punto flotante que el código de máquina optimizado y reordenado generado por su compilador para calcular el resultado de la misma expresión simbólica. De hecho, se garantiza que no se ejecutará el mismo código de máquina para calcular el valor de la expresión dada zd * (1 - tau.d)
, ya que puede considerarse un subconjunto de su programa para el cual se realiza la evaluación de la expresión aislada en tiempo de ejecución De alguna manera arbitraria, "simbólicamente correcta".
La generación de código de punto flotante y la realización de su salida por parte de la CPU son particularmente propensas a inconsistencias simbólicas con otras implementaciones (como un evaluador de expresiones de tiempo de ejecución) debido a la optimización (sustitución, reordenación, eliminación de subexpresiones, etc.), elección de instrucciones, elección de asignación de registros, y el entorno de punto flotante. Si su fragmento contiene muchas variables automáticas en expresiones temporales (como el suyo), la generación de código tiene una libertad especialmente grande con pases de optimización incluso cero, y con esa libertad existe la posibilidad de que, en este caso, pierda precisión en el bit menos significativo de una manera que parece inconsistente.
No obtendrá mucha información sobre por qué el evaluador de tiempo de ejecución de GDB ejecutó las instrucciones que realizó sin una visión profunda del código fuente de GDB, la configuración de compilación y su propio código generado en tiempo de compilación.
Podría llegar al máximo en el ensamblaje generado para su procedimiento para tener una idea de cómo funciona el almacenamiento final en z
, tau
y [en contraste] xiden
trabajo. El flujo de datos para las operaciones de punto flotante que conducen a esas tiendas probablemente no sea lo que parece.
Mucho más fácil, intente hacer que la generación de código sea más determinista al deshabilitar toda la optimización del compilador (por ejemplo, -O0
en GCC) y reescribiendo las expresiones de punto flotante para no usar variables temporales / automáticas. Luego rompa en cada línea en GDB y compare.
Me gustaría poder decirle exactamente por qué se invierte ese poco menos significativo de la mantisa, pero la verdad es que el procesador ni siquiera "sabe" por qué algo se cargó un poco y otra cosa no se debió, por ejemplo, al orden. de evaluación sin una instrucción completa y un seguimiento de datos tanto de su código como de la propia GDB.
He encontrado algo un poco confuso al tratar de resolver un problema aritmético de punto flotante.
Primero, el código. He destilado la esencia de mi problema en este ejemplo:
#include <iostream>
#include <iomanip>
using namespace std;
typedef union {long long ll; double d;} bindouble;
int main(int argc, char** argv) {
bindouble y, z, tau, xinum, xiden;
y.d = 1.0d;
z.ll = 0x3fc5f8e2f0686eee; // double 0.17165791262311053
tau.ll = 0x3fab51c5e0bf9ef7; // double 0.053358253178712838
// xinum = double 0.16249854626123722 (0x3fc4ccc09aeb769a)
xinum.d = y.d * (z.d - tau.d) - tau.d * (z.d - 1);
// xiden = double 0.16249854626123725 (0x3fc4ccc09aeb769b)
xiden.d = z.d * (1 - tau.d);
cout << hex << xinum.ll << endl << xiden.ll << endl;
}
xinum
y xiden
deben tener el mismo valor (cuando y == 1
), pero debido a un error de redondeo de punto flotante, no lo tienen. Esa parte me sale.
La pregunta surgió cuando ejecuté este código (en realidad, mi programa real) a través de GDB para rastrear la discrepancia. Si uso GDB para reproducir las evaluaciones hechas en el código, da un resultado diferente para xiden:
$ gdb mathtest
GNU gdb (Gentoo 7.5 p1) 7.5
...
This GDB was configured as "x86_64-pc-linux-gnu".
...
(gdb) break 16
Breakpoint 1 at 0x4008ef: file mathtest.cpp, line 16.
(gdb) run
Starting program: /home/diazona/tmp/mathtest
...
Breakpoint 1, main (argc=1, argv=0x7fffffffd5f8) at mathtest.cpp:16
16 cout << hex << xinum.ll << endl << xiden.ll << endl;
(gdb) print xiden.d
$1 = 0.16249854626123725
(gdb) print z.d * (1 - tau.d)
$2 = 0.16249854626123722
Notarás que si le pido a GDB que calcule zd * (1 - tau.d)
, da 0.16249854626123722 (0x3fc4ccc09aeb769a), mientras que el código real de C ++ que calcula lo mismo en el programa da 0.16249854626123725 Por lo tanto, GDB debe utilizar un modelo de evaluación diferente para la aritmética de punto flotante. ¿Alguien puede arrojar algo más de luz sobre esto? ¿En qué se diferencia la evaluación de GDB de la evaluación de mi procesador?
Observé esta pregunta relacionada sobre la evaluación de GDB de sqrt(3)
a 0, pero esto no debería ser lo mismo porque no hay llamadas de función involucradas aquí.
No es GDB vs el procesador, es la memoria vs el procesador. El procesador x64 almacena más bits de precisión de lo que realmente tiene la memoria (80 bits frente a 64 bits). Mientras permanezca en la CPU y se registre, conservará 80 bits de precisión, pero cuando se envíe a la memoria determinará cuándo y, por lo tanto, cómo se redondeará. Si GDB envía todos los resultados de los cálculos intermitentes fuera de la CPU (no tengo idea de si este es el caso, o está cerca), hará el redondeo en cada paso, lo que lleva a resultados ligeramente diferentes.
Podría ser porque la FPU x86 funciona en registros con una precisión de 80 bits, pero se redondea a 64 bits cuando el valor se almacena en la memoria. GDB almacenará en la memoria en cada paso del cálculo (interpretado).