powfunction在这里发生了什么?

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

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

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

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

CODE1

 #include #include 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 4.9.2使用gcc -S -masm=intel

  .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 

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

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

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

(旁白:很少有很好的理由将double转换为int 。通常你应该将它格式化为sprintf("%.0f", x) ,它可以正确舍入,或使用floorfunction,它可以处理可能超出int范围的浮点数。如果这些都不适合你的目的,比如货币或日期计算,可能你根本不应该使用浮点数。)

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

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

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

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

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

其余function仅用于优化目的。

除了具有库等价物的内置函数(例如下面讨论的标准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,vfprintf,vprintf和vsprintf 都被认为是内置的-in函数,除非指定了-fno-builtin (或者为单个函数指定了-fno-builtin-function function)。

所以看起来你可以用-fno-builtin-pow禁用这种行为。

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

我们不知道价值观是不同的。

比较int/double的文本时,请务必以足够的精度打印double 精度,以查看它是100.000000还是仅接近 100.000000或hex以消除所有疑问。

 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年的讨论forms: pow()转换为整数,意外结果

我推测tcc人员产生的汇编代码在计算出真正接近100的结果后导致第二个值被向下舍入。就像mikijov在那个历史性post中说的那样,看起来已经修复了bug。

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

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

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