Неточное деление чисел двойной точности (Visual C++ 2008)

StackOverflow https://stackoverflow.com/questions/902320

  •  05-09-2019
  •  | 
  •  

Вопрос

У меня есть код для преобразования значения времени, возвращаемого из 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 миллисекунды.

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

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

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

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 дней, поэтому это значение больше, чем я привык?Таким образом, это действительно проблема с точностью деления больших чисел, но это все еще не объясняет, почему деление в окне наблюдения все еще было правильным.Использует ли он для своих результатов тип более высокой точности, чем двойной?

Это было полезно?

Решение 2

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

Я не уверен, какое решение будет самым быстрым, думаю, мне придется сначала протестировать его.

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();
}

Другие советы

Адион,

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

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

Чтобы узнать больше об этом, чем вы, вероятно, когда-либо хотели знать, см.:

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top