提问者:小点点

pow函数中发生了什么?


我在这里看到了各种答案,这些答案描述了C中pow函数的奇怪行为。
但我在这里有不同的问题要问。

在下面的代码中,我初始化了int x=pow(10,2)int y=pow(10, n)(int n=2)

在第一种情况下,当我打印结果时,它显示100,在另一种情况下,它显示为99

我知道pow返回double并且它在存储在int中时被截断,但我想问为什么输出会有所不同。

代码1

#include<stdio.h>
#include<math.h>
int main()
{
     int n = 2;
     int x;
     int y;
     x = pow(10,2);   //Printing Gives Output 100   
     y = pow(10,n);   //Printing Gives Output 99


     printf("%d %d" , x , y);

}

输出:100 99

为什么输出结果会有所不同。?

我的gcc版本是4.9.2

更新:

代码2

int main()
    {
         int n = 2;
         int x;
         int y;
         x = pow(10,2);   //Printing Gives Output 100   
         y = pow(10,n);   //Printing Gives Output 99
         double k = pow(10,2);
         double l = pow(10,n);


         printf("%d %d\n" , x , y);
         printf("%f %f\n" , k , l);

    }

输出:100 99
100.000000 100.000000

更新CODE1的2个组装说明

使用gcc-S-masm=intel生成GCC4.9.2的程序集说明:

    .LC1:
    .ascii "%d %d\0"
    .text
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
    push    ebp
    mov ebp, esp
    and esp, -16
    sub esp, 48
    call    ___main
    mov DWORD PTR [esp+44], 2
    mov DWORD PTR [esp+40], 100      //Concerned Line
    fild    DWORD PTR [esp+44]
    fstp    QWORD PTR [esp+8]
    fld QWORD PTR LC0
    fstp    QWORD PTR [esp]
    call    _pow                    //Concerned Line
    fnstcw  WORD PTR [esp+30]
    movzx   eax, WORD PTR [esp+30]
    mov ah, 12
    mov WORD PTR [esp+28], ax
    fldcw   WORD PTR [esp+28]
    fistp   DWORD PTR [esp+36]
    fldcw   WORD PTR [esp+30]
    mov eax, DWORD PTR [esp+36]
    mov DWORD PTR [esp+8], eax
    mov eax, DWORD PTR [esp+40]
    mov DWORD PTR [esp+4], eax
    mov DWORD PTR [esp], OFFSET FLAT:LC1
    call    _printf
    leave
    ret
    .section .rdata,"dr"
    .align 8
LC0:
    .long   0
    .long   1076101120
    .ident  "GCC: (tdm-1) 4.9.2"
    .def    _pow;   .scl    2;  .type   32; .endef
    .def    _printf;    .scl    2;  .type   32; .endef

共3个答案

匿名用户

我知道pow返回双精度,并且它在存储在int中时被截断,但我想问为什么输出会有所不同。

你必须首先,如果你还没有,摆脱浮点数在任何方面都是合理或可预测的想法。double只近似实数,你用double做的几乎任何事情都可能是实际结果的近似。

也就是说,正如你已经意识到的,pow(10, n)产生了一个像99.99999999999997这样的值,这是一个精确到15个有效数字的近似值。然后你告诉它截断到小于这个数的最大整数,所以它扔掉了大部分。

(旁白:很少有很好的理由将double转换为int。通常你应该使用sprintf("%.0f", x)之类的东西来格式化它以进行显示,它可以正确舍入,或者使用地板函数,它可以处理可能超出int范围的浮点数。如果这两者都不适合你的目的,比如在货币或日期计算中,你可能根本不应该使用浮点数。)

这里有两件奇怪的事情。首先,为什么pow(10, n)不准确?10、2和100都可以精确地表示为double。我能提供的最好答案是,你使用的C标准库有一个bug。(编译器和标准库,我假设是gcc和glibc,是根据不同的发布计划和由不同的团队开发的。如果pow返回不准确的结果,那可能是glibc的bug,而不是gcc。)

在对你的问题的评论中,amdn发现了一个与FP四舍五入有关的glibcbug,这可能与另一个Q有关

(也可以想象,尽管不太可能,你的处理器的FPU是错误的。)

其次,为什么pow(10, n)pow(10,2)不同?这个要简单得多。gcc优化了可以在编译时计算结果的函数调用,因此pow(10,2)几乎可以肯定被优化为100.0。如果您查看生成的汇编代码,您会发现只有一次对pow的调用。

GCC手册第6.59节描述了哪些标准库函数可以这样处理(点击完整列表的链接):

其余功能用于优化目的。

除了具有库等价物(如下面讨论的标准C库函数)或扩展到库调用的内置函数之外,GCC内置函数始终是内联扩展的,因此没有相应的入口点,并且无法获得它们的地址。尝试在函数调用以外的表达式中使用它们会导致编译时错误。

[...]

ISOC90函数abort, abs,acos,asin,atan2,atan,calloc,ceil,cosh,cos,出口,exp,fabs,地板,fmod,fprintf,fput,frexp,fscanf,isalnum,isalpha,iscntrl,isdigit,isgraph,islower,isprint,issptt,isspace,istop,isxdigit,tolower,touch pper,labs,ldexp,log10,log,malloc,memchr,memcmp,memcpy,memset,modf,pow,printf,putchar,put,scanf,sinh,sin,snprintf,sprintf,sqrt,sscanf,strcat,strchr,strcmp,strcpy,strcspn,strlen,strncat,strncmp,strncmp,strncpy,strncpy,strncpy,strpbrk,strrchr,str-fno-builtin-function为单个函数指定)。

因此,您似乎可以使用-fno-builtin-pow禁用此行为。

匿名用户

为什么输出结果不同。?(在更新的附加代码中)

我们不知道价值观有那么大的不同。

当比较int/double的文本时,请务必以足够的精度打印double,以查看它是100.000000还是靠近100.000000或十六进制以消除所有疑问。

printf("%d %d\n" , x , y);
// printf("%f %f\n" , k , l);
// Is it the FP number just less than 100?
printf("%.17e %.17e\n" , k , l);  // maybe 9.99999999999999858e+01
printf("%a %a\n" , k , l);        // maybe 0x1.8ffffffffffff0000p+6

为什么输出会有所不同。?(在原始代码中)

C没有指定大多数的准确性

// Higher quality functions return 100.0
pow(10,2) --> 100.0   
// Lower quality and/or faster one may return nearby results
pow(10,2) --> 100.0000000000000142...
pow(10,2) --> 99.9999999999999857...

将浮点数(FP)分配给int简单地删除分数,无论分数与1.0有多接近

当将FP转换为整数时,更好地控制转换和舍入以应对微小的计算差异。

// long int lround(double x);
long i = lround(pow(10.0,2.0));

匿名用户

你不是第一个发现这个的人。这是2013年的讨论形式:pow()强制转换为整数,意外结果

我推测tcc人生成的汇编代码导致第二个值在计算完一个非常接近100的结果后被四舍五入。就像米基乔夫在那篇历史性的文章中所说的,看起来bug已经修复了。