5

私は(学習演習として)行列クラスを作成していて、逆関数をテストしているときに出くわして問題が発生しました。

私はそのように任意の行列を入力します:

2 1 1
1 2 1
1 1 2

そして逆数を計算するためにそれを手に入れました、そして私は正しい結果を得ました:

0.75 -0.25 -0.25
-0.25 0.75 -0.25
-0.25 -0.25 0.75

しかし、2つを掛け合わせて、次の単位行列を確実に取得しようとすると、次のようになります。

1 5.5111512e-017 0
0 1 0
-1.11022302e-0.16 0 1

なぜこれらの結果が得られるのですか?いくつかの丸め誤差を理解できる奇妙な数値を乗算している場合は理解できますが、その合計は次のようになります。

2 * -0.25 + 1 * 0.75 + 1 * -0.25

これは明らかに0であり、5.111512e-017ではありません

手動で計算を行うようにした場合。例えば:

std::cout << (2 * -0.25 + 1 * 0.75 + 1 * -0.25) << "\n";

期待通り0になりますか?

すべての数値はdoubleとして表されます。これが私の乗算の過負荷です:

Matrix operator*(const Matrix& A, const Matrix& B)
{
    if(A.get_cols() == B.get_rows())
    {
        Matrix temp(A.get_rows(), B.get_cols());
        for(unsigned m = 0; m < temp.get_rows(); ++m)
        {
            for(unsigned n = 0; n < temp.get_cols(); ++n)
            {
                for(unsigned i = 0; i < temp.get_cols(); ++i)
                {
                    temp(m, n) += A(m, i) * B(i, n);
                }
            }
        }

        return temp;
    }

    throw std::runtime_error("Bad Matrix Multiplication");
}

およびアクセス機能:

double& Matrix::operator()(unsigned r, unsigned c)
{
    return data[cols * r + c];
}

double Matrix::operator()(unsigned r, unsigned c) const
{
    return data[cols * r + c];
}

逆関数を見つける関数は次のとおりです。

Matrix Inverse(Matrix& M)
{
    if(M.rows != M.cols)
    {
        throw std::runtime_error("Matrix is not square");
    }

    int r = 0;
    int c = 0;
    Matrix augment(M.rows, M.cols*2);
    augment.copy(M);

    for(r = 0; r < M.rows; ++r)
    {
        for(c = M.cols; c < M.cols * 2; ++c)
        {
            augment(r, c) = (r == (c - M.cols) ? 1.0 : 0.0);
        }
    }

    for(int R = 0; R < augment.rows; ++R)
    {
        double n = augment(R, R);
        for(c = 0; c < augment.cols; ++c)
        {
            augment(R, c) /= n;
        }

        for(r = 0; r < augment.rows; ++r)
        {
            if(r == R) { continue; }
            double a = augment(r, R);

            for(c = 0; c < augment.cols; ++c)
            {
                augment(r, c) -= a * augment(R, c);
            }
        }
    }

    Matrix inverse(M.rows, M.cols);
    for(r = 0; r < M.rows; ++r)
    {
        for(c = M.cols; c < M.cols * 2; ++c)
        {
            inverse(r, c - M.cols) = augment(r, c);
        }
    }

    return inverse;
}
4

5 に答える 5

11

この論文を読んでください:すべてのコンピューター科学者が浮動小数点演算について知っておくべきこと

于 2011-06-03T18:04:51.627 に答える
9

反転マトリックスに0.250000000000000005のような数値があり、表示のために丸められているだけなので、0.25のような小さな丸い数値が表示されます。

于 2011-06-03T18:08:51.810 に答える
2

この特定のマトリックスでは、逆数はすべて2の累乗であり、正確に表される可能性があるため、これらの数値に問題はありません。一般に、浮動小数点数の演算は、蓄積される可能性のある小さなエラーを導入し、結果は驚くべきものになる可能性があります。

あなたの場合、その逆は不正確であり、最初の数桁を表示しているだけだと確信しています。つまり、正確には 0.25(= 1/4)、0.75(= 3/4)などではありません。

于 2011-06-03T18:10:05.223 に答える
2

特に、正確な2進表現を持たない数値を処理する場合(つまり、数値が2 ^(N)または1 /(2 ^ N)に等しくない場合は、常にこのような浮動小数点の丸め誤差が発生します。ここで、Nは整数値です)。

そうは言っても、結果の精度を上げる方法はいくつかあります。固定精度の浮動小数点値を使用して、数値的に安定したガウスの消去法アルゴリズムをグーグル検索することをお勧めします。

You can also, if you are willing to take a speed hit, incorporate an inifinite-precision math library that uses rational numbers, and if you take that choice, just avoid the use of roots which can create irrational numbers. There are a number of libraries out there that can help you with the use of rational numbers, such as GMP. You can also make a rational class yourself, although beware it's relatively easy to overflow the results of multiple math operations if you are only using unsigned 64-bit values along with an extra sign-flag variable for the components of your rational numbers. That's where GMP, with it's unlimited-length integer string objects comes in handy.

于 2011-06-03T18:29:55.707 に答える
1

これは単純な浮動小数点エラーです。コンピュータ上のsでさえdouble100%正確ではありません。有限のビット数の2進数で基数10の10進数を100%正確に表す方法はありません。

于 2011-06-03T18:04:40.100 に答える