pow功能在这里发生了什么?

时间:2017-02-10 16:48:33

标签: c gcc compiler-optimization pow gcc4.9

我在这里看到的各种答案描述了C. {中pow函数的奇怪行为 但我在这里要问一些不同的东西。

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

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

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

CODE1

#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);

}

Output : 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

更新2 CODE1

的汇编指令

使用gcc -S -masm=intel

生成汇编指令 GCC 4.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

5 个答案:

答案 0 :(得分:7)

  

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

首先,如果你还没有,你必须放弃浮点数在任何方面是明智或可预测的想法。 double只有近似实数,而使用double所做的几乎任何事情都可能是实际结果的近似值。

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

(除此之外:很少有良好的理由将double转换为int。通常您应该将其格式化以显示sprintf("%.0f", x)之类的内容},正确舍入,或使用floor函数,它可以处理可能超出int范围的浮点数。如果这些都不适合您的目的,例如货币或日期计算,可能你根本不应该使用浮点数。)

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

在您的问题评论中,amdn找到了可能相关的glibc bug to do with FP roundinganother Q&A,其中详细说明了为什么会发生这种情况以及它是如何违反C标准的。 chux的答案也解决了这个问题。 (C不需要IEEE 754的实现,但即使它确实如此,pow也不需要使用正确的舍入。)我仍然称这是一个glibc错误,因为它是一个不受欢迎的属性。

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

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

GCC manual, section 6.59描述了可以用这种方式处理哪些标准库函数(按照完整列表的链接):

  

其余功能仅用于优化目的。

     

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

     

[...]

     

ISO C90函数abort,abs,acos,asin,atan2,atan,calloc,ceil,cosh,cos,exit,exp,fabs,floor,fmod,fprintf,fputs,frexp,fscanf,isalnum,isalpha,iscntrl ,isdigit,isgraph,islower,isprint,ispunct,isspace,isupper,isxdigit,tolower,toupper,labs,ldexp,log10,log,malloc,memchr,memcmp,memcpy,memset,modf, pow , printf,putchar,puts,scanf,sinh,sin,snprintf,sprintf,sqrt,sscanf,strcat,strchr,strcmp,strcpy,strcspn,strlen,strncat,strncmp,strncpy,strpbrk,strrchr,strspn,strstr,tanh,tan,除非指定-fno-builtin(或为单个函数指定-fno-builtin-function),否则vfprintf,vprintf和vsprintf 都被识别为内置函数

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

答案 1 :(得分:2)

  

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

我们不知道是不同的。

比较double中的文字时,请务必使用sufficient precision打印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 或十六进制以消除所有疑问。

<math.h>
  

为什么输出结果不同。 ? (在原始代码中)

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... 函数的准确性。以下是所有符合要求的结果。

int

将一个浮点(FP)编号分配给// long int lround(double x); long i = lround(pow(10.0,2.0)); 简单会丢弃该分数,无论分数与1.0的接近程度如何

将FP转换为整数时,最好控制转换并进行舍入以应对较小的计算差异。

<resource type>-b+<language code>[+<country code>]

答案 2 :(得分:0)

你不是第一个发现这个的人。以下是2013年的讨论形式: pow() cast to integer, unexpected result

我推测tcc人员生成的汇编代码在计算真正接近100的结果后导致第二个值被舍入。 就像mikijov在那篇历史性文章中所说的那样,看起来已经修好了。

答案 3 :(得分:0)

正如其他人所提到的,由于浮点截断,代码2返回99。 Code 1返回不同且正确答案的原因是libc优化。

当功率是小的正整数时,执行重复乘法操作更有效。更简单的路径删除了舍入。由于这是内联的,因此您不会看到函数调用。

答案 4 :(得分:-1)

你愚弄了它,认为输入是真实的,所以它给出了一个近似的答案,恰好略低于100,例如99.999999然后被截断为99。