初学者C难题:为什么25 = = 25?

时间:2013-09-20 00:20:36

标签: c arrays gcc codelite

我的中级C编程课程中的第一个实验室有一个简单的任务。我正在从用户那里获得8个双打阵列,然后再增加1个双倍。然后我检查数组中一个double的平方加上数组中其他双精度的平方,看它们是否等于给予程序的最后一个输入的平方(附加的双精度数)。

我的问题是,由于某种原因,当我的两个输入平方等于额外的输入平方时,我的编译器不这么认为。

请让我知道我在这里做错了什么;我正在使用Codelite和gnu gdb调试器和gcc编译器。

样本输入:4 3 3 3 3 3 3 3 5

#include <stdio.h>
#include <math.h>

int main(int argc, char **argv)
{
    int input[8];
    double inputSquared[8];
int pairs[16];
int i, j, k;  //i and j are for loop workers.
double x;
int numPairs = 0;
printf("Welcome to Lab 1.\nEnter 8 integers.  After each entry, press the enter button.\n");
printf("---------------------------------------\n");
for(i=0; i<8; i++){
    printf("Enter integer %d:", i+1);
    scanf("%d", &input[i]);
    printf("\n");
}

//printf("Now enter one more integer.\n  The sum of the squares of the following o this integer squared.\n");
printf("Enter an integer: ");
scanf("%lf", &x);

for(k = 0; k<8; k++){
    inputSquared[k] = pow((double)input[k], 2);
}

for(i = 0; i<8; i++){
    for(j = i + 1; j<8-1; j++){  //does not check for pairs reflexively. If 1 is in the array, it does not check 1^2 + 1^2.
        printf("%lf, %lf; %lf; %lf, %d \n", inputSquared[i], inputSquared[j], pow(x, 2.0), inputSquared[i] + inputSquared[j], ((inputSquared[i] + inputSquared[j]) == ((pow(x, 2.0)))));
        if(inputSquared[i] + inputSquared[j] == pow(x, 2.0)){
            pairs[2 * numPairs] = input[i];
            pairs[2 * numPairs + 1] = input[j];
            numPairs++;
        }
    }
}
if(numPairs == 1)
    printf("\nYou have %d pair:", numPairs);  // grammar condition for having 1 pair
else
    printf("\nYou have %d pairs:\n", numPairs);

for(i = 0; i < numPairs; i++)
    printf("(%d,%d)", pairs[2 * i], pairs[2 * i + 1]);

scanf("%lf", &x);

return 0;
}

4 个答案:

答案 0 :(得分:4)

如果你计算x的平方为:

x * x

甚至

(double)x * (double)x
那么你会得到一个确切的方格。换句话说,

4 * 4 + 3 * 3 == 5 * 5             => 1 (true)
4.0 * 4.0 + 3.0 * 3.0 == 5.0 * 5.0 => 1 (true)

事实上,

5 * 5 == 5.0 * 5.0                 => 1 (true)

但是,

5 * 5 == pow(5.0, 2.0)             => 0 (false)

因为数学库(编译器)不打算检查pow的第二个参数是否恰好是一个小整数。它只是通过计算pow(x, p) = ⅇp*ln(x)来计算价值。不幸的是,这个值通常不能表示为双重 - 事实上,它通常不是一个有理数,所以它根本没有真正的有限表示 - 并且数学库合理地结算由泰勒级数(或类似的东西)计算的近似逼近。

因此pow(5.0, 2.0)不仅略微不准确,而且计算起来也相当复杂。而5 * 5只是一个机器指令。你可以得出自己的结论。


很多人会告诉你,你应该“永远”比较浮点值是否相等。这个建议倾向于导致(或来自)一个心理模型,其中浮点数是一种模糊的,失焦的东西,它可以不可预测地波动,几乎就像它受到海森堡不确定性的影响一样。这实际上并不是一种考虑浮点数的好方法。每个浮点数都是一些理性值的精确表示;事实上,它是一个合理值的精确表示,其分母是2的幂,其分子为0或1到2之间的整数 k -1对于某些小k(52为双精度)最现代的CPU)。

因此,在哪种情况下浮点运算是精确的是可以预测的。例如,如果已知x是一个绝对值小于一年中秒数的整数,那么(double)x * x就是正确的。另一方面,分母(当减少到最低项)是奇数(或具有奇数因子)的分数永远不能精确地表示为浮点数。

答案 1 :(得分:2)

您绝不应该比较精确度的双重值。

使用epsilon(此处使用的epsilon值为0.00000001):

if (abs(x - y) < 0.00000001)
{
}

所以,

if ( abs(inputSquared[i] + inputSquared[j] - pow(x, 2.0)) < 0.0000001)
{ 

[Bbtw:SO上有很多类似的问题]

答案 2 :(得分:2)

当您使用double时,您正在调用所谓的“浮点算术”。实际上,这是表示数字的一种不精确的方式,因为不是每个实数都可以用有限的位精确表示。

因此,某些数字,例如,永远无法准确表示,而不是“25”您的计算机(而不是编译器),有25.00000000000071的结果 - 足够接近所有实际目的,但显然不等于25.

与IEEE-754浮点相关的其他怪癖很少 - 向0.0增加0.1倍并不一定达到1.0等等。

这就是为什么在实践中你永远不应该比较相等,但你应该比较两个数字之间的差值的绝对值是否小于(小,但足够大)epsilon。

const double espilon = 0.00000001
if (fabs(x - y) < epsilon) {
    // Assume approximately equal
}

一个好的(基于MATLAB,但概念适用于使用IEEE-754的所有内容,现在,这意味着无处不在)介绍正在发生的事情:

https://www2.bc.edu/~quillen/sp11/mt414/pres/floatarith.pdf

此外,pow()不是最合理的方格方式 - pow(x, m)内部计算exp(m log(x))以处理任意实数基数和指数。这种特殊的转换正是它失去精度的原因。

与基于乘法的方法相比,这也意味着pow()非常慢。

答案 3 :(得分:1)

这里的核心问题是你有一个低质量的数学库,其中pow无法返回好的结果。通过使用x*x代替pow(x, 2),您可以在此特定情况下解决此问题。

虽然浮点数不能完全代表所有数字(当然也不能用整数算术)和一些数学例程很难实现准确性,但是一个好的数学库力求返回准确的结果。

对于pow函数,在许多情况下应特别寻求准确性,包括结果可准确表示的那些。这包括pow(x, 2)等情况,其中x*x可以用浮点格式表示。

对于具有足够有效位的x的值,上面的解决方法将失败,x*x无法以浮点格式准确表示(或者当您尝试从输入中读取值时完全可以表示)。在这些情况下,其他答案建议您使用容差来比较浮点数。但是,这会引入额外的错误。它减少了假阴性(拒绝数字为不相等,即使它们在精确计算时是相等的),代价是增加误报(接受数字相等,即使它们确实是不相等的)。