Умножение с плавающей запятой Возвращает ovf или -0,0 для небольших целых чисел с плавающей запятой.

Я написал простую функцию умножения матриц. Изначально мои матрицы хранятся в одном массиве в формате (строка, столбец):

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

так выглядела бы идентификационная матрица 2 x 2

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

В реальной функции я помещаю 2 матрицы массива, умноженные на 2d-массивы (легко читаемые человеком), а затем выполняю умножение, чтобы получить новый вывод матрицы. Затем конвертируйте обратно в один массив. Я имею дело только с матрицами 3x3, 3x1 и 1x3. Так что мой код не на 100% совместим с каждой возможностью матрицы. У меня есть глобальные массивы temp3 для моих матриц 3x1 и 1x3 и temp3x3 для хранения матриц 3x3. Я использую глобальный логический квадрат, чтобы определить, к какому из них перейдет новый вывод.

Я понимаю, что мой код неидеален, но у меня вопрос: почему я получаю переполнение для простых операций? Мой код выглядит следующим образом:

#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;
// Временная переменная для хранения матриц
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];

  // поместить матрицу1 в массив 2d
  row = 0;
  for(row = 0; row < rows1; row++)
  {
    column = 0;
    for(column = 0; column < cols1; column++)
    {
      mat1[row][column] = matrix1[row*cols1 + column];
    }
  }

  // поместить матрицу2 в массив 2d
  row = 0;
  for(row = 0; row < rows2; row++)
  {
    column = 0;
    for(column = 0; column < cols2; column++)
    {
      mat2[row][column] = matrix2[row*cols2 + column];
    }
  }

  // получаем новый массив с перемноженными значениями
  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;
  // Помещаем новую матрицу в формат одного массива
  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++;
    }
  }
}

Надеюсь, это все. У меня есть еще код, но он весь в основном закомментирован. В худшем случае я пропустил здесь объявление переменной, но отмечу, что в среде IDE она компилируется и работает нормально. Мой вывод выглядит так:

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

Откуда взялся этот ovf? Я попытался распечатать в операторе умножения в цикле for индекс mat1 x индекс mat2 = "результат". Это на самом деле распечатает именно то, что я ожидаю увидеть, и тогда мой вывод в основном цикле будет в порядке. Что здесь происходит? Я попробовал некоторые задержки, чтобы увидеть, была ли это проблема между вычислениями. Плохо.

я имею в виду размещение в самом внутреннем цикле for (цикл k):

''' Serial.print (mat1 [строка] [k]); Serial.print("x"); Serial.print (mat2 [k] [столбец]); Serial.print ("+"); ''' А затем сразу за ним,

''' Serial.print(" = "); Serial.println (newMat [строка] [столбец]); '''

В этом сценарии я не получу переполнения.

, 👍2

Обсуждение

newMat[строка][столбец] += mat1[строка][k]*mat2[k][столбец];, @Majenko

Что именно на низком уровне MCU на самом деле делает эта линия?, @Majenko

Это охватывает умножение rowxcolumn для каждого нового индекса элемента. Чтобы получить правильное новое значение индекса для новой матрицы 3x3, нужно добавить rowXcolumn. Первый цикл for перебирает количество строк, которые войдут в новую матрицу (это rows1), второй цикл for перебирает количество столбцов для новой матрицы (то есть cols2), а последний цикл — это то, что я только что рассмотрел. Сложение строки путем умножения столбца. чтобы перейти к новому индексированному элементу., @notARobot

Думайте об этом как об общей сумме для нового индекса. Я просто пропустил шаг дополнительной переменной., @notARobot

Какое значение вы спрашиваете?, @Majenko


1 ответ


Лучший ответ:

1
Сначала объявляется

newMat:

double newMat[rows1][cols2];

а затем обновлено:

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

Однако он никогда не был инициализирован. Нестатические локальные переменные не инициализируется неявно: newMat начинает свою жизнь с любого мусор был в оперативной памяти в этом месте. Вы должны инициализировать каждый элемента на ноль, прежде чем вы начнете накапливать сумму произведений.


ОБНОВЛЕНИЕ: относительно инициализации как

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

Я ожидал бы, что вся матрица будет инициализирована всеми нулями, но мой эксперимент показывает, что он инициализирует только первый элемент первого два ряда и весь третий ряд. Инициализация массива списком короче, чем этот массив, который должен инициализировать другие элементы для нуль. Однако это, похоже, не работает рекурсивно с внутренним массивы. Я не знаю, является ли это ошибкой компилятора или нормальным поведением. Однако следующее работает:

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

Что касается копий внутри multiplyMatrixAxB(), вы можете полностью избегайте их с помощью указателей. Например,

double (*mat1)[cols1];

объявляет mat1 как указатель на массивы длиной cols1 из double. Это будет эквивалентно double mat1[][cols1] внутри параметра. list и совместим с двумерным массивом шириной cols1 и неуказанным высота. Если вы укажете это на matrix1:

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

затем вы можете использовать mat1 как другое представление matrix1: вы просматриваете его как массив массивов чисел, а не одномерный массив чисел. Ты затем вы можете использовать mat1, как сейчас, и он будет работать, обращаясь к исходные данные без дополнительной копии.

С помощью этого трюка тело multiplyMatrixAxB() можно сократить до следующее (пропуская проверку ошибок):

// Просмотр векторов в виде матриц.
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;

// Умножение матриц.
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];
  }
,

Абсолютно. Я изменил объявление newMat на «float newMat[rows1][cols2] = {{0},{0}};». Теперь нет проблем. Иногда забывают о мелочах., @notARobot

Я до сих пор иногда получаю -0.0. Есть идеи, откуда это? Я должен просто иметь 0 * 0 или 0 * число в тех местах, где я его получаю. Должен быть просто ноль. Однако ovf остановился., @notARobot

Я поместил «newMat [строка] [столбец] = 0;» выше, где k = 0. Это, казалось, исправило это. Я видел, что если вы просто инициализируете один элемент многомерного массива, остальные, которым не присвоено значение, присваиваются нулю. Поэтому я не думал, что мне нужно явно присваивать значение каждому отдельному индексу., @notARobot