Further details on classes like epsilon compare, further unit tests.

This commit is contained in:
Klaus Basan
2013-04-24 01:59:17 +02:00
parent bd53371de4
commit 472521f281
17 changed files with 187 additions and 70 deletions

View File

@@ -22,12 +22,12 @@ int CSamplesPhysicalQuantities::samples()
CMeasurementPrefix pf1 = CMeasurementPrefix::h(); CMeasurementPrefix pf1 = CMeasurementPrefix::h();
CMeasurementPrefix pf2 = CMeasurementPrefix::M(); CMeasurementPrefix pf2 = CMeasurementPrefix::M();
qDebug() << pf1 << pf2 << 1.0 * pf1; qDebug() << pf1 << pf2 << (1.0 * pf1.toDouble());
CLengthUnit lu1(CLengthUnit::cm()); CLengthUnit lu1(CLengthUnit::cm());
CLengthUnit lu2(CLengthUnit::ft()); CLengthUnit lu2(CLengthUnit::ft());
QString lu1s = lu1; QString lu1s = lu1.toQString();
QString lu2s = lu2; QString lu2s = lu2.toQString();
qDebug() << lu1 << lu2 << lu1s << lu2s; qDebug() << lu1 << lu2 << lu1s << lu2s;
const CLength l1(5.0, CLengthUnit::ft()); // 5 ft const CLength l1(5.0, CLengthUnit::ft()); // 5 ft
CLength l2(1, CLengthUnit::NM()); // 1NM CLength l2(1, CLengthUnit::NM()); // 1NM

View File

@@ -75,8 +75,9 @@ public:
/*! /*!
* \brief Cast as QString * \brief Cast as QString
* \remarks operator cast caused too many ambiguity trouble
*/ */
operator QString() const QString toQString() const
{ {
return this->stringForConverter(); return this->stringForConverter();
} }

View File

@@ -66,7 +66,7 @@ CCoordinateEcef CCoordinateTransformation::toEcef(const CCoordinateNed &ned)
*/ */
CCoordinateEcef CCoordinateTransformation::toEcef(const CCoordinateGeodetic &geo) CCoordinateEcef CCoordinateTransformation::toEcef(const CCoordinateGeodetic &geo)
{ {
// TODO: Clarify the comparions with fixed angles (==90, ==180) -> what happens here // TODO: Clarify the comparisons with fixed angles (==90, ==180) -> what happens here
CLatitude lat = geo.latitude(); CLatitude lat = geo.latitude();
CLongitude lon = geo.longitude(); CLongitude lon = geo.longitude();

View File

@@ -34,5 +34,33 @@ qreal CMath::cubicRootReal(qreal x)
return x < 0 ? -result : result; return x < 0 ? -result : result;
} }
/*
* Round utility method
*/
double CMath::round(double value, int digits)
{
// gosh, is there no Qt method for this??? It's year 2013
double fractpart, intpart;
fractpart = modf(value, &intpart);
if (fractpart == 0) return value; // do not mess any "integers" to the worse
double m = pow(10.0, digits);
qint64 ri = qRound(value * m); // do not loose any range here
double rv = double(ri) / m;
return rv;
}
/*
* Round by given epsilon value
*/
double CMath::roundEpsilon(double value, double epsilon)
{
double fractpart, intpart;
fractpart = modf(value, &intpart);
if (fractpart == 0) return value; // do not mess any "integers" to the worse
qint64 ri = qRound(value / epsilon);
double rv = double(ri) * epsilon; // do not loose any range here
return rv;
}
} // namespace } // namespace
} // namespace } // namespace

View File

@@ -15,10 +15,10 @@ namespace Math
// Some namespace wide constant values // Some namespace wide constant values
//! Mathematical constant Pi //! Mathematical constant Pi
const qreal PI = 4.0 * qAtan(1.0); const double PI = 4.0 * qAtan(1.0);
//! 2 * Pi //! 2 * Pi
const qreal TwoPI = 2.0 * PI; const double TwoPI = 2.0 * PI;
/*! /*!
* \brief Math utils * \brief Math utils
@@ -33,14 +33,14 @@ public:
* \param y * \param y
* \return * \return
*/ */
static qreal hypot(qreal x, qreal y); static double hypot(double x, double y);
/*! /*!
* \brief Calculates the square of x * \brief Calculates the square of x
* \param x * \param x
* \return * \return
*/ */
static inline qreal square(qreal x) static inline double square(double x)
{ {
return x * x; return x * x;
} }
@@ -50,7 +50,7 @@ public:
* \param x * \param x
* \return * \return
*/ */
static inline qreal cubic(const qreal x) static inline double cubic(const double x)
{ {
return x * x * x; return x * x * x;
} }
@@ -60,7 +60,23 @@ public:
* \param x * \param x
* \return * \return
*/ */
static qreal cubicRootReal(qreal x); static double cubicRootReal(double x);
/*!
* \brief Utility round method
* \param value
* \param digits
* \return
*/
static double round(double value, int digits);
/*!
* \brief Round by given epsilon, e.g.
* \param value
* \param epsilon
* \return
*/
static double roundEpsilon(double value, double epsilon);
private: private:
/*! /*!

View File

@@ -69,11 +69,11 @@ template<class ImplMatrix, int Rows, int Columns> bool CMatrixBase<ImplMatrix, R
} }
/* /*
* All values zero? * All values equal?
*/ */
template<class ImplMatrix, int Rows, int Columns> bool CMatrixBase<ImplMatrix, Rows, Columns>::allValuesEqual() const template<class ImplMatrix, int Rows, int Columns> bool CMatrixBase<ImplMatrix, Rows, Columns>::allValuesEqual() const
{ {
double v = this->getElement(0,0); double v = this->getElement(0, 0);
for (int r = 0; r < Rows; r++) for (int r = 0; r < Rows; r++)
{ {
for (int c = 0; c < Columns; c++) for (int c = 0; c < Columns; c++)
@@ -84,6 +84,19 @@ template<class ImplMatrix, int Rows, int Columns> bool CMatrixBase<ImplMatrix, R
return true; return true;
} }
/*
* Round all values
*/
template<class ImplMatrix, int Rows, int Columns> void CMatrixBase<ImplMatrix, Rows, Columns>::round()
{
for (int r = 0; r < Rows; r++)
{
for (int c = 0; c < Columns; c++)
{
this->m_matrix(r, c) = CMath::roundEpsilon(this->m_matrix(r, c), 1E-10);
}
}
}
/* /*
* Convert to string * Convert to string

View File

@@ -203,6 +203,18 @@ public:
return this->m_matrix.isIdentity(); return this->m_matrix.isIdentity();
} }
/*!
* \brief Is identity matrix? Epsilon considered.
* \return
*/
bool isIdentityEpsilon() const
{
ImplMatrix m(0.0);
m += (*this);
m.round();
return m.isIdentity();
}
/*! /*!
* \brief Set as identity matrix * \brief Set as identity matrix
* \return * \return
@@ -228,6 +240,18 @@ public:
*/ */
bool isZero() const; bool isZero() const;
/*!
* \brief Is identity matrix? Epsilon considered.
* \return
*/
bool isZeroEpsilon() const
{
ImplMatrix m(0.0);
m += (*this);
m.round();
return m.isZero();
}
/*! /*!
* \brief All values equal, if so matirx is not invertible * \brief All values equal, if so matirx is not invertible
* \return * \return
@@ -240,6 +264,11 @@ public:
*/ */
void fill(double value) { this->m_matrix.fill(value); } void fill(double value) { this->m_matrix.fill(value); }
/*!
* \brief Round all values
*/
void round();
/*! /*!
* \brief Get element * \brief Get element
* \param row * \param row

View File

@@ -92,13 +92,13 @@ template <class ImplClass> void CVector3DBase<ImplClass>::setElement(size_t row,
} }
/* /*
* Corss product * Cross product
*/ */
template <class ImplClass> ImplClass CVector3DBase<ImplClass>::crossProduct(const ImplClass &otherVector) const template <class ImplClass> ImplClass CVector3DBase<ImplClass>::crossProduct(const ImplClass &otherVector) const
{ {
ImplClass v(otherVector); ImplClass v(otherVector);
v.m_i = this->m_j * otherVector.m_k - this->m_k * otherVector.m_j; v.m_i = this->m_j * otherVector.m_k - this->m_k * otherVector.m_j;
v.m_j = this->m_k * otherVector.m_i - this->m_j * otherVector.m_k; v.m_j = this->m_k * otherVector.m_i - this->m_i * otherVector.m_k;
v.m_k = this->m_i * otherVector.m_j - this->m_j * otherVector.m_i; v.m_k = this->m_i * otherVector.m_j - this->m_j * otherVector.m_i;
return v; return v;
} }

View File

@@ -7,6 +7,7 @@
#define BLACKMISC_MATHVECTOR3DBASE_H #define BLACKMISC_MATHVECTOR3DBASE_H
#include "blackmisc/basestreamstringifier.h" #include "blackmisc/basestreamstringifier.h"
#include "blackmisc/mathematics.h"
namespace BlackMisc namespace BlackMisc
{ {
@@ -75,6 +76,26 @@ public:
*/ */
void setZero(); void setZero();
/*!
* \brief Set zeros
*/
bool isZero() const
{
return this->m_i == 0 && this->m_j == 0 && this->m_k == 0;
}
/*!
* \brief Is identity matrix? Epsilon considered.
* \return
*/
bool isZeroEpsilon() const
{
ImplClass v;
v += (*this);
v.round();
return v.isZero();
}
/*! /*!
* \brief Set zeros * \brief Set zeros
*/ */
@@ -370,6 +391,18 @@ public:
{ {
return sqrt(this->lengthSquared()); return sqrt(this->lengthSquared());
} }
/*!
* \brief Round this vector
*/
void round()
{
const double epsilon = 1E-10;
this->m_i = BlackMisc::Math::CMath::roundEpsilon(this->m_i, epsilon);
this->m_j = BlackMisc::Math::CMath::roundEpsilon(this->m_j, epsilon);
this->m_k = BlackMisc::Math::CMath::roundEpsilon(this->m_k, epsilon);
}
}; };
} // namespace } // namespace

View File

@@ -6,6 +6,7 @@
#ifndef BLACKMISC_PQANGLE_H #ifndef BLACKMISC_PQANGLE_H
#define BLACKMISC_PQANGLE_H #define BLACKMISC_PQANGLE_H
#include "blackmisc/pqphysicalquantity.h" #include "blackmisc/pqphysicalquantity.h"
#include "blackmisc/mathematics.h"
namespace BlackMisc namespace BlackMisc
{ {
@@ -46,15 +47,17 @@ public:
* \brief Convenience method PI * \brief Convenience method PI
* \return * \return
*/ */
const static double pi() { const static double pi()
{
return M_PI; return M_PI;
} }
/*! /*!
* \brief Value as factor of PI (e.g. 0.5PI) * \brief Value as factor of PI (e.g. 0.5PI)
* \return * \return
*/ */
double piFactor() const { double piFactor() const
return CMeasurementUnit::round(this->convertedSiValueToDouble() / M_PI, 6); {
return BlackMisc::Math::CMath::round(this->convertedSiValueToDouble() / M_PI, 6);
} }
}; };

View File

@@ -1,9 +1,12 @@
/* Copyright (C) 2013 VATSIM Community /* Copyright (C) 2013 VATSIM Community / contributors
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "blackmisc/pqbase.h" #include "blackmisc/pqbase.h"
#include "blackmisc/mathematics.h"
using namespace BlackMisc::Math;
namespace BlackMisc namespace BlackMisc
{ {
@@ -35,7 +38,7 @@ CMeasurementPrefix::CMeasurementPrefix(const CMeasurementPrefix &otherMultiplier
/* /*
* Assignment operator * Assignment operator
*/ */
CMeasurementPrefix& CMeasurementPrefix::operator=(const CMeasurementPrefix &otherMultiplier) CMeasurementPrefix &CMeasurementPrefix::operator=(const CMeasurementPrefix &otherMultiplier)
{ {
if (this == &otherMultiplier) return *this; // Same object? Yes, so skip assignment, and just return *this if (this == &otherMultiplier) return *this; // Same object? Yes, so skip assignment, and just return *this
@@ -170,7 +173,7 @@ QString CMeasurementUnit::valueRoundedWithUnit(double value, int digits) const
double CMeasurementUnit::valueRounded(double value, int digits) const double CMeasurementUnit::valueRounded(double value, int digits) const
{ {
if (digits < 0) digits = this->m_displayDigits; if (digits < 0) digits = this->m_displayDigits;
return CMeasurementUnit::round(value, digits); return CMath::round(value, digits);
} }
/* /*
@@ -179,30 +182,19 @@ double CMeasurementUnit::valueRounded(double value, int digits) const
QString CMeasurementUnit::toQStringRounded(double value, int digits) const QString CMeasurementUnit::toQStringRounded(double value, int digits) const
{ {
if (digits < 0) digits = this->m_displayDigits; if (digits < 0) digits = this->m_displayDigits;
double v = CMeasurementUnit::round(value, digits); double v = CMath::round(value, digits);
QString s = QLocale::system().toString(v, 'f', digits); QString s = QLocale::system().toString(v, 'f', digits);
return s; return s;
} }
/*
* Round utility method
*/
double CMeasurementUnit::round(double value, int digits)
{
// gosh, is there no Qt method for this???
// It's year 2013
double m = pow(10.0, digits);
double rv = double(qRound(value * m) / m);
return rv;
}
/* /*
* Epsilon rounding * Epsilon rounding
*/ */
double CMeasurementUnit::epsilonRounding(double value) const double CMeasurementUnit::epsilonUpRounding(double value) const
{ {
// does notwork reliable with qRound for some reason // Rounds a little up in order to avoid fractions
double v = floor((value + this->m_epsilon) / this->m_epsilon); double eps = value > 0 ? this->m_epsilon : -1.0 * this->m_epsilon;
double v = floor((value + eps) / this->m_epsilon);
v *= this->m_epsilon; v *= this->m_epsilon;
return v; return v;
} }

View File

@@ -93,10 +93,6 @@ public:
/*! /*!
* \brief Cast as double * \brief Cast as double
*/ */
operator double() const
{
return this->m_factor;
}
/*! /*!
* \brief Factor, e.g.1000 for "kilo" * \brief Factor, e.g.1000 for "kilo"
@@ -106,6 +102,16 @@ public:
{ {
return this->m_factor; return this->m_factor;
} }
/*!
* \brief Factor
* \return
*/
double toDouble() const
{
return this->getFactor();
}
/*! /*!
* \brief Name, e.g. "kilo" * \brief Name, e.g. "kilo"
* \return * \return
@@ -114,6 +120,7 @@ public:
{ {
return this->m_name; return this->m_name;
} }
/*! /*!
* \brief Prefix, e.g. "k" for "kilo" * \brief Prefix, e.g. "k" for "kilo"
* \return * \return
@@ -464,25 +471,17 @@ public:
*/ */
double conversionToUnit(double value, const CMeasurementUnit &to) const; double conversionToUnit(double value, const CMeasurementUnit &to) const;
// --------------------------------------------------------------------
// -- static
// --------------------------------------------------------------------
/*!
* \brief Utility round method
* \param value
* \param digits
* \return
*/
static double round(double value, int digits);
/*! /*!
* Epsilon rounding. In some conversion rouding is required to avoid * Epsilon rounding. In some conversion rouding is required to avoid
* periodical numbers. * periodical numbers.
* \param value * \param value
* \return * \return
*/ */
double epsilonRounding(double value) const; double epsilonUpRounding(double value) const;
// --------------------------------------------------------------------
// -- static
// --------------------------------------------------------------------
/*! /*!
* \brief Unit is not specified * \brief Unit is not specified

View File

@@ -6,6 +6,7 @@
#ifndef BLACKMISC_PQPHYSICALQUANTITY_H #ifndef BLACKMISC_PQPHYSICALQUANTITY_H
#define BLACKMISC_PQPHYSICALQUANTITY_H #define BLACKMISC_PQPHYSICALQUANTITY_H
#include "blackmisc/mathematics.h"
#include "blackmisc/pqbase.h" #include "blackmisc/pqbase.h"
#include "blackmisc/pqunits.h" #include "blackmisc/pqunits.h"
#include "blackmisc/debug.h" #include "blackmisc/debug.h"
@@ -217,7 +218,8 @@ public:
*/ */
qint32 convertedSiValueToInteger() const qint32 convertedSiValueToInteger() const
{ {
return static_cast<qint32>(CMeasurementUnit::round(this->m_convertedSiUnitValueD, 0)); return static_cast<qint32>(
BlackMisc::Math::CMath::round(this->m_convertedSiUnitValueD, 0));
} }
/*! /*!
@@ -255,15 +257,7 @@ public:
*/ */
void substractUnitValue(double value); void substractUnitValue(double value);
/*! /*!
* \brief Cast as QString
*/
operator QString() const
{
return this->unitValueRoundedWithUnit();
}
/*!
* \brief Multiply operator *= * \brief Multiply operator *=
* \param multiply * \param multiply
* \return * \return

View File

@@ -37,7 +37,7 @@ double CAngleUnit::conversionSexagesimalFromSi(const CMeasurementUnit &angleUnit
{ {
// using rounding here, since fractions can lead to ugly sexagesimal conversion // using rounding here, since fractions can lead to ugly sexagesimal conversion
// e.g. 185.499999 gives 185 29' 59.9999" // e.g. 185.499999 gives 185 29' 59.9999"
value = angleUnit.epsilonRounding(value * 180 / M_PI); // degree value = angleUnit.epsilonUpRounding(value * 180 / M_PI); // degree
double v = floor(value); double v = floor(value);
double c = value - v; double c = value - v;
double mr = c * 60.0; double mr = c * 60.0;

View File

@@ -1,4 +1,4 @@
/* Copyright (C) 2013 VATSIM Community / authors /* Copyright (C) 2013 VATSIM Community / contributors
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -649,4 +649,4 @@ public:
} // namespace } // namespace
} // namespace } // namespace
#endif // BLACKMISC_PQUNITS_H #endif // guard

View File

@@ -90,7 +90,7 @@ void CTestPhysicalQuantitiesBase::frequencyTests()
QVERIFY2(f1.valueRounded(CFrequencyUnit::kHz(), 2) == 1000, "Mega is 1000kHz"); QVERIFY2(f1.valueRounded(CFrequencyUnit::kHz(), 2) == 1000, "Mega is 1000kHz");
QVERIFY2(f1.unitValueToDouble() == 1 , "1MHz"); QVERIFY2(f1.unitValueToDouble() == 1 , "1MHz");
QVERIFY2(f1.convertedSiValueToDouble() == 1000000 , "1E6 Hz"); QVERIFY2(f1.convertedSiValueToDouble() == 1000000 , "1E6 Hz");
CFrequency f2(CMeasurementPrefix::M(), CFrequencyUnit::Hz()) ; // 1 Megahertz CFrequency f2(CMeasurementPrefix::M().toDouble(), CFrequencyUnit::Hz()) ; // 1 Megahertz
QVERIFY2(f1 == f2 , "MHz is 1E6 Hz"); QVERIFY2(f1 == f2 , "MHz is 1E6 Hz");
} }

View File

@@ -18,7 +18,15 @@ void CTestVectorMatrixBase::vectorBasics()
CVector3D v1(1); CVector3D v1(1);
v1 *= 2.0; v1 *= 2.0;
CVector3D v2(2); CVector3D v2(2);
QVERIFY2(v1 == v2, "Matrix should be zero"); QVERIFY2(v1 == v2, "Vectors should be equal");
CVector3D v3(1, 1, 1);
CVector3D v4(2, 2, 2);
CVector3D v5 = v3.crossProduct(v4);
QVERIFY2(v5.isZeroEpsilon(), "Cross product shall be {0, 0, 0}");
CVector3D v6(1, 2, 3);
CVector3D v7(3, 4, 5);
QVERIFY2(v6.crossProduct(v7) == CVector3D(-2, 4, -2), "Cross product is wrong");
QVERIFY2(v6.dotProduct(v7) == 26, "Dot product is wrong, 26 expected");
} }
/* /*
@@ -58,7 +66,8 @@ void CTestVectorMatrixBase::matrixBasics()
QVERIFY2(m1(0, 0) == 1 && m1(1, 0) == 2 && m1(0, 2) == 3, "Index error"); QVERIFY2(m1(0, 0) == 1 && m1(1, 0) == 2 && m1(0, 2) == 3, "Index error");
CMatrix3x3 mi = m1.inverse(invertible); CMatrix3x3 mi = m1.inverse(invertible);
CMatrix3x3 mid = m1 * mi; CMatrix3x3 mid = m1 * mi;
QVERIFY2(mid.isIdentity(), qPrintable(QString("Multiply with inverse should be identity: %1").arg(mid))); mid.round();
QVERIFY2(mid.isIdentity(), qPrintable(QString("Multiply with inverse should be identity: %1").arg(mid.toQString())));
} }
} // namespace } // namespace