在我的程序中编码矩阵乘法,我得到精度误差(大矩阵的结果不准确)。
这是我的代码。当前对象的数据存储在一行一行的扁平数组中。其他矩阵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>
有什么想法吗?
答案 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)
这是一种称为“矩阵蠕变”的现象,如果您不能始终对矩阵进行标准化,则会在矩阵操作期间逐渐发生。