Неточное деление парных чисел (Visual C ++ 2008) - PullRequest
1 голос
/ 23 мая 2009

У меня есть некоторый код для преобразования значения времени, возвращенного из QueryPerformanceCounter, в двойное значение в миллисекундах, так как это удобнее считать с.

Функция выглядит так:

double timeGetExactTime() {
    LARGE_INTEGER timerPerformanceCounter, timerPerformanceFrequency;
    QueryPerformanceCounter(&timerPerformanceCounter);
    if (QueryPerformanceFrequency(&timerPerformanceFrequency)) {
        return (double)timerPerformanceCounter.QuadPart / (((double)timerPerformanceFrequency.QuadPart) / 1000.0);
    }
    return 0.0;
}

Проблема, с которой я столкнулся недавно (я не думаю, что у меня была эта проблема раньше, и в код не было внесено никаких изменений), заключается в том, что результат не очень точен. Результат не содержит десятичных дробей, но он даже менее точен, чем 1 миллисекунда.

Когда я ввожу выражение в отладчик, результат будет настолько точным, насколько я ожидал.

Я понимаю, что double не может содержать точность 64-битного целого числа, но в настоящее время PerformanceCounter требуется только 46 бит (и двойной должен иметь возможность хранить 52 бита без потерь) Кроме того, кажется странным, что отладчик использовал бы другой формат для деления.

Вот некоторые результаты, которые я получил. Программа была скомпилирована в режиме отладки, режим с плавающей запятой в параметрах C ++ был установлен по умолчанию (Precise (/ fp: точный))

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

Кто-нибудь знает, почему точность в Watch отладчика отличается от результата в моей программе?

Обновление: я пытался вычесть 30270310439445 из timerPerformanceCounter.QuadPart перед выполнением преобразования и деления, и теперь оно кажется точным во всех случаях. Может быть, причина, по которой я сейчас вижу только такое поведение, может быть в том, что время работы моего компьютера составляет 16 дней, поэтому значение больше, чем я привык? Таким образом, это кажется проблемой точности деления с большими числами, но это все еще не объясняет, почему деление все еще было правильным в окне Watch. Использует ли он более точный тип, чем double, для своих результатов?

Ответы [ 2 ]

0 голосов
/ 24 мая 2009

Спасибо, использование десятичной дроби, вероятно, тоже будет решением. На данный момент я выбрал немного другой подход, который также работает хорошо, по крайней мере, до тех пор, пока моя программа не будет работать дольше недели или около того без перезапуска. Я просто помню счетчик производительности, когда запускалась моя программа, и вычитал его из текущего счетчика, прежде чем преобразовать в удвоение и выполнить деление.

Я не уверен, какое решение было бы самым быстрым, наверное, мне нужно было бы сначала это проверить.

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();
}
0 голосов
/ 24 мая 2009

Adion,

Если вы не возражаете против снижения производительности, приведите числа QuadPart к десятичному числу вместо двойного перед выполнением деления. Затем приведите полученное число к удвоению.

Вы правы относительно размера чисел. Это сбрасывает точность вычислений с плавающей запятой.

Более подробную информацию об этом, чем вы, возможно, когда-либо хотели узнать, смотрите:

Что должен знать каждый компьютерщик об арифметике с плавающей точкой http://docs.sun.com/source/806-3568/ncg_goldberg.html

...