pow函数中发生了什么? [英] What is happening here in pow function?
问题描述
我在这里看到了各种答案,描述了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屋!