Entwicklung einer einfachen Matrizenklasse



  • @SeppJ sagte in Entwicklung einer einfachen Matrizenklasse:

    Bewusste Absicht von was? 1 ist 1. noch dazu auf der rechten Seite einer Zuweisung. Soll man wirklich für den Fall programmieren, dass val_t einen überladenen Zuweisungsoperator hat? Der zudem eine sehr sonderbare (nicht-arithmetische) Semantik hätte.

    Simple Dokumentation, dass man die Repräsentation der 1 als float, complex oder sonstwas in dem Element speichern möchte und man sich bewusst ist, dass die Matrix nicht unbedingt ints enthält. Da denke ich nichtmal an komische Typen, sondern dass jemand, der meinen Code liest, klar meine Intention herauslesen kann.

    Hier mag es trivial sein, woanders vielleicht nicht (und jemand stellt meinen Code um wo vielleicht eine Integer Promotion nicht mehr so läuft wie beabsichtigt). Es schadet jedenfalls nicht, klar in Code auszudrücken, was man will.

    @Swordfish sagte in Entwicklung einer einfachen Matrizenklasse:

    val_t{ 1 } ^^

    Ja, das wär auch okay 😉


  • Mod

    @Finnegan sagte in Entwicklung einer einfachen Matrizenklasse:

    @SeppJ sagte in Entwicklung einer einfachen Matrizenklasse:

    Bewusste Absicht von was? 1 ist 1. noch dazu auf der rechten Seite einer Zuweisung. Soll man wirklich für den Fall programmieren, dass val_t einen überladenen Zuweisungsoperator hat? Der zudem eine sehr sonderbare (nicht-arithmetische) Semantik hätte.

    Simple Dokumentation, dass man die Repräsentation der 1 als float, complex oder sonstwas in dem Element speichern möchte und man sich bewusst ist, dass die Matrix nicht unbedingt ints enthält. Da denke ich nichtmal an komische Typen, sondern dass jemand, der meinen Code liest, klar meine Intention herauslesen kann.

    Du hast komische Absichten und Dokumentation. Da steht links eine Variable von irgendeinem Typ, rechts der Wert 1, dazwischen ein =. Wie braucht man da noch extra Dokumentation, dass diese Zeile bezwecken soll, dass die Variable links den Wert rechts annimmt?

    Schreibst du auch std::string str = static_cast<std::string>("Hallo");?



  • @Swordfish sagte in Entwicklung einer einfachen Matrizenklasse:

    val_t{ 1 }

    Oh, cool. Wollte ich zuerst machen, erschien mir aber zu simpel. Aber noch wunderbarer ist einfach = 1. Hatte ich auch im Sinn, erschien mir aber schon unmöglich simpler.



  • @SeppJ sagte in Entwicklung einer einfachen Matrizenklasse:

    Du hast komische Absichten und Dokumentation. Da steht links eine Variable von irgendeinem Typ, rechts der Wert 1, dazwischen ein =.

    Links steht ein Funktionsaufruf.

    Wie braucht man da noch extra Dokumentation, dass diese Zeile bezwecken soll, dass die Variable links den Wert rechts annimmt?

    Nein, mein Code sagt, dass es meine Absicht ist, einen Wert vom Typ val_t zuzuweisen, dessen Wert 1 ist. Das sind zwei Eigenschaften, nicht nur der Wert.

    Bei diesem Code hier ist es wumpe, aber das weiss ich auch nur, weil ich den Code-Kontext kenne. Nur an der betreffenden Zeile kann ich das nicht sehen. Z.B. bei ein std::any oder etwas anderem mit Type Deduction könnte der Code im weiteren Verlauf empfindlich auf den fehlenden Cast reagieren.

    Bei einer Matrix rührt mein Bedürfnis dazu auch vornehmlich daher, dass ich eigentlich keine "Eins" will, sondern das Neutrale Element von val_t bezüglich Multiplikation. Das drückt der Cast für mich auch noch aus - ich will ein 1-Element des Typen val_t. Dazu kommt, dass das hier quasi Library-Code ist, da bin ich immer etwas expliziter. In User-Code würde ich auch ne blanke 1 schreiben ...

    Schreibst du auch std::string str = static_cast<std::string>("Hallo");?

    ... so einen Cast mache ich also nicht immer. Hier^^^ z.B. nicht 😉

    Ansonsten sehe ich keinen Schaden das so zu schreiben und verstehe nicht ganz warum das überhaupt eine Diskussion Wert ist. Ich zwinge ja niemanden, das auch so zu machen.


  • Mod

    @Finnegan sagte in Entwicklung einer einfachen Matrizenklasse:

    Ansonsten sehe ich keinen Schaden das so zu schreiben und verstehe nicht ganz warum das überhaupt eine Diskussion Wert ist. Ich zwinge ja niemanden, das auch so zu machen.

    Weil das komisch inkonsistenter Cargo-Cult ist. Wenn du irgendwie argumentierst, dass das sinnvoll ist, dann musst du std::string str = static_cast<std::string>("Hallo"); schreiben. Gerade dann, denn da macht der Cast ja tatsächlich etwas. Tust du aber nicht, weil das offensichtlicher Unsinn ist. Und genauso offensichtlicher Unsinn ist der Fall hier auch, wo du es plötzlich doch machen willst. Das heißt, du machst bloß irgendwelche Muster nach, die du mal gesehen hast, aber ohne sie verstanden zu haben.

    Mal anders gesagt: Was würde passieren, wenn da ut(n, n) = static_cast<float>(1) stünde, aber val_t kein float ist? Wird da deiner Meinung nach dann ein float gespeichert?



  • Wenn das so gemacht wird, ist es ok?

            template <typename val_t>
    	Mat2D_T<val_t> UnitMat(const std::size_t size)
    	{
    		Mat2D_T<val_t> ut(size, size);
    
    		for (std::size_t n = 0; n < size; ++n)
    			ut(n, n) = { 1 };
    
    		return ut;
    	}
    	template <typename val_t>
    	Mat2D_T<val_t> Point2D(const val_t x, const val_t y)
    	{
    		Mat2D_T<val_t> p{ { x, y, { 1 } } };
    		return p;
    	}
    	template <typename val_t>
    	Mat2D_T<val_t> Point2D(const std::initializer_list<val_t>& list)
    	{
    		Mat2D_T<val_t> p{ list };
    		return p;
    	}
    	template <typename val_t>
    	Mat2D_T<val_t> Point3D(const val_t x, const val_t y, const val_t z) 
    	{
    		Mat2D_T<val_t> p{ { x, y, z } };
    		return p;
    	}
    	template <typename val_t>
    	Mat2D_T<val_t> Point3D(const std::initializer_list<val_t> list)
    	{
    		Mat2D_T<val_t> p{ list };
    		return p;
    	}
    

  • Mod

    Lässt sich Zeile 7 überhaupt übersetzen?



  • Huch, Schreck! Sollte es nicht?

    Mat2D_T<float> ut = Mat2D::UnitMat<float>(4);
    Mat2D::print(ut);
    

    Unter Visual Studio 19 geht das bei mir. Ich kann aber gerne die geschweiften Klammern weglassen, wenn das wirklich kein Ding ist.
    Oder eben

    ut(n, n) = val_t{ 1 };
    

  • Mod

    @zeropage sagte in Entwicklung einer einfachen Matrizenklasse:

    Huch, Schreck! Sollte es nicht?

    Jetzt, wo ich das ganze Programm gelesen habe: Doch, sollte gehen. Aber ist eine sehr ungewöhnliche Schreibweise.

    Anders herum: Man sagt zurecht, wenn man Code schreibt, muss man von jedem einzelnen Zeichen genau wissen, wo und warum man es setzt. Kannst du erklären, wieso du die Klammern gesetzt hast?



  • An sich kam das von val_t v = {};als 0. Also eine leere Menge.

    Analog, auch wenn es mir in diesem Zusammenhang komisch vorkam, habe ich durch die letzten Beiträge dann eben eine Zahl eingesetzt.
    Ich dachte, Werte wie 1, 2, 3wären int-Konstanten, die ich nicht einfach einem template zuweisen kann. Hätte ich das mal gemacht.

    EDIT: Aber alles gut. Ist ja richtig, auf Unstimmigkeiten hinzuweisen.



  • Vorneweg, ich habe meinen Kenntnisstand über Matrizen von hier
    https://www.grund-wissen.de/mathematik/lineare-algebra-und-analytische-geometrie/matrizen.html
    Fortgeschrittenere Sachen kenne ich noch nicht.

    Ich habe einen Haufen Rechenoperationen hinzugefügt,

            Mat2D_T operator*(const Mat2D_T& mT) const
    	{
    		if (columns() != mT.rows())
    			throw std::exception("operator*()");
    
    		Mat2D_T nmT(rows(), mT.columns());
    		val_t v = {};
    		for (size_t c = 0; c < mT.columns(); ++c)
    		{
    			for (size_t r = 0; r < rows(); ++r)
    			{
    				v = (*this)(r, 0) * mT(0, c);
    				for (size_t l = 1; l < columns(); ++l)
    				{
    					v += (*this)(r, l) * mT(l, c);
    				}
    				nmT(r, c) = v;
    			}
    			v = {};
    		}
    		return nmT;
    	}
    
    	/////neu/////////////////////////////////////////////
    
    	Mat2D_T& operator+=(const val_t& v)
    	{
    		for (size_t r = 0; r < rows(); ++r)
    			for (size_t c = 0; c < columns(); ++c)
    				(*this)(r, c) = (*this)(r, c) + v;
    	}
    
    	Mat2D_T& operator-=(const val_t& v)
    	{
    		for (size_t r = 0; r < rows(); ++r)
    			for (size_t c = 0; c < columns(); ++c)
    				(*this)(r, c) = (*this)(r, c) - v;
    	}
    
    	Mat2D_T& operator*=(const val_t& v)
    	{
    		for (size_t r = 0; r < rows(); ++r)
    			for (size_t c = 0; c < columns(); ++c)
    				(*this)(r, c) = (*this)(r, c) * v;
    	}
    
    	Mat2D_T& operator/=(const val_t& v)
    	{
    		for (size_t r = 0; r < rows(); ++r)
    			for (size_t c = 0; c < columns(); ++c)
    				(*this)(r, c) = (*this)(r, c) / v;
    	}
    
    	Mat2D_T& operator+=(const Mat2D_T& mT)
    	{
    		if (rows() != mT.rows() && columns() != mT.columns())
    			throw std::exception("operator+=()");
    
    		for (size_t r = 0; r < rows(); ++r)
    			for (size_t c = 0; c < columns(); ++c)
    				(*this)(r, c) = (*this)(r, c) + mT(r, c);
    	}
    
    	Mat2D_T& operator-=(const Mat2D_T& mT)
    	{
    		if (rows() != mT.rows() && columns() != mT.columns())
    			throw std::exception("operator-=()");
    
    		for (size_t r = 0; r < rows(); ++r)
    			for (size_t c = 0; c < columns(); ++c)
    				(*this)(r, c) = (*this)(r, c) - mT(r, c);
    	}
    
    	Mat2D_T operator+(const val_t& v) const
    	{
    		Mat2D_T nmT(rows(), columns());
    
    		for (size_t r = 0; r < rows(); ++r)
    			for (size_t c = 0; c < columns(); ++c)
    				nmT(r, c) = (*this)(r, c) + v;
    
    		return nmT;
    	}
    
    	Mat2D_T operator-(const val_t& v) const
    	{
    		Mat2D_T nmT(rows(), columns());
    
    		for (size_t r = 0; r < rows(); ++r)
    			for (size_t c = 0; c < columns(); ++c)
    				nmT(r, c) = (*this)(r, c) - v;
    
    		return nmT;
    	}
    
    	Mat2D_T operator*(const val_t& v) const
    	{
    		Mat2D_T nmT(rows(), columns());
    
    		for (size_t r = 0; r < rows(); ++r)
    			for (size_t c = 0; c < columns(); ++c)
    				nmT(r, c) = (*this)(r, c) * v;
    
    		return nmT;
    	}
    
    	Mat2D_T operator/(const val_t& v) const
    	{
    		Mat2D_T nmT(rows(), columns());
    
    		for (size_t r = 0; r < rows(); ++r)
    			for (size_t c = 0; c < columns(); ++c)
    				nmT(r, c) = (*this)(r, c) / v;
    
    		return nmT;
    	}
    
    	Mat2D_T operator+(const Mat2D_T& mT) const
    	{
    		if (rows() != mT.rows() && columns() != mT.columns())
    			throw std::exception("operator+()");
    
    		Mat2D_T nmT(rows(), columns());
    
    		for (size_t r = 0; r < rows(); ++r)
    			for (size_t c = 0; c < columns(); ++c)
    				nmT(r, c) = (*this)(r, c) + mT(r, c);
    
    		return nmT;
    	}
    
    	Mat2D_T operator-(const Mat2D_T& mT) const
    	{
    		if (rows() != mT.rows() && columns() != mT.columns())
    			throw std::exception("operator-()");
    
    		Mat2D_T nmT(rows(), columns());
    
    		for (size_t r = 0; r < rows(); ++r)
    			for (size_t c = 0; c < columns(); ++c)
    				nmT(r, c) = (*this)(r, c) - mT(r, c);
    
    		return nmT;
    	}
    

    was ich mich nun frage, eine template-Matrix sollte ja alle möglichen Typen aufnehmen können, also auch zB einen std::vector()oder wieder eine Matrix. Nur rechnen kann man damit nur bedingt. Sollte man allso die Rechenoperationen mit einem eigenen template-Typ auslagern? Oder führt das zu weit?

    EDIT: Und ist es sinnvoll, aus Gründen der Vollständigkeit auch alle undefinierten Fälle zu berücksichtigen wie zB

            Mat2D_T operator/(const Mat2D_T& mT) const
    	{
    		throw std::exception("operator/()");
    	}
    

    ?



  • PS: in den obigen frisch gezeigten Funktionen muss bei der Prüfung auf selbe Dimension ein orstatt ein andhin. Wie konnte das denn (bei mir) nur durchschlüpfen?



  • @zeropage sagte in Entwicklung einer einfachen Matrizenklasse:

    was ich mich nun frage, eine template-Matrix sollte ja alle möglichen Typen aufnehmen können, also auch zB einen std::vector()oder wieder eine Matrix. Nur rechnen kann man damit nur bedingt. Sollte man allso die Rechenoperationen mit einem eigenen template-Typ auslagern? Oder führt das zu weit?

    Eine Matrix ist nur sinnvoll für einen Vektorraum definiert. Der Skalerenkörper des Vektorraums muss entweder die Körper- oder die Schiefkörperaxiome erfüllen. Die Matrizen, die Du bisher betrachtet hast, haben als Skalarenkörper, den Körper der reellen Zahlen. Ein anderer Körper ist der Körper der komplexen Zahlen. Komplexe Matrizen haben noch einige zusätzliche Eigenschaften, die reelle Matrizen nicht haben können z.B. hermitesche Matrix.



  • Aha ok, danke. Schon etwas komplizierter.



  • Habe noch einige Funktionen hinzugefügt:

            Mat_T transpose() const
    	{
    		Mat_T mT(columns(), rows());
    
    		for (size_t r = 0; r < rows(); ++r)
    			for (size_t c = 0; c < columns(); ++c)
    				mT(c, r) = (*this)(r, c);
    
    		return mT;
    	}
    
    	Mat_T& negate() 
    	{
    		return (*this) * -1;
    	}
    	
    	Mat_T negate() const 
    	{ 
    		Mat_T<val_t> mT = (*this);
    		mT = mT * -1;
    		return mT;
    	}
    
    	Mat_T& invert()
    	{
    		if (!invertMatrix((*this)))
    			throw std::exception("invert(): not possible");
    
    		return (*this);
    	}
    
    	Mat_T invert() const
    	{
    		Mat_T<val_t> mT = (*this);
    
    		if (!invertMatrix(mT))
    			throw std::exception("invert(): not possible");
    
    		return mT;
    	}
    
    	template<typename cast_t>
    	Mat_T<cast_t> cast() const
    	{
    		Mat_T<cast_t> mT(rows(), columns());
    
    		for (size_t r = 0; r < rows(); ++r)
    			for (size_t c = 0; c < columns(); ++c)
    				mT(c, r) = static_cast<cast_t>((*this)(r, c));
    
    		return mT;
    	}
    
            bool swapRow(Mat_T<val_t>& mT, size_t rowA, size_t rowB)
    	{
    		if (rowA >= mT.rows() || rowB >= mT.rows())
    			return false;
    
    		for (size_t c = 0; c < mT.columns(); ++c)
    		{
    			const val_t v = mT[rowA][c];
    			mT[rowA][c] = mT[rowB][c];
    			mT[rowB][c] = v;
    		}
    
    		return true;
    	}
    
    	/*Invertiert eine NxN Mat mit Hilfe des Gauß-Jordan-Algorithmus.
    	return: false, falls die Mat nicht invertierbar ist.*/
    	bool invertMatrix(Mat_T<val_t>& imT)
    	{
    		if (imT.rows() != imT.columns())
    			return false;
    
    		size_t N = imT.rows();
    		// Eine Nx2N Mat für den Gauß-Jordan-Algorithmus aufbauen
    		Mat_T<val_t> A(N, 2 * N);
    		for (size_t r = 0; r < N; ++r)
    		{
    			for (size_t c = 0; c < N; ++c)
    				A[r][c] = imT[r][c];
    			for (size_t c = N; c < 2 * N; ++c)
    				A[r][c] = (r == c - N) ? 1.0 : 0.0;
    		}
    		// Gauß-Algorithmus
    		for (size_t k = 0; k < N - 1; ++k)
    		{
    			// Zeilen vertauschen, falls das Pivotelement eine Null ist
    			if (A[k][k] == 0.0)
    			{
    				for (size_t r = k + 1; r < N; ++r)
    				{
    					if (A[r][k] != 0.0)
    					{
    						swapRow(A, k, r);
    						break;
    					}
    					else if (r == N - 1)
    						return false; // Es gibt kein Element != 0
    				}
    			}
    			// Einträge unter dem Pivotelement eliminieren
    			for (size_t r = k + 1; r < N; ++r)
    			{
    				const val_t v = A[r][k] / A[k][k];
    				for (size_t c = k; c < 2 * N; ++c)
    					A[r][c] -= A[k][c] * v;
    			}
    		}
    
    		// Determinante der Mat berechnen
    		val_t det = 1.0;
    		for (size_t k = 0; k < N; ++k)
    			det *= A[k][k];
    
    		if (det == 0.0)  // Determinante ist =0 -> Mat nicht invertierbar
    			return false;
    
    		// Jordan-Teil des Algorithmus durchführen
    		for (size_t k = N - 1; k > 0; --k)
    		{
    			for (int r = k - 1; r >= 0; --r)
    			{
    				const val_t v = A[r][k] / A[k][k];
    				for (size_t c = k; c < 2 * N; ++c)
    					A[r][c] -= A[k][c] * v;
    			}
    		}
    		// Einträge in der linker Mat auf 1 normieren und in imT schreiben
    		for (size_t r = 0; r < N; ++r)
    		{
    			const val_t v = A[r][r];
    			for (size_t c = N; c < 2 * N; ++c)
    				imT[r][c - N] = A[r][c] / v;
    		}
    
    		return true;
    	}
    


  • Wieso funktioniert das jetzt nicht?

            Mat_T invert() const
    	{
    		//Mat_T<val_t> mT = (*this);
    		Mat_T<val_t> mT = copyMatrix(); //Ob nun so oder so
    
    		if (!invertMatrix(mT))
    			throw std::exception("invert(): not possible");
    
    		return mT;
    	}
    
            Mat_T copyMatrix() const
    	{
    		Mat_T<val_t> copyMat(rows(), columns());
    
    		for (size_t r = 0; r < rows(); ++r)
    			for (size_t c = 0; c < columns(); ++c)
    				copyMat(r, c) = component(r, c);
    
    		return copyMat;
    	}
    

    Fehlermeldung:

    Error	C2662	'bool Mat_T<float>::invertMatrix(Mat_T<float> &)': cannot convert 'this' pointer from 'const Mat_T<float>' to 'Mat_T<float> &'	
    

    Was will der converten? Er soll mT nehmen und gut?



  • Die Methode, in der wiederum diese Methode aufgerufen wird darf nicht constsein. Manche Fragen scheinen sich erst zu lösen, wenn man tatsächlich auch fragt.



  • Ist das eine freie Funktion, oder eine Member Funktion?

    bool invertMatrix(Mat_T<val_t>& imT)
    


  • Diese ist noch eine Memberfunktion.



  • Warum ist die nicht const? Du kannst aus const Member Funktionen nur andere Member Funktionen aufrufen, die auch const sind.


Anmelden zum Antworten