views:

111

answers:

8

Hello all,

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 ?

A: 

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

PierrOz
A: 

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

Marcelo Cantos
My arrays are already float[], can't change anything to that. Memory issues at some point, whole application using float[] etc..
Wam
Then I'm not sure that there's much you can do. You've thrown away precision; there's no amount of cleverness that will get it back.
Marcelo Cantos
+1  A: 

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.

ChrisF
Yeah, but converting mePtr and bPtr to double, can I still use mePtr++ or will this make use of only half the elements of my array ? b.data is float[], if mePtr is a double pointer to b.data[0], won't mePtr++ be 8 bytes on the right, so b.data[2] instead of b.data[1] ?
Wam
@Wam - In that case you'll have to cast the values to double in the calculation - I'll update the answer
ChrisF
Tried it, it doesn't solve my problem ...
Wam
A: 

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

Tom Cabanski
Well, floats are less precise than doubles, that's for sure. But all (well, approximately all, my statistic is inaccurate) floating-point numbers are imprecise.
High Performance Mark
@High, doubles have 29 more bits of precision than floats. This makes them about 100 million times more precise.
Marcelo Cantos
@Marcelo -- it sure does. And still lots of us doing serious number crunching have to worry about the loss of precision in long running computations. Changing floats to doubles only postpones the problem it doesn't remove it. Of course, sometimes it postpones the problem until after the computation finishes.
High Performance Mark
A: 

For the kind of long-running iterative matrix-based computations I work on that's very good agreement. To be more helpful:

  • What size are your arrays ? Or, how many f-p operations are you executing ?

  • How did you get your expected value ?

High Performance Mark
- Values are quite big (1,000,000 elements)- Expected value was obtained by using a managed code loop, using only double accumulators. I'm very confident in this value, since it has been verified by other means (for special matrices where a closed formula existed for the result).
Wam
Well, for matrices of that sort of size (you do mean 1000x1000 don't you ?) there are going to be a lot of f-p operations and unless you implement precision-preserving algorithms (which can be very tricky and will always be slower than non-precision-preserving ones) it is unreasonable to expect precise agreements with expected values. Where I work there is no scientific validity to any more than 2 sig-figs anyway so we don't bother; you may have a reason to bother.
High Performance Mark
If you use f-p arithmetic (whether double or higher precision) for your special matrices and if your closed formula is computed with f-p arithmetic, you still have to do some error analysis to validate the benchmark.
High Performance Mark
+1  A: 

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.

Michael Borgwardt
A: 

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

thr
+1  A: 

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 !

Wam