Introduce qgsNanCompatibleEquals for readability

This commit is contained in:
Nyall Dawson 2021-05-13 09:03:57 +10:00
parent c0743d6bef
commit 75eba31996
6 changed files with 67 additions and 18 deletions

View File

@ -182,6 +182,16 @@ Returns a string representation of a double
:param precision: number of decimal places to retain :param precision: number of decimal places to retain
%End %End
bool qgsNanCompatibleEquals( double a, double b );
%Docstring
Compare two doubles, treating nan values as equal
:param a: first double
:param b: second double
.. versionadded:: 3.20
%End
bool qgsDoubleNear( double a, double b, double epsilon = 4 * DBL_EPSILON ); bool qgsDoubleNear( double a, double b, double epsilon = 4 * DBL_EPSILON );
%Docstring %Docstring
Compare two doubles (but allow some difference) Compare two doubles (but allow some difference)

View File

@ -1678,9 +1678,7 @@ bool QgsCoordinateReferenceSystem::operator==( const QgsCoordinateReferenceSyste
if ( !d->mIsValid || !srs.d->mIsValid ) if ( !d->mIsValid || !srs.d->mIsValid )
return false; return false;
if ( std::isnan( d->mCoordinateEpoch ) != std::isnan( srs.d->mCoordinateEpoch ) ) if ( !qgsNanCompatibleEquals( d->mCoordinateEpoch, srs.d->mCoordinateEpoch ) )
return false;
else if ( !std::isnan( d->mCoordinateEpoch ) && d->mCoordinateEpoch != srs.d->mCoordinateEpoch )
return false; return false;
const bool isUser = d->mSrsId >= USER_CRS_START_ID; const bool isUser = d->mSrsId >= USER_CRS_START_ID;

View File

@ -901,13 +901,8 @@ bool QgsCoordinateTransform::setFromCache( const QgsCoordinateReferenceSystem &s
{ {
if ( ( *valIt ).coordinateOperation() == coordinateOperationProj if ( ( *valIt ).coordinateOperation() == coordinateOperationProj
&& ( *valIt ).allowFallbackTransforms() == allowFallback && ( *valIt ).allowFallbackTransforms() == allowFallback
&& qgsNanCompatibleEquals( src.coordinateEpoch(), ( *valIt ).sourceCrs().coordinateEpoch() )
// careful here, nan != nan, so we need to explicitly handle the case when both crses have nan coordinateEpoch && qgsNanCompatibleEquals( dest.coordinateEpoch(), ( *valIt ).destinationCrs().coordinateEpoch() )
&& ( std::isnan( ( *valIt ).sourceCrs().coordinateEpoch() ) == std::isnan( src.coordinateEpoch() )
&& ( std::isnan( src.coordinateEpoch() ) || src.coordinateEpoch() == ( *valIt ).sourceCrs().coordinateEpoch() ) )
&& ( std::isnan( ( *valIt ).destinationCrs().coordinateEpoch() ) == std::isnan( dest.coordinateEpoch() )
&& ( std::isnan( dest.coordinateEpoch() ) || dest.coordinateEpoch() == ( *valIt ).destinationCrs().coordinateEpoch() ) )
) )
{ {
// need to save, and then restore the context... we don't want this to be cached or to use the values from the cache // need to save, and then restore the context... we don't want this to be cached or to use the values from the cache

View File

@ -173,8 +173,7 @@ bool QgsCoordinateTransformPrivate::initialize()
: ( mDestIsDynamic && !std::isnan( mDestCoordinateEpoch ) && !mSourceIsDynamic ) : ( mDestIsDynamic && !std::isnan( mDestCoordinateEpoch ) && !mSourceIsDynamic )
? mDestCoordinateEpoch : std::numeric_limits< double >::quiet_NaN(); ? mDestCoordinateEpoch : std::numeric_limits< double >::quiet_NaN();
if ( mSourceIsDynamic && mDestIsDynamic if ( mSourceIsDynamic && mDestIsDynamic && !qgsNanCompatibleEquals( mSourceCoordinateEpoch, mDestCoordinateEpoch ) )
&& !std::isnan( mSourceCoordinateEpoch ) && mSourceCoordinateEpoch != mDestCoordinateEpoch )
{ {
// transforms from dynamic crs to dynamic crs with different coordinate epochs are not yet supported by PROJ // transforms from dynamic crs to dynamic crs with different coordinate epochs are not yet supported by PROJ
if ( sDynamicCrsToDynamicCrsWarningHandler ) if ( sDynamicCrsToDynamicCrsWarningHandler )

View File

@ -341,6 +341,22 @@ inline QString qgsDoubleToString( double a, int precision = 17 )
return str; return str;
} }
/**
* Compare two doubles, treating nan values as equal
* \param a first double
* \param b second double
* \since QGIS 3.20
*/
inline bool qgsNanCompatibleEquals( double a, double b )
{
const bool aIsNan = std::isnan( a );
const bool bIsNan = std::isnan( b );
if ( aIsNan || bIsNan )
return aIsNan && bIsNan;
return a == b;
}
/** /**
* Compare two doubles (but allow some difference) * Compare two doubles (but allow some difference)
* \param a first double * \param a first double
@ -349,8 +365,10 @@ inline QString qgsDoubleToString( double a, int precision = 17 )
*/ */
inline bool qgsDoubleNear( double a, double b, double epsilon = 4 * std::numeric_limits<double>::epsilon() ) inline bool qgsDoubleNear( double a, double b, double epsilon = 4 * std::numeric_limits<double>::epsilon() )
{ {
if ( std::isnan( a ) || std::isnan( b ) ) const bool aIsNan = std::isnan( a );
return std::isnan( a ) && std::isnan( b ) ; const bool bIsNan = std::isnan( b );
if ( aIsNan || bIsNan )
return aIsNan && bIsNan;
const double diff = a - b; const double diff = a - b;
return diff > -epsilon && diff <= epsilon; return diff > -epsilon && diff <= epsilon;
@ -364,8 +382,10 @@ inline bool qgsDoubleNear( double a, double b, double epsilon = 4 * std::numeric
*/ */
inline bool qgsFloatNear( float a, float b, float epsilon = 4 * FLT_EPSILON ) inline bool qgsFloatNear( float a, float b, float epsilon = 4 * FLT_EPSILON )
{ {
if ( std::isnan( a ) || std::isnan( b ) ) const bool aIsNan = std::isnan( a );
return std::isnan( a ) && std::isnan( b ) ; const bool bIsNan = std::isnan( b );
if ( aIsNan || bIsNan )
return aIsNan && bIsNan;
const float diff = a - b; const float diff = a - b;
return diff > -epsilon && diff <= epsilon; return diff > -epsilon && diff <= epsilon;
@ -374,8 +394,10 @@ inline bool qgsFloatNear( float a, float b, float epsilon = 4 * FLT_EPSILON )
//! Compare two doubles using specified number of significant digits //! Compare two doubles using specified number of significant digits
inline bool qgsDoubleNearSig( double a, double b, int significantDigits = 10 ) inline bool qgsDoubleNearSig( double a, double b, int significantDigits = 10 )
{ {
if ( std::isnan( a ) || std::isnan( b ) ) const bool aIsNan = std::isnan( a );
return std::isnan( a ) && std::isnan( b ) ; const bool bIsNan = std::isnan( b );
if ( aIsNan || bIsNan )
return aIsNan && bIsNan;
// The most simple would be to print numbers as %.xe and compare as strings // The most simple would be to print numbers as %.xe and compare as strings
// but that is probably too costly // but that is probably too costly

View File

@ -47,6 +47,8 @@ class TestQgis : public QObject
void signalBlocker(); void signalBlocker();
void qVariantCompare_data(); void qVariantCompare_data();
void qVariantCompare(); void qVariantCompare();
void testNanCompatibleEquals_data();
void testNanCompatibleEquals();
void testQgsAsConst(); void testQgsAsConst();
void testQgsRound(); void testQgsRound();
void testQgsVariantEqual(); void testQgsVariantEqual();
@ -320,6 +322,29 @@ void TestQgis::qVariantCompare()
QCOMPARE( qgsVariantGreaterThan( lhs, rhs ), greaterThan ); QCOMPARE( qgsVariantGreaterThan( lhs, rhs ), greaterThan );
} }
void TestQgis::testNanCompatibleEquals_data()
{
QTest::addColumn<double>( "lhs" );
QTest::addColumn<double>( "rhs" );
QTest::addColumn<bool>( "expected" );
QTest::newRow( "both nan" ) << std::numeric_limits< double >::quiet_NaN() << std::numeric_limits< double >::quiet_NaN() << true;
QTest::newRow( "first is nan" ) << std::numeric_limits< double >::quiet_NaN() << 5.0 << false;
QTest::newRow( "second is nan" ) << 5.0 << std::numeric_limits< double >::quiet_NaN() << false;
QTest::newRow( "two numbers, not equal" ) << 5.0 << 6.0 << false;
QTest::newRow( "two numbers, equal" ) << 5.0 << 5.0 << true;
}
void TestQgis::testNanCompatibleEquals()
{
QFETCH( double, lhs );
QFETCH( double, rhs );
QFETCH( bool, expected );
QCOMPARE( qgsNanCompatibleEquals( lhs, rhs ), expected );
QCOMPARE( qgsNanCompatibleEquals( rhs, lhs ), expected );
}
class ConstTester class ConstTester
{ {
public: public: