不准确的双打划分(Visual C ++ 2008)

时间:2021-12-22 12:09:55

I have some code to convert a time value returned from QueryPerformanceCounter to a double value in milliseconds, as this is more convenient to count with.

我有一些代码将QueryPerformanceCounter返回的时间值转换为以毫秒为单位的double值,因为这更方便计数。

The function looks like this:

该函数如下所示:

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

The problem I'm having recently (I don't think I had this problem before, and no changes have been made to the code) is that the result is not very accurate. The result does not contain any decimals, but it is even less accurate than 1 millisecond.

我最近遇到的问题(我之前认为我没有遇到过这个问题,并且没有对代码进行任何更改)结果不是很准确。结果不包含任何小数,但它甚至不如1毫秒精确。

When I enter the expression in the debugger, the result is as accurate as I would expect.

当我在调试器中输入表达式时,结果与我期望的一样准确。

I understand that a double cannot hold the accuracy of a 64-bit integer, but at this time, the PerformanceCounter only required 46 bits (and a double should be able to store 52 bits without loss) Furthermore it seems odd that the debugger would use a different format to do the division.

我知道double不能保持64位整数的精度,但此时,PerformanceCounter只需要46位(而double应该能够存储52位而不会丢失)此外,调试器使用它似乎很奇怪一种不同的格式来进行划分。

Here are some results I got. The program was compiled in Debug mode, Floating Point mode in C++ options was set to the default ( Precise (/fp:precise) )

以下是我得到的一些结果。程序在Debug模式下编译,C ++选项中的Floating Point模式设置为默认值(Precise(/ fp:precise))

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

Does anyone know why the accuracy is different in the debugger's Watch compared to the result in my program?

有谁知道为什么调试器的Watch中的精度与我程序中的结果相比有所不同?

Update: I tried deducting 30270310439445 from timerPerformanceCounter.QuadPart before doing the conversion and division, and it does appear to be accurate in all cases now. Maybe the reason why I'm only seeing this behavior now might be because my computer's uptime is now 16 days, so the value is larger than I'm used to? So it does appear to be a division accuracy issue with large numbers, but that still doesn't explain why the division was still correct in the Watch window. Does it use a higher-precision type than double for it's results?

更新:我尝试在进行转换和除法之前从timerPerformanceCounter.QuadPart中扣除30270310439445,现在看起来确实在所有情况下都是准确的。也许我现在只看到这种行为的原因可能是因为我的计算机的正常运行时间现在是16天,所以这个值比我习惯的还要大?所以它看起来似乎是一个具有大数字的除法精度问题,但这仍然无法解释为什么除法在Watch窗口中仍然是正确的。对于它的结果,它是否使用更高精度的类型而不是double?

2 个解决方案

#1


Adion,

If you don't mind the performance hit, cast your QuadPart numbers to decimal instead of double before performing the division. Then cast the resulting number back to double.

如果您不介意性能命中,请在执行除法之前将QuadPart数字转换为十进制而不是两倍。然后将得到的数字重新加倍。

You are correct about the size of the numbers. It throws off the accuracy of the floating point calculations.

你对数字的大小是正确的。它抛弃了浮点计算的准确性。

For more about this than you probably ever wanted to know, see:

有关这方面的更多信息,请参阅:

What Every Computer Scientist Should Know About Floating-Point Arithmetic http://docs.sun.com/source/806-3568/ncg_goldberg.html

每个计算机科学家应该知道的关于浮点运算的内容http://docs.sun.com/source/806-3568/ncg_goldberg.html

#2


Thanks, using decimal would probably be a solution too. For now I've taken a slightly different approach, which also works well, at least as long as my program doesn't run longer than a week or so without restarting. I just remember the performance counter of when my program started, and subtract this from the current counter before converting to double and doing the division.

谢谢,使用十进制可能也是一个解决方案。现在我采取了一种稍微不同的方法,这也很有效,至少只要我的程序运行时间不超过一周左右而不重新启动。我只记得程序启动时的性能计数器,并在转换为double并进行除法之前从当前计数器中减去它。

I'm not sure which solution would be fastest, I guess I'd have to benchmark that first.

我不确定哪种解决方案最快,我想我必须首先对此进行基准测试。

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

#1


Adion,

If you don't mind the performance hit, cast your QuadPart numbers to decimal instead of double before performing the division. Then cast the resulting number back to double.

如果您不介意性能命中,请在执行除法之前将QuadPart数字转换为十进制而不是两倍。然后将得到的数字重新加倍。

You are correct about the size of the numbers. It throws off the accuracy of the floating point calculations.

你对数字的大小是正确的。它抛弃了浮点计算的准确性。

For more about this than you probably ever wanted to know, see:

有关这方面的更多信息,请参阅:

What Every Computer Scientist Should Know About Floating-Point Arithmetic http://docs.sun.com/source/806-3568/ncg_goldberg.html

每个计算机科学家应该知道的关于浮点运算的内容http://docs.sun.com/source/806-3568/ncg_goldberg.html

#2


Thanks, using decimal would probably be a solution too. For now I've taken a slightly different approach, which also works well, at least as long as my program doesn't run longer than a week or so without restarting. I just remember the performance counter of when my program started, and subtract this from the current counter before converting to double and doing the division.

谢谢,使用十进制可能也是一个解决方案。现在我采取了一种稍微不同的方法,这也很有效,至少只要我的程序运行时间不超过一周左右而不重新启动。我只记得程序启动时的性能计数器,并在转换为double并进行除法之前从当前计数器中减去它。

I'm not sure which solution would be fastest, I guess I'd have to benchmark that first.

我不确定哪种解决方案最快,我想我必须首先对此进行基准测试。

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