divisão imprecisa de duplas (Visual C ++ 2008)
Pergunta
Eu tenho algum código para converter um valor de tempo voltou de QueryPerformanceCounter para um valor double em milissegundos, como este é mais conveniente para contar com.
A função esta aparência:
double timeGetExactTime() {
LARGE_INTEGER timerPerformanceCounter, timerPerformanceFrequency;
QueryPerformanceCounter(&timerPerformanceCounter);
if (QueryPerformanceFrequency(&timerPerformanceFrequency)) {
return (double)timerPerformanceCounter.QuadPart / (((double)timerPerformanceFrequency.QuadPart) / 1000.0);
}
return 0.0;
}
O problema que estou tendo recentemente (eu não acho que eu tive esse problema antes, e não foram feitas alterações ao código) é que o resultado não é muito preciso. O resultado não contém casas decimais, mas é ainda menos preciso do que 1 milissegundo.
Quando eu digitar a expressão no depurador, o resultado é tão preciso quanto eu esperaria.
Eu entendo que a dupla pode não manter a precisão de um inteiro de 64 bits, mas, neste momento, o PerformanceCounter necessária apenas 46 bits (e um duplo deve ser capaz de armazenar 52 bits sem perda) Além disso, parece estranho que o depurador iria usar um formato diferente para fazer a divisão.
Aqui estão alguns resultados que obtive. O programa foi compilado no modo de depuração, Flutuar modo Point em C ++ opções foi definido como o padrão (Precise (/ fp: precisa))
timerPerformanceCounter.QuadPart: 30270310439445
timerPerformanceFrequency.QuadPart: 14318180
double perfCounter = (double)timerPerformanceCounter.QuadPart;
30270310439445.000
double perfFrequency = (((double)timerPerformanceFrequency.QuadPart) / 1000.0);
14318.179687500000
double result = perfCounter / perfFrequency;
2114117248.0000000
return (double)timerPerformanceCounter.QuadPart / (((double)timerPerformanceFrequency.QuadPart) / 1000.0);
2114117248.0000000
Result with same expression in debugger:
2114117188.0396111
Result of perfTimerCount / perfTimerFreq in debugger:
2114117234.1810646
Result of 30270310439445 / 14318180 in calculator:
2114117188.0396111796331656677036
Alguém sabe por que a precisão é diferente em Watch do depurador em comparação com o resultado em meu programa?
Update: Eu tentei dedução 30270310439445 de timerPerformanceCounter.QuadPart antes de fazer a conversão e divisão, e que parece ser precisas em todos os casos agora. Talvez a razão pela qual eu só estou vendo esse comportamento agora pode ser porque o tempo de funcionamento do meu computador tem agora 16 dias, então o valor é maior do que eu estou acostumado a? Por isso, parece ser uma questão de precisão divisão com números grandes, mas que ainda não explica por que a divisão ainda estava correta na janela Watch. Ele usa um tipo de maior precisão do que o dobro para os seus resultados?
Solução 2
Obrigado, usando decimal seria provavelmente uma solução também. Por agora eu tenho tido uma abordagem ligeiramente diferente, que também funciona bem, pelo menos tão longo como o meu programa não executar mais de uma semana ou assim sem reiniciar. Eu só me lembro o contador de quando o meu programa começou performance, e subtrair isso a partir do contra-corrente antes de converter para dobrar e fazer a divisão.
Eu não tenho certeza qual solução seria mais rápido, acho que eu teria que benchmark que em primeiro lugar.
bool perfTimerInitialized = false;
double timerPerformanceFrequencyDbl;
LARGE_INTEGER timerPerformanceFrequency;
LARGE_INTEGER timerPerformanceCounterStart;
double timeGetExactTime()
{
if (!perfTimerInitialized) {
QueryPerformanceFrequency(&timerPerformanceFrequency);
timerPerformanceFrequencyDbl = ((double)timerPerformanceFrequency.QuadPart) / 1000.0;
QueryPerformanceCounter(&timerPerformanceCounterStart);
perfTimerInitialized = true;
}
LARGE_INTEGER timerPerformanceCounter;
if (QueryPerformanceCounter(&timerPerformanceCounter)) {
timerPerformanceCounter.QuadPart -= timerPerformanceCounterStart.QuadPart;
return ((double)timerPerformanceCounter.QuadPart) / timerPerformanceFrequencyDbl;
}
return (double)timeGetTime();
}
Outras dicas
Adion,
Se você não se importa o impacto no desempenho, lança seus números QuadPart para decimal em vez de duas vezes antes de executar a divisão. Em seguida, emitir o número de volta, resultando para o dobro.
Você está correto sobre o tamanho dos números. Ele joga fora a precisão dos cálculos de ponto flutuante.
Para saber mais sobre isso do que você provavelmente sempre quis saber, ver:
O que cada cientista computador deve saber sobre Floating-Point Arithmetic http://docs.sun.com/source/806-3568/ncg_goldberg. html