1

在我的中级 C 编程课程中,我的第一个实验室有一个简单的任务。我从用户那里获取了一个包含 8 个双打的数组,然后是 1 个额外的双打。然后我检查数组中一个双精度的平方加上数组中其他双精度的平方,看看它们是否等于给程序的最后一个输入的平方(额外的双精度)。

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

请让我知道我在这里做错了什么;我将 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

4 回答 4

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(对于大多数现代 CPU 上的双精度数为 52)。

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

于 2013-09-20T00:52:48.243 回答
2

您永远不应该比较 double 值的准确性。

使用 epsilon(这里使用 epsilon 值 0.00000001):

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

所以,

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

[顺便说一句:SO上有很多类似的问题]

于 2013-09-20T00:22:42.390 回答
2

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

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

几乎没有其他与 IEEE-754 浮点相关的怪癖 - 将 0.1 添加到 0.0 十倍并不一定达到 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()与基于乘法的方法相比,这也意味着速度慢得离谱。

于 2013-09-20T00:33:54.310 回答
1

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

尽管浮点不能准确地表示所有数字(当然整数运算也不能)并且一些数学例程难以实现准确度,但一个好的数学库会努力返回准确的结果。

对于pow函数,在许多情况下应特别寻求准确性,包括结果可精确表示的情况。这包括诸如pow(x, 2), wherex*x完全可以用浮点格式表示的情况。

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

于 2013-09-20T01:09:08.753 回答