5
\$\begingroup\$

I have written a Matrix library that contains all the main properties of matrices. It's a relatively long project, I am hoping it is ok to post here since I really want to have it reviewed.

The project is compiled in GCC 9.2.0 and Boost 1.71.0, from https://nuwen.net/mingw.html, environment codeblocks windows 10.

Utility.h

#ifndef UTILITY_H_INCLUDED
#define UTILITY_H_INCLUDED

#include <iostream>
#include <math.h>
#include <conio.h>
#include <vector>

#include "Fraction.h"
#include <boost/multiprecision/cpp_int.hpp>

using boost::multiprecision::cpp_int;

using namespace std;

namespace utilities
{

void swapRows(vector<vector<Fraction>>& mx, int row1, int row2,
              int columns)
{
    for (int i = 0; i < columns; i++ )
    {
        std::swap( mx[ row1 ][ i ], mx[ row2 ][ i ] );
    }
}

bool pivotEqualTo_one_Found(std::vector<vector<Fraction>>& mx, int pivot_row, int pivot_col,
                            int cols_num, int& alternative_pivot_row )
{
    for (int i = pivot_row + 1; i < cols_num; ++i)
    {
        if(mx[ i ][ pivot_col ] == 1)
        {
            alternative_pivot_row = i;

            return true;
        }
    }

    return false;
}

bool pivotNot_zero_Found(vector<vector<Fraction>> mx, int pivot_row, int pivot_col,
                         int cols_num, int& col_dif_zero )
{
    Fraction fr(0, 0);
    for (int i = pivot_row + 1; i < cols_num; ++i)
    {
        if(mx[ i ][ pivot_col ] != fr)
        {
            col_dif_zero = i;

            return true;
        }
    }

    return false;
}

bool firstNumberNot_zero(vector<vector<Fraction>> mx, int row_num, int columms,
                                 int& num_coluna_num_dif_zero)
{
    for (int i = 0; i < columms; ++i)
    {
        if (mx[row_num] [ i ] != 0)
        {
            num_coluna_num_dif_zero = i;

            return true;
        }
    }

    return false;
}

void changePivotTo_one(vector<vector<Fraction>>& mx, int row_num, int columms, Fraction constant)
{
    Fraction fr(0, 1);
    for(int i = 0; i < columms; ++i)
        if (mx[ row_num ][ i ].num == 0)
            mx[ row_num ][ i ] = mx[ row_num ][ i ];
        else
            mx[ row_num ][ i ] = (mx[ row_num ][ i ] / constant);
}

void zeroOutTheColumn(vector<vector<Fraction>>& mx, int row_num, int num_pivot_row,
                      int columms, Fraction constant)
{
    for(int i = 0; i < columms; ++i)
    {
        mx[ row_num ][ i ] = mx[ row_num ][ i ] -  (constant * mx[num_pivot_row][i]);
    }
}

}

#endif // UTILITY_H_INCLUDED

Fraction.h

#ifndef FRACTION_H_INCLUDED
#define FRACTION_H_INCLUDED

#include <ostream>
#include <boost/multiprecision/cpp_int.hpp>

using boost::multiprecision::cpp_int;

class Fraction
{
    cpp_int lcd(cpp_int a, cpp_int b);
    cpp_int gcf(cpp_int a, cpp_int b);
    void simplify();

public:
    cpp_int num;
    cpp_int den;

    Fraction () : num(0), den(1) {}
    Fraction (cpp_int n)
    {
        num = n;
        den = 1;
    }
    Fraction(cpp_int _num, cpp_int _den) : num(_num), den(_den) {}

    friend std::ostream& operator<< (std::ostream& os, const Fraction& fr);

    bool operator== (const Fraction& fr)
    {
        return (this->num == fr.num && this->den == fr.den);
    }

    bool operator== (int n)
    {
        return ((this->num / this->den) == n);
    }

    bool operator!= (const Fraction& fr)
    {
        return (this->num != fr.num || this->den != fr.den);
    }

    bool operator!= (int n)
    {
        return ((this->num / this->den) != n);
    }

    Fraction operator+(const Fraction& fr) const;
    Fraction operator/(const Fraction& fr) const;
    Fraction operator-(const Fraction& fr) const;
    Fraction operator*(const Fraction& fr) const;

    friend Fraction operator+(const Fraction& fr, cpp_int n);
    friend Fraction operator+(cpp_int n, const Fraction& fr);
    friend Fraction operator-(const Fraction& fr, cpp_int n);
    friend Fraction operator-(cpp_int n, const Fraction& fr);
    friend Fraction operator/(const Fraction& fr, cpp_int n);
    friend Fraction operator/(cpp_int n, const Fraction& fr);
    friend Fraction operator*(const Fraction& fr, cpp_int n);
    friend Fraction operator*(cpp_int n, const Fraction& fr);

    friend void operator+= (Fraction& f, const Fraction& fr);
    friend void operator-= (Fraction& f, const Fraction& fr);
    friend void operator/= (Fraction& f, const Fraction& fr);
    friend void operator*= (Fraction& f, const Fraction& fr);

    friend void operator+=(Fraction& fr, cpp_int n);
    friend void operator-=(Fraction& fr, cpp_int n);
    friend void operator*=(Fraction& fr, cpp_int n);
    friend void operator/=(Fraction& fr, cpp_int n);
};



#endif // FRACTION_H_INCLUDED

Fraction.cpp

#include "Fraction.h"

using namespace std;

std::ostream& operator << (std::ostream& os, const Fraction& fr)
{
    if(fr.num % fr.den == 0)
    {
        cpp_int res = fr.num / fr.den;

        os << res;
    }
    else
        os << fr.num << "/" << fr.den;

    return os;
}

cpp_int Fraction::gcf(cpp_int a, cpp_int b)
{
    if( b == 0)
        return abs(a);
    else
        return gcf(b, a%b);
}

cpp_int Fraction::lcd(cpp_int a, cpp_int b)
{
    cpp_int n = gcf(a, b);

    return (a / n) * b;
}

void Fraction::simplify()
{
    if (den == 0 || num == 0)
    {
        num = 0;
        den = 1;
    }
    // Put neg. sign in numerator only.
    if (den < 0)
    {
        num *= -1;
        den *= -1;
    }

    // Factor out GCF from numerator and denominator.
    cpp_int n = gcf(num, den);
    num = num / n;
    den = den / n;
}

Fraction Fraction::operator - (const Fraction& fr) const
{
    Fraction sub( (num * fr.den) - (fr.num * den), den * fr.den );

    sub.simplify();

    return sub;
}

Fraction Fraction::operator+(const Fraction& fr) const
{
    Fraction add ((num * fr.den) + (fr.num * den), den * fr.den );

    add.simplify();

    return add;
}

Fraction Fraction::operator*(const Fraction& fr) const
{
    Fraction mult(num * fr.num, den * fr.den);

    mult.simplify();

    return mult;
}

Fraction Fraction::operator / (const Fraction& fr) const
{
    Fraction sub(num * fr.den, den * fr.num);

    sub.simplify();

    return sub;
}

Fraction operator+(const Fraction& fr, cpp_int n)
{
    return (Fraction(n) + fr);
}

Fraction operator+(cpp_int n, const Fraction& fr)
{
    return (Fraction(n) + fr);
}

Fraction operator-(const Fraction& fr, cpp_int n)
{
    return (Fraction(n) - fr);
}

Fraction operator-(cpp_int n, const Fraction& fr)
{
    return (Fraction(n) - fr);
}

Fraction operator/(const Fraction& fr, cpp_int n)
{
    return (Fraction(n) / fr);
}

Fraction operator/(cpp_int n, const Fraction& fr)
{
    return (Fraction(n) / fr);
}

Fraction operator*(const Fraction& fr, cpp_int n)
{
    return (Fraction(n) * fr);
}

Fraction operator*(cpp_int n, const Fraction& fr)
{
    return (Fraction(n) * fr);
}

void operator+=(Fraction& f, const Fraction& fr)
{
    f = f + fr;
}


void operator-=(Fraction& f, const Fraction& fr)
{
    f = f - fr;
}

void operator/=(Fraction& f, const Fraction& fr)
{
    f = f / fr;
}

void operator*=(Fraction& f, const Fraction& fr)
{
    f = f * fr;
}

void operator+=(Fraction& fr, cpp_int n)
{
    fr = fr + n;
}

void operator-=(Fraction& fr, cpp_int n)
{
    fr = fr - n;
}

void operator*=(Fraction& fr, cpp_int n)
{
    fr = fr * n;
}

void operator/=(Fraction& fr, cpp_int n)
{
    fr = fr / n;
}

Matrix.h

#ifndef MATRIX_H_INCLUDED
#define MATRIX_H_INCLUDED

#include <vector>
#include <ostream>
#include <assert.h>
#include "Fraction.h"

#include <boost/multiprecision/cpp_int.hpp>

using boost::multiprecision::cpp_int;

class Matrix
{
private:
    int rows_num;
    int cols_num;

    std::vector <std::vector<Fraction>> data;

public:
    Matrix () = default;

    Matrix(int r, int c) : rows_num(r), cols_num(c)
    {
        assert(r > 0 && c > 0);
        data.resize(r, std::vector<Fraction>( c, {0} ) );
    }
    Matrix(int r, int c, cpp_int n) : rows_num(r), cols_num(c)
    {
        assert(r > 0 && c > 0);
        data.resize(r, std::vector<Fraction>( c, {n} ) );
    }

    friend std::ostream& operator<<(std::ostream& out, const Matrix& mx);
    friend std::ostream& operator<<(std::ostream& out, const std::vector<Fraction>& diag);

    bool operator== (Matrix& mx);
    bool operator!= (Matrix& mx);

    Matrix operator+(const Matrix& mx);
    Matrix operator-(const Matrix& mx);
    Matrix operator*(const Matrix& mx);

    void operator+=(const Matrix& mx);
    void operator-=(const Matrix& mx);
    void operator*=(const Matrix& mx);

    friend Matrix operator*(const Matrix& mx, cpp_int n);
    friend Matrix operator*(cpp_int n, const Matrix& mx);
    friend void operator*=(Matrix& mx, cpp_int n);

    Fraction& operator()(int r, int c)
    {
        return data[r][c];
    }

    int size()
    {
        return rows_num * cols_num;
    }

    void resize(int r, int c)
    {
        data.clear();

        data.resize(r, std::vector<Fraction>( c, {0} ) );

        rows_num = r;
        cols_num = c;
    }

    int rows()
    {
        return rows_num;
    }

    int cols()
    {
        return cols_num;
    }

    static Matrix IDENTITY(int n);
    static Matrix CONSTANT(int r, int c, cpp_int n);

    bool is_square()
    {
        return rows_num == cols_num;
    }

    bool is_identity();
    bool is_symmetric();
    bool is_skewSymmetric();
    bool is_diagonal();
    bool is_null();
    bool is_constant();
    bool is_orthogonal();
    bool is_invertible();
    bool is_upperTriangular();
    bool is_lowerTriangular();

    Matrix transpose();
    Fraction determinant();
    Matrix inverse();
    Matrix gaussJordanElimination();
};



#endif // MATRIX_H_INCLUDED

Matrix.cpp

#ifndef MATRIX_H_INCLUDED
#define MATRIX_H_INCLUDED

#include <vector>
#include <ostream>
#include <assert.h>
#include "Fraction.h"

#include <boost/multiprecision/cpp_int.hpp>

using boost::multiprecision::cpp_int;

class Matrix
{
private:
    int rows_num;
    int cols_num;

    std::vector <std::vector<Fraction>> data;

public:
    Matrix () = default;

    Matrix(int r, int c) : rows_num(r), cols_num(c)
    {
        assert(r > 0 && c > 0);
        data.resize(r, std::vector<Fraction>( c, {0} ) );
    }
    Matrix(int r, int c, cpp_int n) : rows_num(r), cols_num(c)
    {
        assert(r > 0 && c > 0);
        data.resize(r, std::vector<Fraction>( c, {n} ) );
    }

    friend std::ostream& operator<<(std::ostream& out, const Matrix& mx);
    friend std::ostream& operator<<(std::ostream& out, const std::vector<Fraction>& diag);

    bool operator== (Matrix& mx);
    bool operator!= (Matrix& mx);

    Matrix operator+(const Matrix& mx);
    Matrix operator-(const Matrix& mx);
    Matrix operator*(const Matrix& mx);

    void operator+=(const Matrix& mx);
    void operator-=(const Matrix& mx);
    void operator*=(const Matrix& mx);

    friend Matrix operator*(const Matrix& mx, cpp_int n);
    friend Matrix operator*(cpp_int n, const Matrix& mx);
    friend void operator*=(Matrix& mx, cpp_int n);

    Fraction& operator()(int r, int c)
    {
        return data[r][c];
    }

    int size()
    {
        return rows_num * cols_num;
    }

    void resize(int r, int c)
    {
        data.clear();

        data.resize(r, std::vector<Fraction>( c, {0} ) );

        rows_num = r;
        cols_num = c;
    }

    int rows()
    {
        return rows_num;
    }

    int cols()
    {
        return cols_num;
    }

    static Matrix IDENTITY(int n);
    static Matrix CONSTANT(int r, int c, cpp_int n);

    bool is_square()
    {
        return rows_num == cols_num;
    }

    bool is_identity();
    bool is_symmetric();
    bool is_skewSymmetric();
    bool is_diagonal();
    bool is_null();
    bool is_constant();
    bool is_orthogonal();
    bool is_invertible();
    bool is_upperTriangular();
    bool is_lowerTriangular();

    Matrix transpose();
    Fraction determinant();
    Matrix inverse();
    Matrix gaussJordanElimination();
};



#endif // MATRIX_H_INCLUDED

Matrix.cpp

#include "Matrix.h"
#include "Utility.h"

#include <iostream>
#include <assert.h>
#include <boost/format.hpp>

using namespace std;
using namespace utilities;
using namespace boost;

ostream& operator<<(ostream& os, const Matrix& mx)
{
    // a little hack I came up with to my output formatting
    vector<int> vec;

    for(int i = 0; i < mx.rows_num; ++i)
        for(int j = 0; j < mx.cols_num; ++j)
        {
            int n = static_cast<int>(mx.data[i][j].num);
            int d = static_cast<int>(mx.data[i][j].den);
            string s = to_string(n);
            int width = s.size();
            s = to_string(d);
            width += s.size();
            vec.push_back(width);
        }

    int width = *max_element(vec.begin(), vec.end()) + 4;

    string w = "%";
    w += to_string(width) + "s";

    int len = mx.data.size();
    for (int i = 0; i < len; i++)
    {
        int len_ = mx.data[i].size();
        for (int j = 0; j < len_; j++)
            os << format(w.c_str()) % mx.data[i][j];

        os << endl;
    }

    return os;
}

bool Matrix::operator==(Matrix& mx)
{
    if(rows_num != mx.rows_num || cols_num != mx.cols_num)
        return false;

    for(int i = 0; i < rows_num; ++i)
        for(int j = 0; j < cols_num; ++j)
            if(data[i][j] != mx.data[i][j])
                return false;

    return true;
}

bool Matrix::operator!=(Matrix& mx)
{
    if(rows_num != mx.rows_num || cols_num != mx.cols_num)
        return true;

    for(int i = 0; i < rows_num; ++i)
        for(int j = 0; j < cols_num; ++j)
            if(data[i][j] != mx.data[i][j])
                return true;

    return false;
}

Matrix Matrix::operator+(const Matrix& mx)
{
    assert(rows_num == mx.rows_num && cols_num == mx.cols_num);

    Matrix add(rows_num, cols_num);

    for(int i = 0; i < rows_num; ++i)
        for(int j = 0; j < cols_num; ++j)
            add.data[ i ][ j ] = data[ i ][ j ] + mx.data[ i ][ j ];

    return add;
}

Matrix Matrix::operator-(const Matrix& mx)
{
    assert(rows_num == mx.rows_num && cols_num == mx.cols_num);

    Matrix sub(rows_num, cols_num);

    for(int i = 0; i < rows_num; ++i)
        for(int j = 0; j < cols_num; ++j)
            sub.data[ i ][ j ] = data[ i ][ j ] - mx.data[ i ][ j ];

    return sub;
}

Matrix Matrix::operator*(const Matrix& mx)
{
    assert(cols_num == mx.rows_num);

    Matrix mult(rows_num, mx.cols_num);

    for(int i = 0; i < rows_num; ++i)
        for (int j = 0; j < mx.cols_num; ++j)
            for(int x = 0; x < cols_num; ++x)
                mult.data[ i ][ j ] += data[ i ][ x ] * mx.data[ x ][ j ];

    return mult;
}

void Matrix::operator*=(const Matrix& mx)
{
    assert(cols_num == mx.rows_num);

    *this = (*this * mx);
}

void Matrix::operator-=(const Matrix& mx)
{
    assert(rows_num == mx.rows_num && cols_num == mx.cols_num);

    *this = (*this - mx);
}

void Matrix::operator+=(const Matrix& mx)
{
    assert(rows_num == mx.rows_num && cols_num == mx.cols_num);

    *this = (*this + mx);
}

Matrix operator*(const Matrix& mx, cpp_int n)
{
    Matrix mult(mx.rows_num, mx.cols_num);

    for(int i = 0; i < mx.rows_num; ++i)
        for(int j = 0; j < mx.cols_num; ++j)
            mult.data[i][j] = mx.data[i][j] * n;

    return mult;
}

Matrix operator*(cpp_int n, const Matrix& mx)
{
    Matrix mult(mx.rows_num, mx.cols_num);

    for(int i = 0; i < mx.rows_num; ++i)
        for(int j = 0; j < mx.cols_num; ++j)
            mult.data[i][j] = mx.data[i][j] * n;

    return mult;
}

void operator*=(Matrix& mx, cpp_int n)
{
    mx = mx * n;
}

Matrix Matrix::IDENTITY(int n)
{
    assert(n > 0);

    Matrix mx(n,n);

    for(int i = 0; i < n; ++i)
        mx.data[i][i] = {1};

    return mx;
}

Matrix Matrix::CONSTANT(int r, int c, cpp_int n)
{
    vector <std::vector<Fraction>> vec(r, vector<Fraction>( c, {n} ) );
    Matrix mx(r,c);
    mx.data = vec;

    return mx;
}

bool Matrix::is_identity()
{
    if(! is_square())
        return false;

    for(int i = 0; i < rows_num; ++i)
        for(int j = 0; j < cols_num; ++j)
        {
            if(i != j && data[ i ][ j ] != 0)
                return false;
            if(i == j && data[ i ][ j ] != 1)
                return false;
        }

    return true;
}

bool Matrix::is_symmetric()
{
    if(! is_square())
        return false;

    for(int i = 0; i < rows_num; ++i)
        for(int j = 0; j < cols_num; ++j)
            if(data[ i ][ j ] != data[ j ][ i ])
                return false;

    return true;
}

bool Matrix::is_skewSymmetric()
{
    if(! is_square())
        return false;

    for(int i = 0; i < rows_num; ++i)
        for(int j = 0; j < cols_num; ++j)
            if(i != j)
                if( data[ i ][ j ] != ( data[ j ][ i ]*(-1) ) )
                    return false;

    return true;
}

bool Matrix::is_diagonal()
{
    if(! is_square())
        return false;

    for(int i = 0; i < rows_num; ++i)
        for(int j = 0; j < cols_num; ++j)
            if(i != j)
                if( data[ i ][ j ] != 0 )
                    return false;

    return true;
}

bool Matrix::is_null()
{
    for(int i = 0; i < rows_num; ++i)
        for(int j = 0; j < cols_num; ++j)
            if( data[ i ][ j ] != 0 )
                return false;

    return true;
}

bool Matrix::is_constant()
{
    for(int i = 0; i < rows_num; ++i)
        for(int j = 0; j < cols_num; ++j)
            if( data[ i ][ j ] != data[0][0] )
                return false;

    return true;
}

bool Matrix::is_orthogonal()
{
    if(! is_square())
        return false;

    Matrix identity = Matrix::IDENTITY(cols_num);

    return (*this * this->transpose() == identity);
}

bool Matrix::is_invertible()
{
    return this->determinant() != 0;
}

bool Matrix::is_lowerTriangular()
{
    if(! is_square())
        return false;

    for(int i = 0; i < rows_num; ++i)
        for(int j = 0; j < cols_num; ++j)
            if( j > i && data[i][j] != 0)
                return false;

    return true;
}

bool Matrix::is_upperTriangular()
{
    if(! is_square())
        return false;

    for(int i = 0; i < rows_num; ++i)
        for(int j = 0; j < cols_num; ++j)
            if( j < i && data[i][j] != 0)
                return false;

    return true;
}

Matrix Matrix::transpose()
{
    Matrix trans(cols_num, rows_num);

    for(int i = 0; i < rows_num; ++i)
        for(int j = 0; j < cols_num; ++j)
            trans.data[ j ][ i ] = data[ i ][ j ];

    return trans;
}

Fraction Matrix::determinant()
{
    assert(is_square());

    if(is_null())
        return {0};

    if(is_constant())
        return {0};

    if(rows_num == 1)
        return data[0][0];

    if(is_identity())
        return {1};

    bool alternative_pivot_1_found;

    bool pivot_not_zero_found;

    int row_with_alternative_pivot;

    int row_with_pivot_not_zero;

    int pivot_row = 0;
    int pivot_col = 0;

    Matrix mx = *this;
    vector<Fraction> row_mults;
    int sign = 1;

    while (pivot_row < (rows_num - 1))
    {
        alternative_pivot_1_found = pivotEqualTo_one_Found (mx.data, pivot_row, pivot_col,
                                    rows_num, row_with_alternative_pivot);

        pivot_not_zero_found = pivotNot_zero_Found(mx.data,
                               pivot_row, pivot_col, rows_num, row_with_pivot_not_zero);


        if (mx.data[ pivot_row ] [ pivot_col ] != 1 && alternative_pivot_1_found )
        {
            swapRows(mx.data, pivot_row, row_with_alternative_pivot, cols_num);

            sign *= (-1);
        }
        else if (mx.data[ pivot_row ] [ pivot_col ] == 0 && pivot_not_zero_found )
        {
            swapRows(mx.data, pivot_row, row_with_pivot_not_zero, cols_num );

            sign *= (-1);
        }

        int col_dif_zero;

        firstNumberNot_zero(mx.data, pivot_row, cols_num, col_dif_zero);

        if (( mx.data[pivot_row] [col_dif_zero] ) != 1)
        {
            row_mults.push_back(mx.data[pivot_row] [col_dif_zero]);

            changePivotTo_one(mx.data, pivot_row, cols_num,
                              mx.data[ pivot_row ][ col_dif_zero ]);
        }

        int n = pivot_row + 1;

        while (n < rows_num)
        {
            Fraction constant = mx.data[ n ][ col_dif_zero ];

            if(constant != 0)
                zeroOutTheColumn(mx.data, n, pivot_row, cols_num, constant);

            ++n;
        }

        ++pivot_row;
        ++pivot_col;
    }

    Fraction det(1);

    for(int i = 0; i < rows_num; ++i)
        det  *= mx.data[i][i];

    int len = row_mults.size();

    for(int i = 0; i < len; ++i)
        det = det * row_mults[i];

    det *= sign;

    return det;
}

Matrix Matrix::inverse()
{
    assert(is_square());

    if( ! is_invertible())
    {
        cout << "NOT INVERTIBLE\n";
        return *this;
    }

    Matrix mx = *this;
    Matrix inverse = Matrix::IDENTITY(rows_num);

    bool alternative_pivot_1_found;

    bool pivot_not_zero_found;

    bool number_not_zero_found;

    int row_with_alternative_pivot;

    int row_with_pivot_not_zero;

    int pivot_row = 0;
    int pivot_col = 0;

    //Gauss Elimination
    while (pivot_row < (rows_num - 1))
    {
        alternative_pivot_1_found = pivotEqualTo_one_Found (mx.data, pivot_row, pivot_col,
                                    rows_num, row_with_alternative_pivot);

        pivot_not_zero_found = pivotNot_zero_Found(mx.data,
                               pivot_row, pivot_col, rows_num, row_with_pivot_not_zero);

        if (mx.data[ pivot_row ] [ pivot_col ] != 1 && alternative_pivot_1_found )
        {
            swapRows(inverse.data, pivot_row, row_with_alternative_pivot, cols_num);
            swapRows(mx.data, pivot_row, row_with_alternative_pivot, cols_num);
        }
        else if (mx.data[ pivot_row ] [ pivot_col ] == 0 && pivot_not_zero_found )
        {
            swapRows(inverse.data, pivot_row, row_with_pivot_not_zero, cols_num);
            swapRows(mx.data, pivot_row, row_with_pivot_not_zero, cols_num );

        }

        int col_dif_zero;

        number_not_zero_found = firstNumberNot_zero(mx.data, pivot_row, cols_num, col_dif_zero);

        if(number_not_zero_found)
        {
            if (( mx.data[pivot_row] [col_dif_zero] ) != 1)
            {
                changePivotTo_one(inverse.data, pivot_row, cols_num,
                                  mx.data[ pivot_row ][ col_dif_zero ]);

                changePivotTo_one(mx.data, pivot_row, cols_num,
                                  mx.data[ pivot_row ][ col_dif_zero ]);
            }
        }

        int n = pivot_row + 1;

        if(number_not_zero_found)
        {
            while (n < rows_num)
            {
                zeroOutTheColumn(inverse.data, n, pivot_row, cols_num, mx.data[ n ][ col_dif_zero ]);
                zeroOutTheColumn(mx.data, n, pivot_row, cols_num, mx.data[ n ][ col_dif_zero ]);

                ++n;
            }
        }

        ++pivot_row;
        ++pivot_col;
    }

    //Jordan Elimination
    while(pivot_row > 0)
    {
        int col_dif_zero;

        number_not_zero_found = firstNumberNot_zero(mx.data, pivot_row, mx.cols_num, col_dif_zero);

        if(number_not_zero_found)
        {
            if (( mx.data[pivot_row] [col_dif_zero] ) != 1)
            {
                changePivotTo_one(inverse.data, pivot_row, mx.cols_num, mx.data[ pivot_row ][ col_dif_zero ]);
                changePivotTo_one(mx.data, pivot_row, mx.cols_num, mx.data[ pivot_row ][ col_dif_zero ]);
            }
        }

        int n = pivot_row - 1;

        if(number_not_zero_found)
        {
            while (n >= 0)
            {
                zeroOutTheColumn(inverse.data, n, pivot_row, mx.cols_num, mx.data[ n ][ col_dif_zero ]);
                zeroOutTheColumn(mx.data, n, pivot_row, mx.cols_num, mx.data[ n ][ col_dif_zero ]);

                --n;
            }
        }
        --pivot_row;
    }

    return inverse;
}

Matrix Matrix::gaussJordanElimination()
{
    Matrix mx = *this;

    bool alternative_pivot_1_found;

    bool pivot_not_zero_found;

    bool number_not_zero_found;

    int row_with_alternative_pivot;

    int row_with_pivot_not_zero;

    int pivot_row = 0;
    int pivot_col = 0;

    ///Gauss Elimination
    while (pivot_row < (rows_num - 1))
    {
        alternative_pivot_1_found = pivotEqualTo_one_Found (mx.data, pivot_row, pivot_col,
                                    rows_num, row_with_alternative_pivot);

        pivot_not_zero_found = pivotNot_zero_Found(mx.data,
                               pivot_row, pivot_col, rows_num, row_with_pivot_not_zero);

        if (mx.data[ pivot_row ] [ pivot_col ] != 1 && alternative_pivot_1_found )
        {
            swapRows(mx.data, pivot_row, row_with_alternative_pivot, cols_num);
        }
        else if (mx.data[ pivot_row ] [ pivot_col ] == 0 && pivot_not_zero_found )
        {
            swapRows(mx.data, pivot_row, row_with_pivot_not_zero, cols_num );
        }

        int col_dif_zero;

        number_not_zero_found = firstNumberNot_zero(mx.data, pivot_row, cols_num, col_dif_zero);

        if(number_not_zero_found)
        {
            if (( mx.data[pivot_row] [col_dif_zero] ) != 1)
            {
                changePivotTo_one(mx.data, pivot_row, cols_num,
                                  mx.data[ pivot_row ][ col_dif_zero ]);
            }
        }

        int n = pivot_row + 1;

        if(number_not_zero_found)
        {
            while (n < rows_num)
            {
                zeroOutTheColumn(mx.data, n, pivot_row, cols_num, mx.data[ n ][ col_dif_zero ]);

                ++n;
            }
        }

        ++pivot_row;
        ++pivot_col;
    }

    //Jordan Elimination
    while(pivot_row > 0)
    {
        int col_dif_zero;

        number_not_zero_found = firstNumberNot_zero(mx.data, pivot_row, mx.cols_num, col_dif_zero);

        if(number_not_zero_found)
        {
            if (( mx.data[pivot_row] [col_dif_zero] ) != 1)
            {
                changePivotTo_one(mx.data, pivot_row, mx.cols_num, mx.data[ pivot_row ][ col_dif_zero ]);
            }
        }

        int n = pivot_row - 1;

        if(number_not_zero_found)
        {
            while (n >= 0)
            {
                zeroOutTheColumn(mx.data, n, pivot_row, mx.cols_num, mx.data[ n ][ col_dif_zero ]);

                --n;
            }
        }
        --pivot_row;
    }

    return mx;
}

main.cpp

#include <iostream>
#include "Matrix.h"

using namespace std;
using namespace boost;

int main()
{
    const int m = 5, n = 5;
    Matrix a(m,n), b(3,4,3), c;

    a(0,0) = {-5};
    a(0,1) = {5};
    a(0,2) = {-6};
    a(0,3) = {-1};
    a(0,4) = {0};

    a(1,0) = {0};
    a(1,1) = {-5};
    a(1,2) = {10};
    a(1,3) = {-3};
    a(1,4) = {3};

    a(2,0) = {1};
    a(2,1) = {11};
    a(2,2) = {6};
    a(2,3) = {1};
    a(2,4) = {7};

    a(3,0) = {4};
    a(3,1) = {5};
    a(3,2) = {-9};
    a(3,3) = {9};
    a(3,4) = {-7};

    a(4,0) = {-5};
    a(4,1) = {10};
    a(4,2) = {0};
    a(4,3) = {-4};
    a(4,4) = {4};


    cout << "The Matrix A:" << endl;
    cout << a << endl;
    cout << "The Determinant of Matrix A: " << a.determinant() << endl;

    if(a.is_invertible())
    {
        cout << "The Inverse of Matrix A:" << endl;
        cout << a.inverse() << endl;
    }
    else
        cout << "The Matrix A is not Invertible" << endl;

    cout << "The Transpose of Matrix A:" << endl;
    cout << a.transpose() << endl;

    Matrix x(5,5,4);

    cout << "\nThe Matrx X:" << endl;
    cout << x;

    x *= a;

    cout << "\nThe Matrx X After Multiplication:" << endl;
    cout << x;

    c = x * 4;

    cout << "\nThe Matrx C:" << endl;
    cout << c;

   b(0,2) = {4};
   b(1,2) = {5};
   b(1,3) = {2};
   b(2,0) = {-8};
   b(2,3) = {9};
   b(0,0) = {1};
   b(0,1) = {2};


   cout << endl << "The Matrix B:" << endl;
   cout << b;

   cout << endl << "The Matrix After Being Applied the Gauss-Jordan Elimination:" << endl;
   cout << b.gaussJordanElimination() << endl;

   Matrix mx(4,4,4);

    cout << mx.determinant() << endl;

    for(int i = 0; i < m; ++i)
        for(int j = 0; j < n; ++j)
        {
            int x;
              cout << "Mx[" << i + 1 << "][" << j + 1 << "]: ";
                cin >> x;

               a(i,j) = {x};
        }

        cout << "The Matrix A:" << endl;
    cout << a << endl;



    c = Matrix::IDENTITY(m);


    //  cout << a << endl;
    //  cout << a.transpose();

    //cout << a.transpose().determinant() << endl << endl;
    //  cout << a.determinant();

    //cout << c;

}

I use the brute-force method to determine the inverse, determinant and perform the Gauss-Jordan elimination as it is the method I learnt when doing them by hand. But they require too many computations and I am looking for better way (not partial pivoting) to do it.

Edit: I had the link to my GitHub page with this project but I have updated the project based on the first review. Updated Project on GitHub.

\$\endgroup\$
2
  • \$\begingroup\$ Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers. \$\endgroup\$
    – Vogel612
    Commented Feb 8, 2020 at 13:16
  • \$\begingroup\$ @Vogel612 I know, the edit I did doesn't change the code as I deleted a double. \$\endgroup\$
    – HBatalha
    Commented Feb 8, 2020 at 15:55

2 Answers 2

8
\$\begingroup\$
using namespace std;

Never do that; certainly not in a header - that inflicts the harm onto every source file that includes the header.


Prefer to include your own headers before Standard Library headers. This can help expose unsatisfied dependencies of your library's headers.

Prefer <cmath> to <math.h> (etc.), as this puts the standard library identifiers into the std namespace, rather than the global namespace. Why does Utility.h need this header anyway?

WTF is <conio.h>? It's not a standard library header. Thankfully, it seems it can be removed.

No need to include <ostream> just for its types - include <iosfwd> instead for faster compilation. You'll need <ostream> in the implementation files, of course.


Be careful with indentation:

for(int i = 0; i < m; ++i)
    for(int j = 0; j < n; ++j)
    {
        ...
    }
    ...

    cout << "The Matrix A:" << endl;
cout << a << endl;

The first output line is indented as if it's part of the outer loop, but it's not.


Use initializers to initialize members. This allows compilers (e.g. g++ -Weffc++) to spot when you fail to initialise. Here, we're not even consistent:

Fraction () : num(0), den(1) {}
Fraction (cpp_int n)
{
    num = n;
    den = 1;
}
Fraction(cpp_int _num, cpp_int _den) : num(_num), den(_den) {}

The first and last use initializers; why not the middle one? These three can actually be combined into a single constructor, by using default arguments:

Fraction(cpp_int num = 0, cpp_int den = 1)
    : num{std::move(num)},
      den{std::move(den)}
{
    simplify();
}

The std::move() may reduce copying there.


With the compiler errors and warnings sorted out, we can move on to the logic of the program.

Fraction

This seems fairly straightforward, but could usefully lose the extraneous parentheses and this-> clutter that's all over the place (we're not writing Python!).

We could do with some tests of Fraction (and I really recommend using a pre-made test framework for this).

The output streaming operator can test for integers with a simple den == 1, since we always keep fractions in their reduced form. That's much cheaper than using %.

The comparison member functions should be declared const.

I think operator==(int) is broken, because it performs integer division and ignores the remainder. A more robust version would be (untested):

bool operator==(int n) const
{
    return n * den == num;
}

It's conventional to write operator!=() in terms of == (i.e. return !(*this == other);); that makes it easier to see the correspondence, and reduce the chance of error.

Many of the operators have overloads that are not required, given that cpp_int has implicit promotion to Fraction.

Some operators are missing: unary + and -, pre- and post- ++ and --, !, explicit operator bool, <, <=, >, >=. Possibly also %?

When we reimplement std::gcd() to accept cpp_int, let's not give it a gratuitously different name; it should be static, since it doesn't need to access this. The lcf() member (which parallels std::lcd()) is unused.

The arithmetic operators have a lot of duplication. Implement the non-assigning functions in terms of the assigning ones. For example:

class Fraction
{
    Fraction& operator+=(const Fraction& fr);
    Fraction operator+(const Fraction& fr) const;
};

Fraction& Fraction::operator+=(const Fraction& fr)
{
    num = num * fr.den + fr.num * den;
    den *= fr.den;
    simplify();
    return *this;
}

Fraction Fraction::operator+(Fraction fr) const
{
    return fr += *this;
}

Notice the return types (assignment operators always return a reference to the object) and passing by value to operator+().

With the above changes applied, I get the following refactored (simplified) class:

#include <iosfwd>
#include <utility>

#include <boost/multiprecision/cpp_int.hpp>

class Fraction
{
    using cpp_int = boost::multiprecision::cpp_int;

    static cpp_int gcd(const cpp_int& a, const cpp_int& b);
    void simplify();

public:
    cpp_int num;
    cpp_int den;

    Fraction(cpp_int num = 0, cpp_int den = 1)
        : num{std::move(num)},
          den{std::move(den)}
    {
        simplify();
    }

    Fraction(int num = 0, int den = 1)
        : num{num},
          den{den}
    {
        simplify();
    }

    friend std::ostream& operator<<(std::ostream& os, const Fraction& fr);

    bool operator==(const Fraction& fr) const { return num == fr.num && den == fr.den; }
    bool operator!=(const Fraction& fr) const { return !(*this == fr); }

    bool operator<(const Fraction& fr) const { return num * fr.den < den * fr.num; }
    bool operator<=(const Fraction& fr) const { return *this == fr || *this < fr; }
    bool operator>(const Fraction& fr) const { return !(*this<=fr); }
    bool operator>=(const Fraction& fr) const { return !(*this<fr); }

    explicit operator bool() const { return num != 0; }

    Fraction operator+() const;
    Fraction operator-() const;

    Fraction& operator++();
    Fraction& operator--();

    Fraction operator++(int);
    Fraction operator--(int);

    Fraction& operator+=(const Fraction& fr);
    Fraction& operator-=(const Fraction& fr);
    Fraction& operator*=(const Fraction& fr);
    Fraction& operator/=(const Fraction& fr);
};

Fraction operator+(Fraction a, const Fraction& b) { return a += b; }
Fraction operator-(Fraction a, const Fraction& b) { return a -= b; }
Fraction operator*(Fraction a, const Fraction& b) { return a *= b; }
Fraction operator/(Fraction a, const Fraction& b) { return a /= b; }
std::ostream& operator<<(std::ostream& os, const Fraction& fr)
{
    os << fr.num;
    if (fr.den != 1) {
        os << "/" << fr.den;
    }
    return os;
}

Fraction::cpp_int Fraction::gcd(const Fraction::cpp_int& a, const Fraction::cpp_int& b)
{
    return b ? gcd(b, a%b) : a;
}

void Fraction::simplify()
{
    // Denominators are always positive
    if (den < 0) {
        num = -num;
        den = -den;
    }

    // Factor out gcd from numerator and denominator.
    auto const n = gcd(abs(num), den);
    num /= n;
    den /= n;
}


Fraction Fraction::operator+() const
{
    return *this;
}

Fraction Fraction::operator-() const
{
    return { -num, den };
}

Fraction& Fraction::operator++()
{
    num += den;
    return *this;
}

Fraction& Fraction::operator--()
{
    num -= den;
    return *this;
}

Fraction Fraction::operator++(int)
{
    auto old = *this;
    ++*this;
    return old;
}

Fraction Fraction::operator--(int)
{
    auto old = *this;
    --*this;
    return old;
}

Fraction& Fraction::operator+=(const Fraction& fr)
{
    num = num * fr.den + fr.num * den;
    den *= fr.den;
    simplify();
    return *this;
}

Fraction& Fraction::operator-=(const Fraction& fr)
{
    return *this += -fr;
}

Fraction& Fraction::operator*=(const Fraction& fr)
{
    num *= fr.num;
    den *= fr.den;
    simplify();
    return *this;
}

Fraction& Fraction::operator/=(const Fraction& fr)
{
    return *this *= { fr.den, fr.num };
}

Matrix

The first thing I see here is that we use (signed) int for the dimensions. I think it would be less surprising if we had std::size_t instead, like all the standard containers.

The structure (vector of vectors) has unnecessary overhead, and poor locality of reference. A simple improvement would be to use a single vector and index into it as a raster (i.e. index = col + row * width). More advanced versions are possible with the same public interface (e.g. for tile-based or sparse storage). When making this change, it makes sense for the "utilities" functions to be brought in as private members, rather than passing the storage and its shape to them.

Keep using std::vector for the storage - that's great, because it enables the Rule of Zero; we don't need to implement our own copy/move constructors and assignment.

Some of the review of Fraction operators applies here: assignment operators should return a reference to *this, and comparison operators should be const. There are a lot of additional functions here that also should be const and/or accept const-ref arguments.

It's not clear why * and *= take a cpp_int - why not a Fraction?

The naming of IDENTITY and CONSTANT is unconventional - most programmers use all-caps for macros, which need special care as they behave differently from functions (e.g. scope rules and muliply-expanded arguments). Please don't distract from the real macros like that.

I don't see the value of CONSTANT - it seems to merely duplicate the three-argument constructor. Similarly, resize() is redundant - we can just assign a new matrix of the required size.

Also on naming, transpose() sounds like a mutator, but it actually creates a transposed copy of the matrix. I'd call that transposed() instead (and mark it const).

The comparison operator is over-complicated. We can simply compare the members, since std::vector provides a memberwise equality operator:

bool Matrix::operator==(const Matrix& mx) const
{
    return height == mx.height
        && width == mx.width
        && data == mx.data;
}

Or even, with a rasterised data (since vector compare tests the lengths):

bool Matrix::operator==(const Matrix& mx) const
{
    return width == mx.width
        && data == mx.data;
}

Element access using operator() needs to have const and non-const overloads. I find it helps the implementation to have a private at(x,y) method (it's easier to type when applied to this).

Here's what I'd expect from the interface:

#ifndef MATRIX_H_INCLUDED
#define MATRIX_H_INCLUDED

#include "Fraction.h"

#include <vector>
#include <iosfwd>
#include <assert.h>

class Matrix
{
    std::size_t height = 0;
    std::size_t width = 0;

    std::vector<Fraction> data = {};

    Fraction& at(std::size_t r, std::size_t c)
    { return data[r * width + c]; }

    const Fraction& at(std::size_t r, std::size_t c) const
    { return data[r * width + c]; }


public:
    Matrix()
        : Matrix{0, 0}
    {}

    Matrix(std::size_t height, std::size_t width, Fraction n = 0)
        : height{height},
          width{width},
          data(width * height, n)
    {}

    friend std::ostream& operator<<(std::ostream& out, const Matrix& mx);

    bool operator==(const Matrix& mx) const;
    bool operator!=(const Matrix& mx) const;

    Matrix& operator+=(const Matrix& mx);
    Matrix& operator-=(const Matrix& mx);
    Matrix& operator*=(const Matrix& mx);
    Matrix operator*(const Matrix&) const;

    // scalar multiplication
    Matrix& operator*=(const Fraction& n);

    Fraction& operator()(std::size_t r, std::size_t c)
    { return at(r, c); }

    const Fraction& operator()(std::size_t r, std::size_t c) const
    { return at(r, c); }

    std::size_t size() const
    { return height * width; }

    std::size_t rows() const
    { return height; }

    std::size_t cols() const
    { return width; }

    static Matrix identity(std::size_t n);

    bool is_square() const
    { return height == width; }

    bool is_identity() const;
    bool is_symmetric() const;
    bool is_skewSymmetric() const;
    bool is_diagonal() const;
    bool is_null() const;
    bool is_constant() const;
    bool is_orthogonal() const;
    bool is_invertible() const;
    bool is_upperTriangular() const;
    bool is_lowerTriangular() const;

    Matrix transpose() const;
    Fraction determinant() const;
    Matrix inverse() const;
    Matrix gaussJordanElimination() const;

private:
    void swapRows(std::size_t row1, std::size_t row2);
    bool pivotEqualTo_one_Found(std::size_t pivot_row, std::size_t pivot_col, std::size_t& alternative_pivot_row) const;
    bool pivotNot_zero_Found(std::size_t pivot_row, std::size_t pivot_col, std::size_t& col_dif_zero) const;
    bool firstNumberNot_zero(std::size_t row_num, std::size_t& num_coluna_num_dif_zero) const;
    void changePivotTo_one(std::size_t row_num, Fraction constant);
    void zeroOutTheColumn(std::size_t row_num, std::size_t num_pivot_row, Fraction constant);
};


Matrix operator+(Matrix a, const Matrix& b)
{ return a += b; }

Matrix operator-(Matrix a, const Matrix& b)
{ return a -= b; }

Matrix operator*(Matrix mx, const Fraction& n)
{ return mx *= n; }

Matrix operator*(const Fraction& n, Matrix mx)
{ return mx *= n; }

#endif // MATRIX_H_INCLUDED

Moving on to the implementation of Matrix, I'll start with <<. I think it's easier to use std::setw rather than composing a boost::format string. It's also inefficient to create a vector of widths to find the maximum - in this case, I'd leave the standard algorithm and just update as we go (this may change when C++20 Ranges are more widely available). Don't use std::endl unless you really need to flush - \n is much more lightweight.

Those changes give me this:

std::ostream& operator<<(std::ostream& os, const Matrix& mx)
{
    // find maximum element width
    std::size_t max_width = 1;
    for (auto const& element: mx.data) {
        auto w = element.to_string().size();
        if (w > max_width) {
            max_width = w;
        }
    }

    // use the max width to format elements
    max_width += 4;             // padding between elements

    for (std::size_t i = 0;  i < mx.height;  i++) {
        for (std::size_t j = 0;  j < mx.width;  j++) {
            os << std::setw(max_width) <<  mx.at(i, j);
        }
        os << std::endl;
    }

    return os;
}

That required a simple to_string() member in Fraction:

std::string Fraction::to_string() const
{
    std::ostringstream os;
    os << *this;
    return os.str();
}

We don't need to hand-code loops in the addition and subtraction operators - std::transform() does that for us (and simplifies the path to parallelising):

Matrix& Matrix::operator-=(const Matrix& mx)
{
    assert(height == mx.height);
    assert(width == mx.width);
    std::transform(data.begin(), data.end(),
                   mx.data.begin(), data.begin(),
                   std::minus{});
    return *this;
}

Matrix& Matrix::operator+=(const Matrix& mx)
{
    assert(height == mx.height);
    assert(width == mx.width);
    std::transform(data.begin(), data.end(),
                   mx.data.begin(), data.begin(),
                   std::plus{});
    return *this;
}

We can simplify is_identity() to use the code we already wrote:

bool Matrix::is_identity() const
{
    if (! is_square())
        return false;

    return *this == identity(width);
}

And, similarly, is_symmetric():

bool Matrix::is_symmetric() const
{
    return *this == transposed();
}

Admittedly, these two now do more work when returning false, so you might not want to use these implementations.

We can reduce the work done in is_skewSymmetric() by about half, by starting j beyond the diagonal:

bool Matrix::is_skewSymmetric() const
{
    if (!is_square()) {
        return false;
    }

    for (std::size_t i = 0; i < height; ++i) {
        for (std::size_t j = i+1; j < width; ++j) {
            if (at(i, j) != -at(j, i)) {
                return false;
            }
        }
    }

    return true;
}

I don't like the name of is_null() - to me that implies an uninitalised (zero-size) Matrix. I'd call it is_zero() and use <algorithm> to simplify; similarly for is_constant():

bool Matrix::is_zero() const
{
    return std::all_of(data.begin(), data.end(),
                       [](auto const& x){ return x == 0; });
}

bool Matrix::is_constant() const
{
    return std::adjacent_find(data.begin(), data.end(), std::not_equal_to{})
        == data.end();
}

The is_*Triangular() predicates can be sped up in a similar manner to is_skewSymmetric(), by avoiding j <= i or j >= i as appropriate:

bool Matrix::is_orthogonal() const
{
    if (!is_square())
        return false;

    return(*this * transposed() == identity(width));
}

bool Matrix::is_invertible() const
{
    return determinant() != 0;
}

bool Matrix::is_lowerTriangular() const
{
    if (!is_square())
        return false;

    for (std::size_t i = 0;  i < height;  ++i)
        for (std::size_t j = i + 1;  j < width;  ++j)
            if (at(i, j))
                return false;

    return true;
}

bool Matrix::is_upperTriangular() const
{
    if (!is_square())
        return false;

    for (std::size_t i = 0;  i < height;  ++i)
        for (std::size_t j = 0;  j < i;  ++j)
            if (at(i, j) != 0)
                return false;

    return true;
}

In determinant(), many of the locals can be moved to smaller scope. We're also calling pivotEqualTo_one_Found() and pivotNot_zero_Found() every time through the loop regardless of whether we use the results. We can short-circuit test to only call those functions when needed, and also combine their results to a single block:

    std::size_t other_row;
    if (mx.at(pivot_row, pivot_col) != 1 && mx.pivotEqualTo_one_Found(pivot_row, pivot_col, other_row)
        || mx.at(pivot_row, pivot_col) == 0 && mx.pivotNot_zero_Found(pivot_row, pivot_col, other_row))
    {
        mx.swapRows(pivot_row, other_row);
        sign *= -1;
    }

Immediatlely after this, we call firstNumberNot_zero() but ignore the result. This is a serious bug, as col_dif_zero will be uninitialised if it returned false, meaning Undefined Behaviour. I think that if we have a row with all zeros, then the result will be zero, so we can return immediately in that case.

Modified:

Fraction Matrix::determinant() const
{
    assert(is_square());

    if (height == 1) {
        return at(0,0);
    }
    if (is_zero() || is_constant()) {
        return 0;
    }
    if (is_identity()) {
        return 1;
    }

    Matrix mx = *this;
    std::vector<Fraction> row_mults;
    int sign = 1;

    std::size_t pivot_row = 0;
    std::size_t pivot_col = 0;
    while (pivot_row < (height - 1)) {
        std::size_t other_row;
        if (mx.at(pivot_row, pivot_col) != 1 && mx.pivotEqualTo_one_Found(pivot_row, pivot_col, other_row)
            || mx.at(pivot_row, pivot_col) == 0 && mx.pivotNot_zero_Found(pivot_row, pivot_col, other_row))
        {
            mx.swapRows(pivot_row, other_row);
            sign *= -1;
        }

        std::size_t col_dif_zero;

        if (!mx.firstNumberNot_zero(pivot_row, col_dif_zero)) {
            return 0;
        }

        if (mx.at(pivot_row, col_dif_zero) != 1) {
            row_mults.push_back(mx.at(pivot_row, col_dif_zero));
            mx.changePivotTo_one(pivot_row, mx.at(pivot_row, col_dif_zero));
        }

        for (std::size_t n = pivot_row + 1;  n < height;  ++n) {
            auto const constant = mx.at(n, col_dif_zero);
            if (mx.at(n, col_dif_zero)) {
                mx.zeroOutTheColumn(n, pivot_row, constant);
            }
        }

        ++pivot_row;
        ++pivot_col;
    }

    Fraction det = sign;
    for (std::size_t i = 0;  i < height;  ++i) {
        det *= mx.at(i, i);
    }

    // now multiply by all the row_mults
    return std::accumulate(row_mults.begin(), row_mults.end(),
                           det, std::multiplies());
}

Looking next at inverse(), it writes output to std::cout. We should use std::cerr for error messages; in a library, we should strive to avoid writing to standard streams, and instead signal the caller by different means - I'd suggest raising an exception instead.

We can make a similar simplification as we did to determinant() where we swap rows in the Gauss elimination step. Following that, we have:

    if (number_not_zero_found) {
        ...
    }

    if (number_not_zero_found) {
        ...
    }

The value isn't changed in the block of the first if, so just combine these. There's a similar structure in the Jordan elimination step, too. That gives us:

Matrix Matrix::inverse() const
{
    assert(is_square());

    if (!is_invertible()) {
        throw std::range_error("Matrix not invertible");
    }

    Matrix mx = *this;
    Matrix inverse = identity(height);

    //Gauss Elimination
    std::size_t pivot_row = 0;
    std::size_t pivot_col = 0;
    while (pivot_row < (height - 1)) {
        std::size_t other_row;
        if (mx.at(pivot_row, pivot_col) != 1 && mx.pivotEqualTo_one_Found(pivot_row, pivot_col, other_row)
            || mx.at(pivot_row, pivot_col) == 0 && mx.pivotNot_zero_Found(pivot_row, pivot_col, other_row))
        {
            mx.swapRows(pivot_row, other_row);
            inverse.swapRows(pivot_row, other_row);
        }

        std::size_t col_dif_zero;
        if (mx.firstNumberNot_zero(pivot_row, col_dif_zero)) {
            if (mx.at(pivot_row, col_dif_zero) != 1) {
                inverse.changePivotTo_one(pivot_row, mx.at(pivot_row, col_dif_zero));
                mx.changePivotTo_one(pivot_row, mx.at(pivot_row, col_dif_zero));
            }
            for (std::size_t n = pivot_row + 1;  n < height; ++n) {
                inverse.zeroOutTheColumn(n, pivot_row, mx.at(n, col_dif_zero));
                mx.zeroOutTheColumn(n, pivot_row, mx.at(n, col_dif_zero));
            }
        }

        ++pivot_row;
        ++pivot_col;
    }

    //Jordan Elimination
    while (pivot_row > 0) {
        std::size_t col_dif_zero;
        if (mx.firstNumberNot_zero(pivot_row, col_dif_zero)) {
            if (mx.at(pivot_row, col_dif_zero) != 1) {
                inverse.changePivotTo_one(pivot_row, mx.at(pivot_row, col_dif_zero));
                mx.changePivotTo_one(pivot_row, mx.at(pivot_row, col_dif_zero));
            }
            for (size_t n = pivot_row;  n > 0;  --n) {
                inverse.zeroOutTheColumn(n - 1, pivot_row, mx.at(n - 1, col_dif_zero));
                mx.zeroOutTheColumn(n - 1, pivot_row, mx.at(n - 1, col_dif_zero));

            }
        }
        --pivot_row;
    }

    return inverse;
}

We can apply the same simplifications to gaussJordanElimination:

Matrix Matrix::gaussJordanElimination() const
{
    Matrix mx = *this;

    std::size_t pivot_row = 0;
    std::size_t pivot_col = 0;

    ///Gauss Elimination
    while (pivot_row < (height - 1)) {
        std::size_t other_row;
        if (mx.at(pivot_row, pivot_col) != 1 && mx.pivotEqualTo_one_Found(pivot_row, pivot_col, other_row)
            || mx.at(pivot_row, pivot_col) == 0 && mx.pivotNot_zero_Found(pivot_row, pivot_col, other_row))
        {
            mx.swapRows(pivot_row, other_row);
        }

        std::size_t col_dif_zero;
        if (mx.firstNumberNot_zero(pivot_row, col_dif_zero)) {
            if ((mx.at(pivot_row, col_dif_zero)) != 1) {
                mx.changePivotTo_one(pivot_row, mx.at(pivot_row, col_dif_zero));
            }

            for (std::size_t n = pivot_row + 1;  n < height;  ++n) {
                mx.zeroOutTheColumn(n, pivot_row, mx.at(n, col_dif_zero));
            }
        }

        ++pivot_row;
        ++pivot_col;
    }

    //Jordan Elimination
    while (pivot_row > 0) {
        std::size_t col_dif_zero;
        if (mx.firstNumberNot_zero(pivot_row, col_dif_zero)) {
            if ((mx.at(pivot_row, col_dif_zero)) != 1) {
                mx.changePivotTo_one(pivot_row, mx.at(pivot_row, col_dif_zero));
            }
        }

        for (std::size_t n = pivot_row;  n > 0;  --n) {
            mx.zeroOutTheColumn(n-1, pivot_row, mx.at(n-1, col_dif_zero));
        }
        --pivot_row;
    }

    return mx;
}

Full refactored code

Fraction.h

#ifndef FRACTION_H_INCLUDED
#define FRACTION_H_INCLUDED

#include <iosfwd>
#include <string>
#include <utility>

#include <boost/multiprecision/cpp_int.hpp>

class Fraction
{
    using cpp_int = boost::multiprecision::cpp_int;

    cpp_int num;
    cpp_int den;

public:
    Fraction(cpp_int num = 0, cpp_int den = 1)
        : num{std::move(num)},
          den{std::move(den)}
    {}

    Fraction(int num, int den = 1)
        : num{num},
          den{den}
    {}

    friend std::ostream& operator<<(std::ostream& os, const Fraction& fr);

    std::string to_string() const;

    bool operator==(const Fraction& fr) const { return num == fr.num && den == fr.den; }
    bool operator!=(const Fraction& fr) const { return !(*this == fr); }

    bool operator<(const Fraction& fr) const { return num * fr.den < den * fr.num; }
    bool operator<=(const Fraction& fr) const { return *this == fr || *this < fr; }
    bool operator>(const Fraction& fr) const { return !(*this<=fr); }
    bool operator>=(const Fraction& fr) const { return !(*this<fr); }

    explicit operator bool() const { return num != 0; }

    Fraction operator+() const;
    Fraction operator-() const;

    Fraction& operator++();
    Fraction& operator--();

    Fraction operator++(int);
    Fraction operator--(int);

    Fraction& operator+=(const Fraction& fr);
    Fraction& operator-=(const Fraction& fr);
    Fraction& operator*=(const Fraction& fr);
    Fraction& operator/=(const Fraction& fr);

private:
    static cpp_int gcd(const cpp_int& a, const cpp_int& b);
    void simplify();
};

Fraction operator+(Fraction a, const Fraction& b) { return a += b; }
Fraction operator-(Fraction a, const Fraction& b) { return a -= b; }
Fraction operator*(Fraction a, const Fraction& b) { return a *= b; }
Fraction operator/(Fraction a, const Fraction& b) { return a /= b; }

#endif // FRACTION_H_INCLUDED

Matrix.h

#ifndef MATRIX_H_INCLUDED
#define MATRIX_H_INCLUDED

#include "Fraction.h"

#include <cassert>
#include <cstddef>
#include <iosfwd>
#include <vector>

class Matrix
{
    std::size_t height = 0;
    std::size_t width = 0;

    std::vector<Fraction> data = {};

    Fraction& at(std::size_t r, std::size_t c)
    { return data[r * width + c]; }

    const Fraction& at(std::size_t r, std::size_t c) const
    { return data[r * width + c]; }


public:
    Matrix()
        : Matrix{0, 0}
    {}

    Matrix(std::size_t height, std::size_t width, const Fraction& n = 0)
        : height{height},
          width{width},
          data(width * height, n)
    {}

    Matrix(std::size_t height, std::size_t width, std::initializer_list<Fraction> values)
        : height{height},
          width{width},
          data(values)
    {
        assert(values.size() == size());
    }

    friend std::ostream& operator<<(std::ostream& out, const Matrix& mx);

    bool operator==(const Matrix& mx) const;
    bool operator!=(const Matrix& mx) const;

    Matrix& operator+=(const Matrix& mx);
    Matrix& operator-=(const Matrix& mx);
    Matrix& operator*=(const Matrix& mx);
    Matrix operator*(const Matrix&) const;

    // scalar multiplication
    Matrix& operator*=(const Fraction& n);

    Fraction& operator()(std::size_t r, std::size_t c)
    { return at(r, c); }

    const Fraction& operator()(std::size_t r, std::size_t c) const
    { return at(r, c); }

    std::size_t size() const
    { return height * width; }

    std::size_t rows() const
    { return height; }

    std::size_t cols() const
    { return width; }

    static Matrix identity(std::size_t n);

    bool is_square() const
    { return height == width; }

    bool is_identity() const;
    bool is_symmetric() const;
    bool is_skewSymmetric() const;
    bool is_diagonal() const;
    bool is_zero() const;
    bool is_constant() const;
    bool is_orthogonal() const;
    bool is_invertible() const;
    bool is_upperTriangular() const;
    bool is_lowerTriangular() const;

    Matrix transposed() const;
    Fraction determinant() const;
    Matrix inverse() const;
    Matrix gaussJordanElimination() const;

private:
    void swapRows(std::size_t row1, std::size_t row2);
    bool pivotEqualTo_one_Found(std::size_t pivot_row, std::size_t pivot_col, std::size_t& alternative_pivot_row) const;
    bool pivotNot_zero_Found(std::size_t pivot_row, std::size_t pivot_col, std::size_t& col_dif_zero) const;
    bool firstNumberNot_zero(std::size_t row_num, std::size_t& num_coluna_num_dif_zero) const;
    void changePivotTo_one(std::size_t row_num, Fraction constant);
    void zeroOutTheColumn(std::size_t row_num, std::size_t num_pivot_row, Fraction constant);
};


Matrix operator+(Matrix a, const Matrix& b)
{ return a += b; }

Matrix operator-(Matrix a, const Matrix& b)
{ return a -= b; }

Matrix operator*(Matrix mx, const Fraction& n)
{ return mx *= n; }

Matrix operator*(const Fraction& n, Matrix mx)
{ return mx *= n; }

#endif // MATRIX_H_INCLUDED

Fraction.cpp

#include "Fraction.h"

#include <ostream>
#include <sstream>

std::ostream& operator<<(std::ostream& os, const Fraction& fr)
{
    os << fr.num;
    if (fr.den != 1) {
        os << "/" << fr.den;
    }
    return os;
}

std::string Fraction::to_string() const
{
    std::ostringstream os;
    os << *this;
    return os.str();
}

Fraction::cpp_int Fraction::gcd(const Fraction::cpp_int& a, const Fraction::cpp_int& b)
{
    return b ? gcd(b, a%b) : a;
}

void Fraction::simplify()
{
    // Denominators are always positive
    if (den < 0) {
        num = -num;
        den = -den;
    }

    // Factor out gcd from numerator and denominator.
    auto const n = gcd(abs(num), den);
    num /= n;
    den /= n;
}


Fraction Fraction::operator+() const
{
    return *this;
}

Fraction Fraction::operator-() const
{
    return { -num, den };
}

Fraction& Fraction::operator++()
{
    num += den;
    return *this;
}

Fraction& Fraction::operator--()
{
    num -= den;
    return *this;
}

Fraction Fraction::operator++(int)
{
    auto old = *this;
    ++*this;
    return old;
}

Fraction Fraction::operator--(int)
{
    auto old = *this;
    --*this;
    return old;
}

Fraction& Fraction::operator+=(const Fraction& fr)
{
    num = num * fr.den + fr.num * den;
    den *= fr.den;
    simplify();
    return *this;
}

Fraction& Fraction::operator-=(const Fraction& fr)
{
    return *this += -fr;
}

Fraction& Fraction::operator*=(const Fraction& fr)
{
    num *= fr.num;
    den *= fr.den;
    simplify();
    return *this;
}

Fraction& Fraction::operator/=(const Fraction& fr)
{
    return *this *= { fr.den, fr.num };
}

Matrix.cpp

#include "Matrix.h"

#include <algorithm>
#include <cassert>
#include <functional>
#include <iomanip>
#include <iostream>
#include <numeric>

std::ostream& operator<<(std::ostream& os, const Matrix& mx)
{
    // find maximum element width
    std::size_t max_width = 1;
    for (auto const& element: mx.data) {
        auto w = element.to_string().size();
        if (w > max_width) {
            max_width = w;
        }
    }

    max_width += 4;             // add padding between elements

    // use the max width to format elements
    for (std::size_t i = 0;  i < mx.height;  i++) {
        for (std::size_t j = 0;  j < mx.width;  j++) {
            os << std::setw(max_width) <<  mx.at(i, j);
        }
        os << std::endl;
    }

    return os;
}

bool Matrix::operator==(const Matrix& mx) const
{
    return width == mx.width
        && data == mx.data;
}

bool Matrix::operator!=(const Matrix& mx) const
{
    return !(*this == mx);
}

Matrix Matrix::operator*(const Matrix& mx) const
{
    assert(width == mx.height);

    Matrix mult(height, mx.width);

    for (std::size_t i = 0;  i < height;  ++i)
        for (std::size_t j = 0;  j < mx.width;  ++j)
            for (std::size_t x = 0;  x < width;  ++x)
                mult.at(i, j) += at(i, x) * mx.at(x, j);

    return mult;
}

Matrix& Matrix::operator*=(const Matrix& mx)
{
    return *this = (*this * mx);
}

Matrix& Matrix::operator+=(const Matrix& mx)
{
    assert(height == mx.height);
    assert(width == mx.width);
    std::transform(data.begin(), data.end(),
                   mx.data.begin(), data.begin(),
                   std::plus{});
    return *this;
}

Matrix& Matrix::operator-=(const Matrix& mx)
{
    assert(height == mx.height);
    assert(width == mx.width);
    std::transform(data.begin(), data.end(),
                   mx.data.begin(), data.begin(),
                   std::minus{});
    return *this;
}

Matrix& Matrix::operator*=(const Fraction& n)
{
    for (auto& element: data) {
        element *= n;
    }
    return *this;
}

Matrix Matrix::identity(std::size_t n)
{
    assert(n > 0);

    Matrix mx{n,n};
    for (std::size_t i = 0; i < n; ++i)
        mx.at(i,i) = 1;

    return mx;
}

bool Matrix::is_identity() const
{
    if (! is_square())
        return false;

    return *this == identity(width);
}

bool Matrix::is_symmetric() const
{
    return *this == transposed();
}

bool Matrix::is_skewSymmetric() const
{
    if (!is_square()) {
        return false;
    }

    for (std::size_t i = 0; i < height; ++i) {
        for (std::size_t j = i+1; j < width; ++j) {
            if (at(i, j) != -at(j, i)) {
                return false;
            }
        }
    }

    return true;
}

bool Matrix::is_diagonal() const
{
    if (!is_square())
        return false;

    for (std::size_t i = 0; i < height; ++i)
        for (std::size_t j = 0; j < width; ++j)
            if (i != j && at(i, j) != 0)
                return false;

    return true;
}

bool Matrix::is_zero() const
{
    return std::all_of(data.begin(), data.end(),
                       [](auto const& x){ return x == 0; });
}

bool Matrix::is_constant() const
{
    return std::adjacent_find(data.begin(), data.end(), std::not_equal_to{})
        == data.end();
}

bool Matrix::is_orthogonal() const
{
    if (!is_square())
        return false;

    return(*this * transposed() == identity(width));
}

bool Matrix::is_invertible() const
{
    return determinant() != 0;
}

bool Matrix::is_lowerTriangular() const
{
    if (!is_square())
        return false;

    for (std::size_t i = 0;  i < height;  ++i)
        for (std::size_t j = i + 1;  j < width;  ++j)
            if (at(i, j))
                return false;

    return true;
}

bool Matrix::is_upperTriangular() const
{
    if (!is_square())
        return false;

    for (std::size_t i = 0;  i < height;  ++i)
        for (std::size_t j = 0;  j < i;  ++j)
            if (at(i, j) != 0)
                return false;

    return true;
}

Matrix Matrix::transposed() const
{
    Matrix trans(width, height);

    for (std::size_t i = 0; i < height; ++i)
        for (std::size_t j = 0; j < width; ++j)
            trans.at(j, i) = at(i, j);

    return trans;
}

Fraction Matrix::determinant() const
{
    assert(is_square());

    if (height == 1) {
        return at(0,0);
    }
    if (is_zero() || is_constant()) {
        return 0;
    }
    if (is_identity()) {
        return 1;
    }

    Matrix mx = *this;
    std::vector<Fraction> row_mults;
    int sign = 1;

    std::size_t pivot_row = 0;
    std::size_t pivot_col = 0;
    while (pivot_row < (height - 1)) {
        std::size_t other_row;
        if (mx.at(pivot_row, pivot_col) != 1 && mx.pivotEqualTo_one_Found(pivot_row, pivot_col, other_row)
            || mx.at(pivot_row, pivot_col) == 0 && mx.pivotNot_zero_Found(pivot_row, pivot_col, other_row))
        {
            mx.swapRows(pivot_row, other_row);
            sign *= -1;
        }

        std::size_t col_dif_zero;

        if (!mx.firstNumberNot_zero(pivot_row, col_dif_zero)) {
            return 0;
        }

        if (mx.at(pivot_row, col_dif_zero) != 1) {
            row_mults.push_back(mx.at(pivot_row, col_dif_zero));
            mx.changePivotTo_one(pivot_row, mx.at(pivot_row, col_dif_zero));
        }

        for (std::size_t n = pivot_row + 1;  n < height;  ++n) {
            auto const constant = mx.at(n, col_dif_zero);
            if (mx.at(n, col_dif_zero)) {
                mx.zeroOutTheColumn(n, pivot_row, constant);
            }
        }

        ++pivot_row;
        ++pivot_col;
    }

    Fraction det = sign;
    for (std::size_t i = 0;  i < height;  ++i) {
        det *= mx.at(i, i);
    }

    // now multiply by all the row_mults
    return std::accumulate(row_mults.begin(), row_mults.end(),
                           det, std::multiplies());
}

Matrix Matrix::inverse() const
{
    assert(is_square());

    if (!is_invertible()) {
        throw std::range_error("Matrix not invertible");
    }

    Matrix mx = *this;
    Matrix inverse = identity(height);

    std::size_t pivot_row = 0;
    std::size_t pivot_col = 0;

    //Gauss Elimination
    while (pivot_row < (height - 1)) {
        std::size_t other_row;
        if (mx.at(pivot_row, pivot_col) != 1 && mx.pivotEqualTo_one_Found(pivot_row, pivot_col, other_row)
            || mx.at(pivot_row, pivot_col) == 0 && mx.pivotNot_zero_Found(pivot_row, pivot_col, other_row))
        {
            mx.swapRows(pivot_row, other_row);
            inverse.swapRows(pivot_row, other_row);
        }

        std::size_t col_dif_zero;
        if (mx.firstNumberNot_zero(pivot_row, col_dif_zero)) {
            if (mx.at(pivot_row, col_dif_zero) != 1) {
                inverse.changePivotTo_one(pivot_row, mx.at(pivot_row, col_dif_zero));
                mx.changePivotTo_one(pivot_row, mx.at(pivot_row, col_dif_zero));
            }
            for (std::size_t n = pivot_row + 1;  n < height; ++n) {
                inverse.zeroOutTheColumn(n, pivot_row, mx.at(n, col_dif_zero));
                mx.zeroOutTheColumn(n, pivot_row, mx.at(n, col_dif_zero));
            }
        }

        ++pivot_row;
        ++pivot_col;
    }

    //Jordan Elimination
    while (pivot_row > 0) {
        std::size_t col_dif_zero;
        if (mx.firstNumberNot_zero(pivot_row, col_dif_zero)) {
            if (mx.at(pivot_row, col_dif_zero) != 1) {
                inverse.changePivotTo_one(pivot_row, mx.at(pivot_row, col_dif_zero));
                mx.changePivotTo_one(pivot_row, mx.at(pivot_row, col_dif_zero));
            }
            for (std::size_t n = pivot_row;  n > 0;  --n) {
                inverse.zeroOutTheColumn(n - 1, pivot_row, mx.at(n - 1, col_dif_zero));
                mx.zeroOutTheColumn(n - 1, pivot_row, mx.at(n - 1, col_dif_zero));

            }
        }
        --pivot_row;
    }

    return inverse;
}

Matrix Matrix::gaussJordanElimination() const
{
    Matrix mx = *this;

    std::size_t pivot_row = 0;
    std::size_t pivot_col = 0;

    ///Gauss Elimination
    while (pivot_row < (height - 1)) {
        std::size_t other_row;
        if (mx.at(pivot_row, pivot_col) != 1 && mx.pivotEqualTo_one_Found(pivot_row, pivot_col, other_row)
            || mx.at(pivot_row, pivot_col) == 0 && mx.pivotNot_zero_Found(pivot_row, pivot_col, other_row))
        {
            mx.swapRows(pivot_row, other_row);
        }

        std::size_t col_dif_zero;
        if (mx.firstNumberNot_zero(pivot_row, col_dif_zero)) {
            if ((mx.at(pivot_row, col_dif_zero)) != 1) {
                mx.changePivotTo_one(pivot_row, mx.at(pivot_row, col_dif_zero));
            }

            for (std::size_t n = pivot_row + 1;  n < height;  ++n) {
                mx.zeroOutTheColumn(n, pivot_row, mx.at(n, col_dif_zero));
            }
        }

        ++pivot_row;
        ++pivot_col;
    }

    //Jordan Elimination
    while (pivot_row > 0) {
        std::size_t col_dif_zero;
        if (mx.firstNumberNot_zero(pivot_row, col_dif_zero)) {
            if ((mx.at(pivot_row, col_dif_zero)) != 1) {
                mx.changePivotTo_one(pivot_row, mx.at(pivot_row, col_dif_zero));
            }
        }

        for (std::size_t n = pivot_row;  n > 0;  --n) {
            mx.zeroOutTheColumn(n-1, pivot_row, mx.at(n-1, col_dif_zero));
        }
        --pivot_row;
    }

    return mx;
}

#include <algorithm>

void Matrix::swapRows(std::size_t row1, std::size_t row2)
{
    auto const a1 = data.begin() + width * row1;
    auto const z1 = a1 + width;
    auto const a2 = data.begin() + width * row2;
    std::swap_ranges(a1, z1, a2);
}

bool Matrix::pivotEqualTo_one_Found(std::size_t pivot_row, std::size_t pivot_col, std::size_t& alternative_pivot_row) const
{
    for (std::size_t i = pivot_row + 1;  i < height;  ++i) {
        if (at(i, pivot_col) == 1) {
            alternative_pivot_row = i;
            return true;
        }
    }

    return false;
}

bool Matrix::pivotNot_zero_Found(std::size_t pivot_row, std::size_t pivot_col, std::size_t& col_dif_zero) const
{
    for (std::size_t i = pivot_row + 1;  i < height;  ++i) {
        if (at(i, pivot_col)) {
            col_dif_zero = i;
            return true;
        }
    }

    return false;
}

bool Matrix::firstNumberNot_zero(std::size_t row_num, std::size_t& num_coluna_num_dif_zero) const
{
    for (std::size_t i = 0;  i < width;  ++i) {
        if (at(row_num, i) != 0) {
            num_coluna_num_dif_zero = i;
            return true;
        }
    }

    return false;
}

void Matrix::changePivotTo_one(std::size_t row_num, Fraction constant)
{
    for (std::size_t i = 0;  i < width;  ++i)
        if (at(row_num, i) == 0)
            at(row_num, i) = at(row_num, i);
        else
            at(row_num, i) = at(row_num, i) / constant;
}

void Matrix::zeroOutTheColumn(std::size_t row_num, std::size_t num_pivot_row, Fraction constant)
{
    for (std::size_t i = 0;  i < width;  ++i) {
        at(row_num, i) -= at(num_pivot_row, i) * constant;
    }
}

Unit tests

As I said, we could do with some unit tests to give us more confidence when making changes. Here's a few to be starting with:

#include <gtest/gtest.h>

TEST(Fraction, equals)
{
    const Fraction zero{};
    const Fraction one{1};
    const Fraction another_one{1};
    const Fraction three_quarters{3,4};
    const Fraction three_fourths{3,4};
    EXPECT_EQ(zero, zero);
    EXPECT_NE(zero, one);
    EXPECT_EQ(one, another_one);
    EXPECT_EQ(three_quarters, three_fourths);
    EXPECT_NE(one, three_quarters);
}

TEST(Fraction, compare)
{
    const Fraction zero{};
    const Fraction one{1};
    const Fraction three_quarters{3,4};
    EXPECT_FALSE(zero < zero);
    EXPECT_TRUE(zero <= zero);
    EXPECT_TRUE(zero < one);
    EXPECT_TRUE(three_quarters < one);
    EXPECT_TRUE(three_quarters <= one);
    EXPECT_FALSE(zero > zero);
    EXPECT_TRUE(zero >= zero);
    EXPECT_FALSE(zero > one);
    EXPECT_FALSE(three_quarters > one);
    EXPECT_FALSE(three_quarters >= one);
}

TEST(Fraction, to_string)
{
    // Since to_string is implemented in terms of operator<<, we're
    // fully testing that, too.
    const Fraction zero{};
    const Fraction one{1};
    const Fraction half{1,2};
    EXPECT_EQ("0", zero.to_string());
    EXPECT_EQ("1", one.to_string());
    EXPECT_EQ("1/2", half.to_string());
}

TEST(Fraction, simplify)
{
    const Fraction half{1,2};
    const Fraction x{2,4};
    const Fraction y{3,6};
    EXPECT_EQ(x, half);
    EXPECT_EQ(y, half);

    const Fraction minus_one_half{-1,2};
    const Fraction one_minus_half{1,-2};
    EXPECT_EQ(minus_one_half, one_minus_half);
}

TEST(Fraction, increment_decrement)
{
    const Fraction one_quarter{1,4};
    const Fraction five_quarters{5,4};
    const Fraction nine_quarters{9,4};
    auto a = one_quarter;
    EXPECT_EQ(five_quarters, ++a);
    EXPECT_EQ(five_quarters, a);
    EXPECT_EQ(five_quarters, a++);
    EXPECT_EQ(nine_quarters, a);
    auto b = nine_quarters;
    EXPECT_EQ(five_quarters, --b);
    EXPECT_EQ(five_quarters, b);
    EXPECT_EQ(five_quarters, b--);
    EXPECT_EQ(one_quarter, b);
}

TEST(Fraction, add_subtract)
{
    // These are implemented in terms of += and -=
    const Fraction one_quarter{1,4};
    const Fraction one_half{1,2};
    const Fraction minus_one_half{1,-2};
    const Fraction five_sixths{5,6};
    const Fraction seven_twelfths{7,12};
    EXPECT_EQ(one_half, +one_half);
    EXPECT_EQ(minus_one_half, -one_half);
    EXPECT_EQ(0-one_half, -one_half);
    EXPECT_EQ(one_half, one_quarter + one_quarter);
    EXPECT_EQ(one_half - one_quarter, one_quarter);
    EXPECT_EQ(seven_twelfths, five_sixths - one_quarter);
}

TEST(Fraction, multiply_divide)
{
    // These are implemented in terms of *= and /=
    const Fraction one_quarter{1,4};
    const Fraction one_half{1,2};
    EXPECT_EQ(one_half, one_quarter * 2);
    EXPECT_EQ(one_half, 2 * one_quarter);
    EXPECT_EQ(one_half, one_quarter / one_half);
}

TEST(Matrix, equals)
{
    EXPECT_EQ(Matrix{}, Matrix{});
    EXPECT_EQ(Matrix::identity(3), Matrix::identity(3));
    EXPECT_NE(Matrix{}, Matrix::identity(1));

    const Matrix all_zero{3, 3, 0};
    const Matrix all_one{3, 3, 1};
    const Matrix all_default{3, 3};
    EXPECT_EQ(all_zero, all_default);
    EXPECT_NE(all_zero, all_one);

    const Matrix two_by_three{2, 3};
    const Matrix three_by_two{3, 2};
    EXPECT_NE(two_by_three, three_by_two);
}

TEST(Matrix, accessors)
{
    const Matrix two_by_three{2, 3};
    EXPECT_EQ(2, two_by_three.rows());
    EXPECT_EQ(3, two_by_three.cols());
    EXPECT_EQ(6, two_by_three.size());
    EXPECT_FALSE(two_by_three.is_square());
    EXPECT_FALSE(two_by_three.is_identity());
    EXPECT_TRUE(two_by_three.is_constant());
    EXPECT_TRUE(two_by_three.is_zero());

    const Matrix null{};
    EXPECT_TRUE(null.is_zero());
    EXPECT_TRUE(null.is_square());
    EXPECT_TRUE(null.is_symmetric());
    EXPECT_TRUE(null.is_skewSymmetric());

    const Matrix zero{2,2};
    EXPECT_TRUE(zero.is_zero());
    EXPECT_TRUE(zero.is_square());
    EXPECT_FALSE(zero.is_identity());
    EXPECT_TRUE(zero.is_symmetric());
    EXPECT_TRUE(zero.is_skewSymmetric());

    const Matrix one{2,2,1};
    EXPECT_FALSE(one.is_zero());
    EXPECT_TRUE(one.is_constant());
    EXPECT_TRUE(one.is_square());
    EXPECT_FALSE(one.is_identity());
    EXPECT_TRUE(one.is_symmetric());
    EXPECT_FALSE(one.is_skewSymmetric());
    EXPECT_FALSE(one.is_upperTriangular());
    EXPECT_FALSE(one.is_lowerTriangular());

    const Matrix identity = Matrix::identity(2);
    EXPECT_FALSE(identity.is_zero());
    EXPECT_FALSE(identity.is_constant());
    EXPECT_TRUE(identity.is_square());
    EXPECT_TRUE(identity.is_identity());
    EXPECT_TRUE(identity.is_symmetric());
    EXPECT_TRUE(identity.is_skewSymmetric());
    EXPECT_TRUE(identity.is_upperTriangular());
    EXPECT_TRUE(identity.is_lowerTriangular());

    Matrix two_by_two{2, 2,
                      {1, 2,
                       0, 4}};
    EXPECT_TRUE(two_by_two.is_upperTriangular());
    EXPECT_FALSE(two_by_two.is_lowerTriangular());
    EXPECT_FALSE(two_by_two.is_skewSymmetric());
    EXPECT_FALSE(two_by_two.is_symmetric());
    two_by_two(1,0) = 2;
    EXPECT_FALSE(two_by_two.is_skewSymmetric());
    EXPECT_TRUE(two_by_two.is_symmetric());
    two_by_two(1,0) = -2;
    EXPECT_TRUE(two_by_two.is_skewSymmetric());
    EXPECT_FALSE(two_by_two.is_symmetric());
    two_by_two(0,1) = 0;
    EXPECT_FALSE(two_by_two.is_upperTriangular());
    EXPECT_TRUE(two_by_two.is_lowerTriangular());
}

TEST(Matrix, plus_minus)
{
    Matrix zero{3,2};
    Matrix one{3,2,1};
    Matrix two{3,2,2};
    Matrix three{3,2,3};
    ASSERT_EQ(one, one + zero);
    ASSERT_EQ(three, one + two);
    ASSERT_EQ(two, three - one);
    ASSERT_EQ(zero, one - one);
}

TEST(Matrix, transposed)
{
    Matrix a{2, 3,
             { 1, 2, 3,
               4, 5, 6 }};
    Matrix b{3, 2,
             { 1, 4,
               2, 5,
               3, 6 }};
    ASSERT_EQ(a, b.transposed());
    ASSERT_EQ(b, a.transposed());
}

TEST(Matrix, determinant)
{
    // identity matrices have determinant == 1
    ASSERT_EQ(Fraction{1}, Matrix::identity(3).determinant());
    // example from Wikipedia
    Matrix a{3, 3,
             { -2, 2, -3,
               -1, 1,  3,
               2,  0, -1 }};
    ASSERT_EQ(Fraction{18}, a.determinant());
    // from https://people.richland.edu/james/lecture/m116/matrices/determinant.html
    Matrix b{4, 4,
             { 3, 2, 0, 1,
               4, 0, 1, 2,
               3, 0, 2, 1,
               9, 2, 3, 1 }};
    ASSERT_EQ(Fraction{24}, b.determinant());
}

TEST(Matrix, inverse)
{
    Matrix a{3, 3,
             { -2, 2, -3,
               -1, 1,  3,
               2,  0, -1 }};
    Matrix b = a.inverse();
    ASSERT_EQ(a * b, Matrix::identity(3));
    ASSERT_EQ(b * a, Matrix::identity(3));
}
\$\endgroup\$
3
3
\$\begingroup\$

Some comments, more about methods that about the code.

Fraction

Personally I would have called it Rational since that what it is: rational numbers. But this is a matter of taste.

The constructor should assert that _den != 0.

The operator!= should compare the simplified fractions.

Should it not be lcm (lowest common multiple) and gcd (greatest common divisor)?

Matrix

Your matrices are dense, meaning that you keep track of all entries. If you care for performance you should also implement sparse matrices, which only keep track of the non-zero entries. There are several data structures for sparse matrices.

Identity and constant matrices should have a special separate representation and re-implement the operations for performance. Actually you should use polymorphism and have a type hierarchy that accounts for: dense matrices, sparse matrices, diagonal matrices, constant matrices, identity matrix. Diagonal matrices will use one single vector of fractions, constant matrices only one fraction and the identity doesn't need any internal representation.

You should use factorizations, instead of brute force for performance:

\$\endgroup\$
1
  • \$\begingroup\$ The suggestions are good, I will have to learn them though, learn to do them by hand, only then writing them in cpp. \$\endgroup\$
    – HBatalha
    Commented Feb 8, 2020 at 21:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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