C ++ Ponto de flutuação Perda de precisão: 3015/0,00025298219406977296
-
22-09-2019 - |
Pergunta
O problema.
Compilador Microsoft Visual C ++ 2005, 32bits Windows XP SP3, CPU AMD 64 x2.
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;
O resultado do cálculo (ou seja, "F") é 11917835.000000000 (((não assinado __int64) (& f)) == 0x4166bb4160000000) Embora deva ser 11917834.814763514 (ou seja, ((não assinado __int64) (& f)) == 0x4166bb415a128aef).
Ou seja, a parte fracionária está perdida.
Infelizmente, preciso de uma parte fracionária para estar correta.
Perguntas:
1) Por que isso acontece?
2) Como posso resolver o problema?
Informação adicional:
0) O resultado é tomado diretamente Na janela "Watch" (não foi impressa e não esqueci de definir a precisão da impressão). Também forneci despejo hexadecimal da variável de ponto flutuante, por isso tenho certeza absoluta do resultado do cálculo.
1) A desmontagem de f = a/b é:
fld qword ptr [a]
fdiv qword ptr [b]
fstp qword ptr [f]
2) f = 3015/0,00025298219406977296; produz resultado correto (f == 11917834.814763514, ((não assinado __int64) (& f)) == 0x4166bb415a128aef), mas parece que, neste caso, o resultado é simplesmente calculado durante o tempo de compilação:
fld qword ptr [__real@4166bb415a128aef (828EA0h)]
fstp qword ptr [f]
Então, como posso resolver esse problema?
PS Encontrei uma solução alternativa temporária (preciso apenas de parte fracionária da divisão, então eu simplesmente uso f = fmod (a/b)/b no momento), mas ainda gostaria de saber como corrigir esse problema corretamente - A precisão dupla deve ser de 16 dígitos decimais, portanto, esse cálculo não deve causar problemas.
Solução
Você está usando o DirectX em seu programa em qualquer lugar, pois isso faz com que a unidade de ponto flutuante seja alterada para o modo de precisão única, a menos que você diga especificamente quando você não cria o dispositivo e causaria exatamente isso
Outras dicas
Curiosamente, se você declarar A e B como flutuadores, obterá exatamente 11917835.000000000. Portanto, meu palpite é que há uma conversão para a precisão única acontecendo em algum lugar, seja na maneira como as constantes são interpretadas ou posteriormente nos cálculos.
Qualquer um dos casos é um pouco surpreendente, considerando o quão simples é o seu código. Você não está usando nenhuma diretiva do compilador exótico, forçando a precisão única para todos os números de ponto flutuante?
EDIT: Você realmente confirmou que o programa compilado gera um resultado incorreto? Caso contrário, o candidato mais provável para a conversão de precisão única (errônea) seria o depurador.
Se você precisar de matemática precisa, não use ponto flutuante.
Faça um favor a si mesmo e obtenha uma biblioteca Bignum com suporte racional de número.
Eu acho que você está imprimindo o número sem especificar uma precisão. Experimente isso:
#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;
}
Isso produz:
11917834.814763514000000
O que me parece correto. Estou usando o VC ++ 2008 em vez de 2005, mas acho que a diferença está no seu código, não no compilador.
Tem certeza de que está examinando o valor de f logo após a instrução FSTP? Se você tem otimizações ativadas, talvez a janela do relógio possa estar mostrando um valor assumido em algum ponto posterior (isso parece um pouco plausível ao dizer que está olhando para a parte fracionária de F mais tarde - algumas instruções acabam massando fora de alguma forma?)