1

中級 C プログラミング クラスの最初のラボで、簡単なタスクがあります。ユーザーから 8 つの double の配列を取得し、さらに 1 つの double を取得しています。次に、配列からの 1 つの double の 2 乗と、配列内の他の double の 2 乗をチェックして、それらがプログラムに与えられた最後の入力 (追加の double) の 2 乗と等しいかどうかを確認します。

私の問題は、何らかの理由で、2 つの入力の 2 乗が追加の入力の 2 乗に等しい場合、コンパイラがそうではないと判断することです。

ここで何が間違っているのか教えてください。gnu gdb デバッガーと gcc コンパイラーで Codelite を使用しています。

サンプル入力: 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 の 2 乗を次のように計算した場合:

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) は、 の 2 番目の引数がたまたま小さな整数であるかどうかを確認することを気にしないためです。先に進み、 を計算して値を計算します。残念ながら、その値は一般に double として表現できません。実際、通常は有理数でさえないため、実際には有限の表現はまったくありません。数学ライブラリは合理的に解決します。 Taylor級数(または同様のもの)によって計算された近似値。pow(x, p) = ⅇp*ln(x)

したがって、 はpow(5.0, 2.0)わずかに不正確であるだけでなく、計算も非常に複雑です。一方5 * 5、単一の機械語命令です。あなたはおそらくあなた自身の結論を引き出すことができます。


多くの人は、浮動小数点値が等しいかどうかを「決して」比較すべきではないと言うでしょう。そのアドバイスは、浮動小数点数が一種のあいまいで焦点が合っていないものであり、ハイゼンベルグの不確実性にさらされているかのように、予測不能に変動する可能性があるというメンタル モデルにつながる (またはそこから来る) 傾向があります。これは実際には、浮動小数点数を考える上であまり良い方法ではありません。すべての浮動小数点数は、有理数の正確な表現です。実際、これは、分母が 2 の累乗で分子が 0 であるか、一部の小さい k に対して 1 から 2 k -1 の整数 (最近のほとんどの CPU では double の場合は 52) である有理値の正確な表現です。

したがって、どのような状況で浮動小数点演算が正確になるかはかなり予測可能です。たとえば、xが絶対値が 1 年の秒数より小さい整数であることがわかっている場合、(double)x * xは正確に正しいです。一方、分母が (最小項まで縮約された場合に) 奇数である (または奇数の係数を持つ) 分数は、浮動小数点数として正確に表すことはできません。

于 2013-09-20T00:52:48.243 に答える
2

使用しdoubleているときは、「浮動小数点演算」と呼ばれるものを呼び出しています。実際には、すべての実数を有限数のビットで正確に表現できるわけではないため、これは数値を表現するための不正確な方法です。

その結果、たとえば、一部の数値は正確に表現できず、コンピューター (コンパイラーではない) の結果は "25" ではなく、たとえば 25.00000000000071 になります。すべての実用的な目的には十分に近いですが、明らかに等しくはありません。 25に。

IEEE-754 浮動小数点に関連するその他の癖はほとんどありません。0.1 を 0.0 に 10 倍しても、必ずしも 1.0 に達するとは限りません。

これが、実際には、等しいかどうかを比較するべきではなく、2 つの数値の差の絶対値が (小さいが十分に大きい) イプシロンより小さいかどうかを比較する必要がある理由です。

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 に答える
2

正確さのために double 値を比較しないでください。

イプシロンを使用します (ここでは 0.00000001 のイプシロン値が使用されます):

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

それで、

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

[Bbtw: SO にはかなりの数の同様の質問があります]

于 2013-09-20T00:22:42.390 に答える
1

ここでの中心的な問題は、pow良い結果を返さない低品質の数学ライブラリがあることです。x*xこの特定のケースでは、代わりにを使用することで、この問題を回避できますpow(x, 2)

浮動小数点はすべての数値を正確に表すことはできず (もちろん整数演算もできません)、一部の数学ルーチンは精度を実装するのが困難ですが、優れた数学ライブラリは正確な結果を返すよう努めています。

関数についてはpow、結果が正確に表現できる場合など、特に精度が求められる場合が数多くあります。これには、浮動小数点形式で正確に表現pow(x, 2)できるなどのケースが含まれます。x*x

上記の回避策は、浮動小数点形式で正確に表現できないx十分な有効ビットを持つの値の場合 (または、正確に表現できない入力から値を読み取ろうとした場合) に失敗します。x*xこれらの場合、他の回答では、許容誤差を使用して浮動小数点数を比較するようアドバイスされています。ただし、これにより追加のバグが発生します。これにより、偽陰性 (正確に計算された場合は等しいにもかかわらず、数値が等しくないとして拒否される) が減少しますが、偽陽性 (実際には数値が等しくない場合でも数値が等しいと見なされる) が増加します。

于 2013-09-20T01:09:08.753 に答える