Крошечные цифры вместо нуля? - PullRequest
5 голосов
/ 03 июня 2011

Я делал матричный класс (как учебное упражнение), и наткнулся на него и выполнил во время тестирования моей обратной функции.

Я ввел произвольную матрицу как таковую:

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

Но когда я попытался умножить их вместе, чтобы убедиться, что получил матрицу тождеств, я получаю:

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, как и ожидалось?

Все числа представлены в виде двойных.Вот моя перегрузка умножения:

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;
}

Ответы [ 5 ]

11 голосов
/ 03 июня 2011
9 голосов
/ 03 июня 2011

В вашей инвертированной матрице есть цифры, например, 0,250000000000000005, они просто округляются для отображения, поэтому вы видите маленькие округлые цифры, например, 0,25.

2 голосов
/ 03 июня 2011

Вы всегда будете сталкиваться с такими ошибками округления с плавающей запятой, особенно при работе с числами, которые не имеют точных двоичных представлений (т. Е. Ваши числа не равны 2 ^ (N) или 1 / (2 ^) N), где N - некоторое целочисленное значение).

При этом существует ряд способов повысить точность ваших результатов, и вы можете выполнить поиск в Google для численно устойчивых алгоритмов исключения Гаусса с использованием значений с плавающей запятой с фиксированной точностью.

Вы также можете, если хотите получить быстрый удар, включить математическую библиотеку с бесконечной точностью, которая использует рациональные числа, и, если вы выберете этот выбор, просто избегайте использования корней, которые могут создавать иррациональные числа. Существует ряд библиотек, которые могут помочь вам с использованием рациональных чисел, таких как GMP . Вы также можете создать класс rational самостоятельно, хотя имейте в виду, что сравнительно легко переполнить результаты нескольких математических операций, если вы используете только 64-разрядные значения без знака вместе с дополнительной переменной sign-flag для компонентов ваших рациональных чисел. Вот где GMP с его целочисленными строковыми объектами неограниченной длины пригодится.

2 голосов
/ 03 июня 2011

У вас не должно возникнуть проблем с этими числами, поскольку в этой конкретной матрице обратное значение равно степени 2 и может быть точно представлено.В целом, операции с числами с плавающей запятой приводят к небольшим ошибкам, которые могут накапливаться, и результаты могут быть удивительными.

В вашем случае, я уверен, что обратное неверно, и вы просто отображаете первые несколько цифр,То есть это не точно 0,25 (= 1/4), 0,75 (= 3/4) и т. Д.

1 голос
/ 03 июня 2011

Это просто простая ошибка с плавающей точкой.Даже double s на компьютерах не на 100% точны.Просто невозможно 100% точно представить десятичное число с основанием 10 в двоичном виде с конечным числом битов.

...