pow() cast to integer, unexpected result

2019-01-12 09:31发布

I have some problems using an integer cast for the pow() function in the C programming language. The compiler I'm using is the Tiny C Compiler (tcc version 0.9.24) for the Windows platform. When executing the following code, it outputs the unexpected result 100, 99:

#include <stdio.h>
#include <math.h>

int main(void)
{
    printf("%d, ", (int) pow(10, 2));
    printf("%d", (int) pow(10, 2));
    return 0;
}

However, at this online compiler the output is as expected: 100, 100. I don't know what is causing this behavior. Any thoughts? Programming error from me, compiler bug?

3条回答
孤傲高冷的网名
2楼-- · 2019-01-12 09:36

Some investigation in assembly code. (OllyDbg)

#include <stdio.h>
#include <math.h>

int main(void)
{
    int x1 = (int) pow(10, 2);
    int x2 = (int) pow(10, 2);
    printf("%d %d", x1, x2);
    return 0;
}

The related assembly section:

FLD QWORD PTR DS:[402000]   // Loads 2.0 onto stack
SUB ESP,8
FSTP QWORD PTR SS:[ESP]
FLD QWORD PTR DS:[402008]   // Loads 10.0 onto stack
SUB ESP,8
FSTP QWORD PTR SS:[ESP]
CALL <JMP.&msvcrt.pow>      // Calls pow API
                            // Returned value 100.00000000000000000
...


FLDCW WORD PTR DS:[402042]  //   OH! LOOK AT HERE
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...

FLD QWORD PTR DS:[402010]   // Loads 2.0 onto stack
SUB ESP,8
FSTP QWORD PTR SS:[ESP]
FLD QWORD PTR DS:[402018]   // Loads 10.0 onto stack
SUB ESP,8
FSTP QWORD PTR SS:[ESP]
CALL <JMP.&msvcrt.pow>      // Calls pow API again
                            // Returned value 99.999999999999999990

The generated code for two calls is the same, but the outputs are different. I don't know why tcc put FLDCW there. But the main reason of two different values are that line.

Before that line the round Mantissa Precision Control Bits is 53bit (10), but after execution of that line (it loads FPU register control) it will set to 64bits (11). On the other hand Rounding Control is nearest so 99.999999999999999990 is the result. Read more...

 

enter image description here

 


Solution:

After using (int) to cast a float to an int, you should expect this numeric error, because this casting truncates the values between [0, 1) to zero.

Assume the 102 is 99.9999999999. After that cast, the result is 99.

Try to round the result before casting it to integer, for example:

printf("%d", (int) (floor(pow(10, 2) + 0.5)) );

 

查看更多
爷的心禁止访问
3楼-- · 2019-01-12 09:49

It seems that the rounding method may change, thus requiring an ASM instruction finit to reset the FPU. In FreeBASIC on Windows, I get 99.9999 even in the first try, and so I think for you after the first try it would be a consistent 99.9999. (But I call this undefined behavior indeed, more than a bug in the C runtime's pow().)

So my advice is not do the conversion with round down. To avoid such issues, use, for example:

int x1 = (int)(pow(10, 2)+.5);

查看更多
走好不送
4楼-- · 2019-01-12 10:00

You found a bug in tcc. Thanks for that. The patch has just been commited to the repository. It will be included in the next release, but that might take a while. You can of course pull the source and build it yourself. The patch is here

http://repo.or.cz/w/tinycc.git/commitdiff/73faaea227a53e365dd75f1dba7a5071c7b5e541

查看更多
登录 后发表回答