矩阵乘法的精度误差

时间:2010-04-27 12:22:02

标签: c# matrix floating-point double precision

在我的程序中编码矩阵乘法,我得到精度误差(大矩阵的结果不准确)。

这是我的代码。当前对象的数据存储在一行一行的扁平数组中。其他矩阵B的数据存储在扁平数组中,逐列(因此我可以使用指针算法)。

protected double[,] multiply (IMatrix B)
{
    int columns = B.columns;
    int rows = Rows;
    int size = Columns;

    double[,] result = new double[rows,columns];
    for (int row = 0; row < rows; row++)
    {
       for (int col = 0; col < columns; col++)
       {
           unsafe
           {
               fixed (float* ptrThis = data)
               fixed (float* ptrB = B.Data)
               {
                   float* mePtr = ptrThis + row*rows;
                   float* bPtr = ptrB + col*columns;
                   double value = 0.0;
                   for (int i = 0; i < size; i++)
                   {
                       value += *(mePtr++) * *(bPtr++);
                   }
                   result[row, col] = value;
               }
           }
       }
    }
}

实际上,代码有点复杂:我对多个块进行乘法运算(因此我不是从0到大小,而是从localStart到localStop),然后总结得到的矩阵。

我的问题:对于一个大矩阵,我得到精度错误:

NUnit.Framework.AssertionException: Error at (0,1)
    expected: <6.4209571409444209E+18>
     but was: <6.4207619776304906E+18>

有什么想法吗?

7 个答案:

答案 0 :(得分:1)

我原先声明您应该将floats转换为doubles。但是,正如您所指出的那样会破坏您的算法。

你可以尝试:

value += (double)*(mePtr++) * (double)*(bPtr++);

现在代码存在的问题是乘法是以float精度完成的,然后添加到double。首先投射到double会有所帮助。

使用中间double变量可能更清楚 - 但这取决于你。

如果这不能满足您的要求,那么您需要考虑使用decimal代替double。但是,这可能会导致性能下降,因此首先要做一些基准测试。

答案 1 :(得分:1)

也许您所要做的就是使用Kahan summation。但是你可以never expect使用浮点数学来完全一个特定的结果。

答案 2 :(得分:1)

原来这只是......一个错误。结束了,而不是:

float* mePtr = ptrThis + row*rows;
float* bPtr = ptrB + col*columns;

我的行的正确索引器是:

float* mePtr = ptrThis + row * size;
float* bPtr = ptrB + col * size;

对不起,这里不是真正的花哨答案。但感谢您的帮助!

答案 3 :(得分:0)

哼,它并没有真正解决你的问题,但在NUnit中,你可以允许有一个精度错误并选择这个epsilon的值

答案 4 :(得分:0)

作为起点,请在任何地方使用double而不是float

答案 5 :(得分:0)

至少,你应该在整个过程中使用双打。漂浮物非常不精确。

答案 6 :(得分:0)

这是一种称为“矩阵蠕变”的现象,如果您不能始终对矩阵进行标准化,则会在矩阵操作期间逐渐发生。