矩阵乘法的精度误差

发布于 2024-08-30 14:33:02 字数 1239 浏览 1 评论 0原文

在我的程序中编写矩阵乘法时,出现精度错误(大型矩阵的结果不准确)。

这是我的代码。当前对象的数据逐行存储在扁平数组中。其他矩阵 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 到 size,而是从 localStart 到 localStop),然后对结果矩阵求和。

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

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

有什么想法吗?

Coding a matrix multiplication in my program, I get precision errors (inaccurate results for large matrices).

Here's my code. The current object has data stored in a flattened array, row after row. Other matrix B has data stored in a flattened array, column after column (so I can use pointer arithmetic).

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

Actually, the code is a bit more complicated : I do the multiply thing for several chunks (so instead of having i from 0 to size, I go from localStart to localStop), then sum up the resulting matrices.

My problem : for a big matrix I get precision error :

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

Any idea ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

顾铮苏瑾 2024-09-06 14:33:02

也许您所要做的就是使用 Kahan 求和。但您永远不会指望得到确切特定的浮点数学结果。

Perhaps all you have to do is use Kahan summation. But you can never expect to get exactly a specific result with floating-point math.

時窥 2024-09-06 14:33:02

事实证明这只是......一个错误。最终结果是:

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

我的行的正确索引器是:

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

抱歉,这里的答案并不是很奇特。但感谢您的帮助!

Turns out it was just ... a bug. Ended up that instead of having :

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

The correct indexers for my rows were :

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

Sorry for that, not really fancy answer here. But thanks for the help !

绝影如岚 2024-09-06 14:33:02

我最初说过,您应该将浮点型转换为双精度型。然而,正如您所指出的,这会破坏您的算法。

您可以尝试:

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

您的代码现在存在的问题是乘法是以float精度完成的,然后添加到double。首先转换为 double 会在一定程度上有所帮助。

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

如果这不能给您带来所需的准确性,那么您需要考虑使用decimal而不是double。但是,这可能会导致性能下降,因此首先进行一些基准测试。

I originally stated that you should convert the floats to doubles. However, as you point out that will break your algorithm.

You could try:

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

A problem with your code as it now stands is that the multiplication is being done in float precision then added to a double. Casting to double first will help to some extent.

It might be clearer to use intermediate double variables - but that's up to you.

If this doesn't give you the desire accuracy then you'll need to consider using decimal instead of double. However, this may result in a performance hit so do some benchmarks first.

梅窗月明清似水 2024-09-06 14:33:02

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

Hem, it doesn't really solve your problem but in NUnit, you can allow to have a precision error and choose the value of this epsilon

羞稚 2024-09-06 14:33:02

首先,在任何地方使用 double 而不是 float

As a starting point, use double everywhere instead of float.

淡墨 2024-09-06 14:33:02

至少,你应该自始至终都使用双打。浮动非常不精确。

At the very least, you should be using doubles throughout. Floats are very imprecise.

蘸点软妹酱 2024-09-06 14:33:02

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

This is a phenomenon called "Matrix Creep" which happens gradually during matrix manipulations if you don't consistently normalize your matrices.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文