2

I wrote a simple multiplication function for matrices. Originally my matrices are stored in a single array in format (row, column):

(row0, col0), (row0, col1), (row0, colN), (row1, col0), ... (rowN, col0).

so a 2x2 identity matrix would look like

float eye[4] = {1, 0, 0, 1};

In the actual function I put the 2 array matrices being multiplied into 2d arrays (easy human readable) and then do the multiplication to get the new matrix output. Then convert back to single array. I am only dealing with 3x3, 3x1, and 1x3 matrices. So my code is not be 100% compatible with every matrix possibility. I have global arrays temp3 for my 3x1 and 1x3 matrices, and temp3x3 to hold 3x3 matrices. I use a global bool squared to determine which one the new output will go to.

I get that my code is not perfect, but my question is, why do I get overflow for simple operations? My code is as follows:

#include <math.h>
#include <WString.h>

#define FLOAT sizeof(float)
void multiplyMatrixAxB(const uint8_t rows1, const uint8_t cols1, const uint8_t size1, float matrix1[], const uint8_t rows2, const uint8_t cols2, const uint8_t size2, float matrix2[]);

float K[3]; // 3x1
float dgdn[3]; // 1x3

bool squared = false;
// Temp Variable to hold matrixes
float temp3x3[9];
float temp3[3];

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Starting Matrix Test Program\n"));
  
  uint8_t i;
  K[0] = 1.0;
  K[1] = 2.0;
  K[2] = 3.0;

  dgdn[0] = 0.0;
  dgdn[1] = 0.0;
  dgdn[2] = 1.0;

  Serial.println(F("Matrix K setup as:"));
  for(i = 0; i < sizeof(K)/FLOAT; i++)
  {
    Serial.print(K[i]);
    Serial.print(F("\n"));
  }

  Serial.println(F("Matrix dgdn setup as:"));
  for(i = 0; i < sizeof(dgdn)/FLOAT; i++)
  {
    Serial.print(dgdn[i]);
    Serial.print(F("\t"));
  }
}

void loop()
{
  squared = true;
  Serial.println(F("\n\nA 3x1 times a 1x3 should give 3x3"));
  multiplyMatrixAxB(3,1, sizeof(K)/FLOAT, K, 1, 3, sizeof(dgdn)/FLOAT, dgdn);
  for(i = 0; i < 9; i++)
  {
    Serial.print(*(temp3x3 + i), 6);
    if(!((i+1)%3))
    {
      Serial.println();
    }
    else
    {
      Serial.print(F("\t"));
    }
  }

  Serial.print(F("\n\n"));
  Serial.println(F("End of Test"));
  while(1){delay(1000);}
}

void multiplyMatrixAxB(const uint8_t rows1, const uint8_t cols1, const uint8_t size1, float matrix1[], const uint8_t rows2, const uint8_t cols2, const uint8_t size2, float matrix2[])
{
  if(cols1 != rows2)
  {
    Serial.println(F("Multiplying Invalid Matrixes. Check ColsA and RowsB"));
    while(1){delay(1000);}
  }
  
  if(size1 != rows1*cols1)
  {
    Serial.println(F("rows and columns do not match size of matrix A"));
    while(1){delay(1000);}
  }
  
  if(size2 != rows2*cols2)
  {
    Serial.println(F("rows and columns do not match size of matrix B"));
    while(1){delay(1000);}
  }
  
  uint8_t row, column, k, i;
  double mat1[rows1][cols1];
  double mat2[rows2][cols2];
  double newMat[rows1][cols2];

  // place matrix1 into 2d array
  row = 0;
  for(row = 0; row < rows1; row++)
  {
    column = 0;
    for(column = 0; column < cols1; column++)
    {
      mat1[row][column] = matrix1[row*cols1 + column];
    }
  }

  // place matrix2 into 2d array
  row = 0;
  for(row = 0; row < rows2; row++)
  {
    column = 0;
    for(column = 0; column < cols2; column++)
    {
      mat2[row][column] = matrix2[row*cols2 + column];
    }
  }

  // get new array with multiplied values
  row = 0;
  for(row = 0; row < rows1; row++)
  {
    column = 0;
    for(column = 0; column < cols2; column++)
    {
      k = 0;
      for(k = 0; k < cols1; k++)
      {
        newMat[row][column] += mat1[row][k]*mat2[k][column];
      }
    }
  }

  row = 0;
  i = 0;
  // Place new matrix into single array format
  for(row = 0; row < rows1; row++)
  {
    column = 0;
    for(column = 0; column < cols2; column++)
    {
      if(squared)
      {
        temp3x3[i] = newMat[row][column];
      }
      else
      {
        temp3[i] = newMat[row][column];
      }
      i++;
    }
  }
}

Hopefully that covered everything. I have more code, but it is all mostly commented out. At worst I missed a variable declaration here, but I will mention that in the IDE it compiles fine and runs. My output looks like this:

Starting Matrix Test Program

Matrix K setup as:
1.00
2.00
3.00
Matrix dgdn setup as:
0.00    0.00    1.00    

A 3x1 times a 1x3 should give 3x3
0.000000    0.000000    1.000000
ovf 0.000000    2.000000
0.000000    0.000000    3.000000


End of Test

Where does this ovf come from? I have tried printing out in the multiplication statement in the for loop the index of mat1 x the index of mat2 = "result". This will actually print out exactly what I expect to see and then my output in the main loop will be ok. What is going on? I tried some delays to see if that was the issue between calculations as well. No good.

what I mean is placing in the inner most for loop (k loop):

''' Serial.print(mat1[row][k]); Serial.print(" x "); Serial.print(mat2[k][column]); Serial.print(" + "); ''' And then just outside it,

''' Serial.print(" = "); Serial.println(newMat[row][column]); '''

I will not get any overflow in this scenario.

5
  • newMat[row][column] += mat1[row][k]*mat2[k][column]; – Majenko Oct 16 '20 at 15:22
  • What, precisely, at the low-level of the MCU, is that line really doing? – Majenko Oct 16 '20 at 16:10
  • This covers the rowxcolumn multiplication for each new element index. The rowXcolumn needs added up to get the correct new index value for the new 3x3 matrix. First for loop iterates the number of rows that will go in the new matrix (which is rows1) , second for loop iterates the number of columns for the new matrix (which is cols2), and the last loop is what I just covered. The addition of the row by column multiplication. to go into the new indexed element. – notARobot Oct 16 '20 at 17:09
  • Think of it as the summed total for the new index. I just skipped the extra variable step. – notARobot Oct 16 '20 at 17:11
  • What are you asking the value to? – Majenko Oct 16 '20 at 17:21
2

newMat is first declared:

double newMat[rows1][cols2];

and then updated:

newMat[row][column] += mat1[row][k]*mat2[k][column];

However, it has never been initialized. Non-static local variables are not implicitly initialized: newMat starts its life containing whatever garbage was in RAM at this location. You should initialize every element to zero before you start accumulating the sum of products.


Edit: Regarding the initialization as

float newMat[rows1][cols2] = {{0},{0}};

I would expect that to initialize the whole matrix to all zeros, but my experiment shows that it only initializes the first element of the first two rows, and the entire third row. Initializing an array with a list shorter than that array is supposed to initialize the other elements to zero. However, this appears to not work recursively with the inner arrays. I do not know whether it is a compiler bug or normal behavior. The following, however, does work:

float newMat[rows1][cols2] = {};  // zero-initialize

Regarding the copies within multiplyMatrixAxB(), you can completely avoid them by using pointers. For example,

double (*mat1)[cols1];

declares mat1 as a pointer to arrays of length cols1 of double. This would be equivalent to double mat1[][cols1] within a parameter list, and is compatible with a 2D array of width cols1 and unspecified height. If you make this point to matrix1:

double (*mat1)[cols1] = (double (*)[cols1]) matrix1;

you can then use mat1 as a different view of matrix1: you view it as an array of arrays of numbers, rather than a 1D array of numbers. You can then use mat1 as you do now, and it will work by accessing the original data, without needing an extra copy.

Using this trick, the body of multiplyMatrixAxB() can be reduced to the following (skipping error checking):

// View the vectors as matrices.
double (*mat1)[cols1] = (double (*)[cols1]) matrix1;
double (*mat2)[cols2] = (double (*)[cols2]) matrix2;;
double (*newMat)[cols2];
if (squared)
  newMat = (double (*)[cols2]) temp3x3;
else
  newMat = (double (*)[cols2]) temp3;

// Matrix multiplication.
for (int i = 0; i < rows1; i++)
  for (int j = 0; j < cols2; j++) {
    newMat[i][j] = 0;
    for (int k = 0; k < cols1; k++)
      newMat[i][j] += mat1[i][k] * mat2[k][j];
  }
3
  • Absolutely it. I changed the newMat declaration to "float newMat[rows1][cols2] = {{0},{0}};". No issues now. Sometimes forget the little things. – notARobot Oct 16 '20 at 17:19
  • I still sometimes get the -0.0. Any idea where that comes from? I should just have 0 * 0 or 0* a number in the places I'm getting it. Should just be zero. The ovf stopped though. – notARobot Oct 16 '20 at 17:35
  • I placed "newMat[row][column] = 0;" above where k = 0 is. That seemed to fix it. I saw that if you just initialize one element of the multi-dimensional array, the rest that are not given a value are assigned to zero. So I did not think I needed to explicitly assign a value to each individual index. – notARobot Oct 16 '20 at 17:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.