Skip to content
Permalink
Browse files
Introduce qgsNanCompatibleEquals for readability
  • Loading branch information
nyalldawson committed May 13, 2021
1 parent c0743d6 commit 75eba31996cd36dc2d6a8be9c5bddb88bcf5b7a8
@@ -182,6 +182,16 @@ Returns a string representation of a double
:param precision: number of decimal places to retain
%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 );
%Docstring
Compare two doubles (but allow some difference)
@@ -1678,9 +1678,7 @@ bool QgsCoordinateReferenceSystem::operator==( const QgsCoordinateReferenceSyste
if ( !d->mIsValid || !srs.d->mIsValid )
return false;

if ( std::isnan( d->mCoordinateEpoch ) != std::isnan( srs.d->mCoordinateEpoch ) )
return false;
else if ( !std::isnan( d->mCoordinateEpoch ) && d->mCoordinateEpoch != srs.d->mCoordinateEpoch )
if ( !qgsNanCompatibleEquals( d->mCoordinateEpoch, srs.d->mCoordinateEpoch ) )
return false;

const bool isUser = d->mSrsId >= USER_CRS_START_ID;
@@ -901,13 +901,8 @@ bool QgsCoordinateTransform::setFromCache( const QgsCoordinateReferenceSystem &s
{
if ( ( *valIt ).coordinateOperation() == coordinateOperationProj
&& ( *valIt ).allowFallbackTransforms() == allowFallback

// careful here, nan != nan, so we need to explicitly handle the case when both crses have nan 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() ) )
&& qgsNanCompatibleEquals( src.coordinateEpoch(), ( *valIt ).sourceCrs().coordinateEpoch() )
&& qgsNanCompatibleEquals( 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
@@ -173,8 +173,7 @@ bool QgsCoordinateTransformPrivate::initialize()
: ( mDestIsDynamic && !std::isnan( mDestCoordinateEpoch ) && !mSourceIsDynamic )
? mDestCoordinateEpoch : std::numeric_limits< double >::quiet_NaN();

if ( mSourceIsDynamic && mDestIsDynamic
&& !std::isnan( mSourceCoordinateEpoch ) && mSourceCoordinateEpoch != mDestCoordinateEpoch )
if ( mSourceIsDynamic && mDestIsDynamic && !qgsNanCompatibleEquals( mSourceCoordinateEpoch, mDestCoordinateEpoch ) )
{
// transforms from dynamic crs to dynamic crs with different coordinate epochs are not yet supported by PROJ
if ( sDynamicCrsToDynamicCrsWarningHandler )
@@ -341,6 +341,22 @@ inline QString qgsDoubleToString( double a, int precision = 17 )
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)
* \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() )
{
if ( std::isnan( a ) || std::isnan( b ) )
return std::isnan( a ) && std::isnan( b ) ;
const bool aIsNan = std::isnan( a );
const bool bIsNan = std::isnan( b );
if ( aIsNan || bIsNan )
return aIsNan && bIsNan;

const double diff = a - b;
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 )
{
if ( std::isnan( a ) || std::isnan( b ) )
return std::isnan( a ) && std::isnan( b ) ;
const bool aIsNan = std::isnan( a );
const bool bIsNan = std::isnan( b );
if ( aIsNan || bIsNan )
return aIsNan && bIsNan;

const float diff = a - b;
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
inline bool qgsDoubleNearSig( double a, double b, int significantDigits = 10 )
{
if ( std::isnan( a ) || std::isnan( b ) )
return std::isnan( a ) && std::isnan( b ) ;
const bool aIsNan = std::isnan( a );
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
// but that is probably too costly
@@ -47,6 +47,8 @@ class TestQgis : public QObject
void signalBlocker();
void qVariantCompare_data();
void qVariantCompare();
void testNanCompatibleEquals_data();
void testNanCompatibleEquals();
void testQgsAsConst();
void testQgsRound();
void testQgsVariantEqual();
@@ -320,6 +322,29 @@ void TestQgis::qVariantCompare()
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
{
public:

0 comments on commit 75eba31

Please sign in to comment.