pow函数中发生了什么? [英] What is happening here in pow function?

查看:227
本文介绍了pow函数中发生了什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在这里看到了各种答案,描述了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); //打印输出100
y = pow(10,n); //打印输出99


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




输出:100 99 code>



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



我的gcc版本是4.9.2

更新

代码2

  int main()
{
int n = 2;
int x;
int y;
x = pow(10,2); //打印输出100
y = pow(10,n); //打印输出99
double k = pow(10,2);
double l = pow(10,n);


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


code

$ b $输出: 100 99 code>

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
和esp,-16
sub esp,48
call ___main
mov DWORD PTR [esp + 44],2
mov DWORD PTR [esp + 40],100 //有关行
fild DWORD PTR [esp + 44]
fstp QWORD PTR [esp + 8 ]
fld QWORD PTR LC0
fstp QWORD PTR [esp]
call _pow //相关行
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 $ b $ b .identGCC:(tdm-1)4.9.2
.def _pow; .scl 2; .type 32; .endef
.def _printf; .scl 2; .type 32; .endef


解决方案


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


您首先,如果你还没有,就放弃浮点数字在任何方面都是合理的或可预测的想法。 double only 接近实际数字,几乎所有您对 double 做的事情都可能是接近实际结果。

也就是说,如你所知, pow(10,n)结果在一个像 99.99999999999997 这样的值中,这个值近似为15位有效数字。然后你告诉它截断到最小的整数,所以它把大部分这些扔掉了。



(另外:很少有一个很好将一个 double 转换为 int 的原因。通常,您应该将其格式化为用 sprintf(%。0f,x),它可以正确舍入,或者使用 floor 函数,它可以处理可能超出 int 范围的浮点数。如果这些浮点数都不符合您的目的(例如在货币或日期计算中),那么您可能不应该使用浮动点数)。



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



在你的问题的评论中,amdn找到了 glibc可能与FP舍入有关的错误,可能与此相关并且,它详细描述了为什么会发生这种情况,以及它不违反C标准。 chux的回答也解决了这个问题。 (C不需要执行 IEEE 754 ,但即使如此, pow 不需要使用正确的四舍五入。)我仍然称这是一个glibc错误,因为它是一个不理想的属性。



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

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



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


其余功能用于优化目的。



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

[...]



ISO C90函数中止abs,acos,asin,atan2,atan,calloc,ceil,cosh,cos,exit,exp,fabs,floor,fmod,fprintf,fputs,frexp,fscanf,isalnum,isalpha ,iscn,isdigit,isgraph,islower,isprint,ispunct,isspace,isupper,isxdigit,tolower,toupper,实验室,ldexp,log10,日志,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,除非指定了 -fno-builtin (或),否则tan,vfprintf,vprintf和vsprintf 都将被识别为内置函数 -fno-builtin-function 是为单个函数指定的)。

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


I have seen various answer here that depicts Strange behavior of pow function in C.
But I Have something different to ask here.

In the below code I have initialized int x = pow(10,2) and int y = pow(10,n) (int n = 2).

In first case it when I print the result it shows 100 and in the other case it comes out to be 99.

I know that pow returns double and it gets truncated on storing in int, but I want to ask why the output comes to be different.

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

Why is the output coming out to be different. ?

My gcc version is 4.9.2

Update :

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

    }

Output : 100 99
100.000000 100.000000

Update 2 Assembly Instructions FOR CODE1

Generated Assembly Instructions GCC 4.9.2 using 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

解决方案

I know that pow returns double and it gets truncated on storing in int, but I want to ask why the output comes to be different.

You must first, if you haven't already, divest yourself of the idea that floating-point numbers are in any way sensible or predictable. double only approximates real numbers and almost anything you do with a double is likely to be an approximation to the actual result.

That said, as you have realized, pow(10, n) resulted in a value like 99.99999999999997, which is an approximation accurate to 15 significant figures. And then you told it to truncate to the largest integer less than that, so it threw away most of those.

(Aside: there is rarely a good reason to convert a double to an int. Usually you should either format it for display with something like sprintf("%.0f", x), which does rounding correctly, or use the floor function, which can handle floating-point numbers that may be out of the range of an int. If neither of those suit your purpose, like in currency or date calculations, possibly you should not be using floating point numbers at all.)

There are two weird things going on here. First, why is pow(10, n) inaccurate? 10, 2, and 100 are all precisely representable as double. The best answer I can offer is that the C standard library you are using has a bug. (The compiler and the standard library, which I assume are gcc and glibc, are developed on different release schedules and by different teams. If pow is returning inaccurate results, that is probably a bug in glibc, not gcc.)

In the comments on your question, amdn found a glibc bug to do with FP rounding that might be related and another Q&A that goes into more detail about why this happens and how it's not a violation of the C standard. chux's answer also addresses this. (C doesn't require implementation of IEEE 754, but even if it did, pow isn't required to use correct rounding.) I will still call this a glibc bug, because it's an undesirable property.

(It's also conceivable, though unlikely, that your processor's FPU is wrong.)

Second, why is pow(10, n) different from pow(10, 2)? This one is far easier. gcc optimizes away function calls for which the result can be calculated at compile time, so pow(10, 2) is almost certainly being optimized to 100.0. If you look at the generated assembly code, you will find only one call to pow.

The GCC manual, section 6.59 describes which standard library functions may be treated in this way (follow the link for the full list):

The remaining functions are provided for optimization purposes.

With the exception of built-ins that have library equivalents such as the standard C library functions discussed below, or that expand to library calls, GCC built-in functions are always expanded inline and thus do not have corresponding entry points and their address cannot be obtained. Attempting to use them in an expression other than a function call results in a compile-time error.

[...]

The ISO C90 functions 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 and vsprintf are all recognized as built-in functions unless -fno-builtin is specified (or -fno-builtin-function is specified for an individual function).

So it would seem you can disable this behavior with -fno-builtin-pow.

这篇关于pow函数中发生了什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆