2010-03-28 16 views
17

El problema.C++ pérdida de precisión en punto flotante: 3015/0.00025298219406977296

compilador de Microsoft Visual C++ 2005, ventanas de 32 bits xp sp3, amd 64 x2 cpu.

Código:

double a = 3015.0; 
double b = 0.00025298219406977296; 
//*((unsigned __int64*)(&a)) == 0x40a78e0000000000 
//*((unsigned __int64*)(&b)) == 0x3f30945640000000 
double f = a/b;//3015/0.00025298219406977296; 

el resultado del cálculo (es decir, "F") es 11917835,000000000 (((unsigned __int64) (& f)) == 0x4166bb4160000000) aunque debería ser 11,917,834.814763514 (es decir ((unsigned __int64) (& f)) == 0x4166bb415a128aef).
I.e. parte fraccional se pierde
Desafortunadamente, necesito que la parte fraccional sea correcta.

Preguntas:
1) ¿Por qué sucede esto?
2) ¿Cómo puedo solucionar el problema?

Otros detalles:
0) El resultado se toma directamente desde la ventana de "ver" (que no se imprimió, y no se olvidó de fijar la impresión de precisión). También proporcioné el volcado hexadecimal de la variable de coma flotante, por lo que estoy absolutamente seguro sobre el resultado del cálculo.
1) El desmontaje de f = a/b es:

fld   qword ptr [a] 
fdiv  qword ptr [b] 
fstp  qword ptr [f] 

2) f = 3015/0,00025298219406977296; rendimientos resultado correcto (f == 11917834,814763514, ((__int64 sin firmar) (& f)) == 0x4166bb415a128aef), pero parece que en este caso es simplemente resultado calculado durante el tiempo de compilación:

fld   qword ptr [[email protected] (828EA0h)] 
fstp  qword ptr [f] 

Así , ¿Como puedo solucionar este problema?

P.S. He encontrado una solución temporal (solo necesito una parte fraccional de la división, así que simplemente uso f = fmod (a/b)/b en este momento), pero aún me gustaría saber cómo solucionar este problema de manera adecuada: el doble se supone que la precisión es de 16 dígitos decimales, por lo que no se supone que este cálculo cause problemas.

Respuesta

15

¿Está utilizando DirectX en su programa en cualquier lugar, que hace que la unidad de coma flotante para ser cambiado a modo de simple precisión a menos que específicamente dice que no cuando se crea el dispositivo y podría causar exactamente esto

+1

Esta es una respuesta correcta. El programa usa Direct3D y, por supuesto, el cálculo ocurre después de la creación del dispositivo. Lo curioso es que sabía que D3D ajustaba la precisión de la FPU, pero me olvidé por completo, porque no he visto este error en los últimos años. Problema resuelto. – SigTerm

+1

¿Qué indicador se debe usar al crear el dispositivo? ¿Existe el mismo problema con Direct2D? – dalle

1

Supongo que está imprimiendo el número sin especificar una precisión. Prueba esto:

#include <iostream> 
#include <iomanip> 

int main() { 
    double a = 3015.0; 
    double b = 0.00025298219406977296; 
    double f = a/b; 

    std::cout << std::fixed << std::setprecision(15) << f << std::endl; 
    return 0; 
} 

Esto produce:

11917834,814763514000000

que parece correcto para mí. Estoy usando VC++ 2008 en lugar de 2005, pero supongo que la diferencia está en tu código, no en el compilador.

+0

No, no estoy imprimiendo el número, el resultado se toma directamente de la ventana "mirar". – SigTerm

+0

¿Has intentado imprimirlo? ¡Tal vez el error está en la ventana del reloj! –

+0

@Martin La ventana del reloj muestra la precisión completa. –

4

Curiosamente, si declara tanto a como b como flotantes, obtendrá exactamente 11917835.000000000. Así que mi suposición es que hay una conversión a una sola precisión en algún lugar, ya sea en la interpretación de las constantes o más adelante en los cálculos.

En cualquier caso, es un poco sorprendente, teniendo en cuenta lo simple que es su código. No está utilizando ninguna directiva de compilación exótica, lo que obliga a una sola precisión para todos los números de punto flotante?

Editar: ¿Ha confirmado realmente que el programa compilado genera un resultado incorrecto? De lo contrario, el candidato más probable para la conversión de precisión única (errónea) sería el depurador.

+0

No hay conversión a precisión simple como se muestra claramente en el desmontaje. –

+0

No en esas tres líneas, de todos modos. –

2

Si necesita cálculos precisos, no use coma flotante.

Hazte un favor y obtén una biblioteca BigNum con soporte racional de números.

+3

No necesita 11917834.814763514100059144562708, solo necesita 11917834.814763514. Renunciar órdenes de magnitud en rendimiento y memoria solo para obtener la precisión incorporada en la máquina parece un poco irracional (perdón por el juego de palabras). – Gabe

+0

Claro, no tenemos derecho a esperar exactitud, ¡pero aún tenemos derecho a pedir el nivel de corrección que nos promete la especificación de coma flotante! – AakashM

+0

Sin ofender, pero creo que usar bignums solo para un cálculo es demasiado, al menos en este caso. – SigTerm

0

¿Estás seguro de que estás examinando el valor de f justo después de la instrucción fstp? Si tiene activadas las optimizaciones, tal vez la ventana del reloj muestre un valor tomado en algún momento posterior (esto parece un poco plausible, ya que dice que está mirando la parte fraccional de f más adelante - ¿alguna instrucción lo enmascara? de alguna manera?)

Cuestiones relacionadas