pow函数输出异常

4
以下是C语言代码
int main(){
    int n=10;    
    int t1=pow(10,2);
    int t2=pow(n,2);
    int t3=2*pow(n,2);
    printf("%d\n",t1);
    printf("%d\n",t2);
    printf("%d\n",t3);
    return (0);
}

给出以下输出结果:

100
99
199

我正在使用devcpp编译器。 这似乎毫无意义,对吧? 有什么想法吗? (pow(10,2)可能是99.9999之类的东西,并不能解释第一个输出。此外,即使我包含math.h,我也得到了相同的输出)


9
pow函数返回一个浮点类型double。您正在将其用作整型int,这并不合适。 - Dan Fego
3
pow 函数返回的是 double 类型而不是 int 类型,你正在截取数字中的非整数部分。 - Alexander Oh
3
你的代码不是有效的C语言,因为你调用了未声明的pow函数。 - R.. GitHub STOP HELPING ICE
1
这个问题确实很有趣,但由于问题包含虚假代码(缺少包含等),这使得诊断实际问题变得困难,我不太愿意点赞它... - R.. GitHub STOP HELPING ICE
@pinkpanther,我认为这取决于编译器等因素...我已经创建了一个循环1..1000,每次它们分别是10099199。我相信在你的机器上使用你的编译器时,每次输出将是100100200 - yulian
显示剩余13条评论
3个回答

6
你正在使用一种质量较差的数学库。一个好的数学库将返回精确的结果,对于那些可以精确表示的值而言。
通常,数学库例程必须是近似值,因为浮点格式不能精确地表示准确的数学结果,并且计算各种函数是困难的。然而,对于pow,有一些有限的可以精确表示的结果,例如10的2次方。一个好的数学库将确保这些结果被正确返回。你正在使用的库没有做到这一点。

不行 - 这可能导致实现变慢,这对于正确使用浮点数运算的开发人员来说是不可接受(或不太理想)的。 - djechlin
  1. 我同意你的第一句话,“质量不佳的数学库”。如果在二进制64(典型double)或二进制32(典型float)中有一个好的并且速度快的pow(),即使不使用“可精确表示”的测试,也可能导致pow(10,2)的非精确答案。但是像pow()这样的函数允许偏移1 ULP,因此它变成了OP的教学时刻。
  2. 对于pow(x,y),当x(精确整数或非精确整数)<0如pow(-2.1,-3.0)时,需要对y进行整数精确性测试。
- chux - Reinstate Monica
@chux:(1)除了非规范性附录H中提到的条款5.2.8(我在当前版本(2012年(E))的LIA-1中找不到),我没有在C标准中看到1-ULP要求。我没有在规范性但可选的附录F中看到精度规范。(2)对于负x需要确切整数y的要求是关于函数定义域的问题,而不是结果的准确性,并且在这个问题中并不相关。 - Eric Postpischil
2
@chux:通常,任何实现具有≤ 1 ULP精度的库都会实际瞄准<1 ULP。这被称为“忠实舍入”,并且有一个重要的原因。忠实舍入保证在数学结果实际可表示的情况下没有任何误差,因为如果返回的结果是x,而数学结果是x,则|x-x | <1 ULP保证x除了x之外不能是任何可表示的值。因此,实现忠实舍入的库必须返回pow(10,2)的确切结果。 - Eric Postpischil
是的。这就是为什么我同意“质量较差的数学库”的观点,并在此答案上投了第一个赞成票。 - chux - Reinstate Monica
显示剩余3条评论

3
把结果计算以 double 的形式存储。使用 %f 而不是 %d 来打印 double。你会发现,99 实际上更像 99.999997,这应该更有意义。
通常,当处理任何浮点数运算时,你应该假设结果是近似的;也就是说,稍微偏离一点。因此,当你想要精确的结果 - 就像在这里一样 - 你会遇到麻烦。
在使用函数之前,你应始终了解它们的返回类型。例如,请参见 cplusplus.com
double pow (double base, double exponent); /* C90 */

从其他回答中,我了解到有一些情况下,您可以期望 pow 或其他浮点数计算是精确的。一旦您理解了困扰浮点数计算的必要不确定性,请参考以下内容。


2
这不是关于函数返回类型或浮点运算方式的问题。在这种情况下,pow函数可以返回精确结果。它之所以没有返回精确结果,是因为实现较差,而不是因为格式不支持或结果无法计算。 - Eric Postpischil
此外,IEEE规定当正确答案是精确的时候必须得到正确的结果,因此未能交付是一个错误。 - R.. GitHub STOP HELPING ICE
@R..:您是指IEEE 754-2008标准还是其他标准?哪个条款?754-2008 9.2建议语言标准定义某些函数,包括pow,以正确舍入方式实现,但它并不强制执行。它使用“应该”并定义“应该”表示有几种可能性特别适合,但不排除其他可能性或更喜欢但不是必需的。是否有关于确切可表示情况的其他文本? - Eric Postpischil
@EricPostpischil 看编辑。我把我的回答集中在了我所理解的浮点数学上。我认为更重要的是让OP明白必要的不精确性,然后再理解什么时候可以期望得到精确的结果。换句话说,如果OP使用更精确的pow函数,鉴于OP对float和精度的先前了解,结果可能是正确的,但这只是巧合。 - djechlin

2
你的变量 t1t2t3 必须是 double 类型,因为 pow() 返回的是 double。

但如果你确实希望它们是int类型,请使用round()函数。

int t1 = pow(10,2);
int t2 = round(pow(n,2));
int t3 = 2 * round(pow(n,2));

它将返回的值 99.9...199.9... 四舍五入为 100.0200.0。然后 t2 == 100,因为它是 int 类型,t3 也是如此。

输出结果将为:

100
100
200

因为 round 函数返回最接近 x 的整数值,将一半的情况远离零进行四舍五入,与当前的取整方向无关。

更新:这里是math.h的评论:

/* Excess precision when using a 64-bit mantissa for FPU math ops can
cause unexpected results with some of the MSVCRT math functions.  For
example, unless the function return value is stored (truncating to
53-bit mantissa), calls to pow with both x and y as integral values
sometimes produce a non-integral result. ... */

好的,我不知道99.99999...是什么意思,也不知道为什么"double类型的规范"保证这将是结果。 - djechlin
1
@JulianKhlevnoy:错了。double值确实可以是一个整数;只是它们不一定要求是整数。(而且数学计算的结果通常也不会是整数) - SLaks
@djechlin,我已经编辑了我的答案。还有另一个函数可以将值四舍五入到最近的整数。 (但如果我错了,请告诉我) - yulian
2
取消了踩。我并不认为这是一个好答案,因为它掩盖了提问者对double如何工作的无知,但我认为这至少是目前情况下正确的代码。 - djechlin
@chux,"1 ULP" 是什么意思? - yulian
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接