Say hello to qFloatDistance()

This function is useful if a floating point comparison requires a
certain precision. The return value can be considered as the precision.
For instance, if you want to compare two 32-bit floating point values
and all you need is a 24-bit precision, you can use this function like
this:
if (qFloatDistance(a,b) < (1 << 7)) {   // The last 7 bits are not
                                        // significant
    // precise enough
}

Task-number: QTBUG-32632

Change-Id: I020a58d2f9f9452ac3c510b4bb560dc806f0d93c
Reviewed-by: Shawn Rutledge <shawn.rutledge@digia.com>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
bb10
Jan Arve Saether 2013-07-31 15:43:09 +02:00 committed by The Qt Project
parent 88999e9ea0
commit e37001aad7
3 changed files with 232 additions and 0 deletions

View File

@ -41,6 +41,7 @@
#include "qnumeric.h"
#include "qnumeric_p.h"
#include <string.h>
QT_BEGIN_NAMESPACE
@ -99,4 +100,139 @@ Q_CORE_EXPORT double qQNaN() { return qt_qnan(); }
Q_CORE_EXPORT double qInf() { return qt_inf(); }
/*!
\internal
*/
static inline quint32 f2i(float f)
{
quint32 i;
memcpy(&i, &f, sizeof(f));
return i;
}
/*!
Returns the number of representable floating-point numbers between \a a and \a b.
This function provides an alternative way of doing approximated comparisons of floating-point
numbers similar to qFuzzyCompare(). However, it returns the distance between two numbers, which
gives the caller a possibility to choose the accepted error. Errors are relative, so for
instance the distance between 1.0E-5 and 1.00001E-5 will give 110, while the distance between
1.0E36 and 1.00001E36 will give 127.
This function is useful if a floating point comparison requires a certain precision.
Therefore, if \a a and \a b are equal it will return 0. The maximum value it will return for 32-bit
floating point numbers is 4,278,190,078. This is the distance between \c{-FLT_MAX} and
\c{+FLT_MAX}.
The function does not give meaningful results if any of the arguments are \c Infinite or \c NaN.
You can check for this by calling qIsFinite().
The return value can be considered as the "error", so if you for instance want to compare
two 32-bit floating point numbers and all you need is an approximated 24-bit precision, you can
use this function like this:
\code
if (qFloatDistance(a, b) < (1 << 7)) { // The last 7 bits are not
// significant
// precise enough
}
\endcode
\sa qFuzzyCompare()
\relates <QtGlobal>
*/
Q_CORE_EXPORT quint32 qFloatDistance(float a, float b)
{
static const quint32 smallestPositiveFloatAsBits = 0x00000001; // denormalized, (SMALLEST), (1.4E-45)
/* Assumes:
* IEE754 format.
* Integers and floats have the same endian
*/
Q_STATIC_ASSERT(sizeof(quint32) == sizeof(float));
Q_ASSERT(qIsFinite(a) && qIsFinite(b));
if (a == b)
return 0;
if ((a < 0) != (b < 0)) {
// if they have different signs
if (a < 0)
a = -a;
else /*if (b < 0)*/
b = -b;
return qFloatDistance(0.0F, a) + qFloatDistance(0.0F, b);
}
if (a < 0) {
a = -a;
b = -b;
}
// at this point a and b should not be negative
// 0 is special
if (!a)
return f2i(b) - smallestPositiveFloatAsBits + 1;
if (!b)
return f2i(a) - smallestPositiveFloatAsBits + 1;
// finally do the common integer subtraction
return a > b ? f2i(a) - f2i(b) : f2i(b) - f2i(a);
}
/*!
\internal
*/
static inline quint64 d2i(double d)
{
quint64 i;
memcpy(&i, &d, sizeof(d));
return i;
}
/*!
Returns the number of representable floating-point numbers between \a a and \a b.
This function serves the same purpose as \c{qFloatDistance(float, float)}, but
returns the distance between two \c double numbers. Since the range is larger
than for two \c float numbers (\c{[-DBL_MAX,DBL_MAX]}), the return type is quint64.
\sa qFuzzyCompare()
\relates <QtGlobal>
*/
Q_CORE_EXPORT quint64 qFloatDistance(double a, double b)
{
static const quint64 smallestPositiveFloatAsBits = 0x1; // denormalized, (SMALLEST)
/* Assumes:
* IEE754 format double precision
* Integers and floats have the same endian
*/
Q_STATIC_ASSERT(sizeof(quint64) == sizeof(double));
Q_ASSERT(qIsFinite(a) && qIsFinite(b));
if (a == b)
return 0;
if ((a < 0) != (b < 0)) {
// if they have different signs
if (a < 0)
a = -a;
else /*if (b < 0)*/
b = -b;
return qFloatDistance(0.0, a) + qFloatDistance(0.0, b);
}
if (a < 0) {
a = -a;
b = -b;
}
// at this point a and b should not be negative
// 0 is special
if (!a)
return d2i(b) - smallestPositiveFloatAsBits + 1;
if (!b)
return d2i(a) - smallestPositiveFloatAsBits + 1;
// finally do the common integer subtraction
return a > b ? d2i(a) - d2i(b) : d2i(b) - d2i(a);
}
QT_END_NAMESPACE

View File

@ -57,6 +57,9 @@ Q_CORE_EXPORT double qSNaN();
Q_CORE_EXPORT double qQNaN();
Q_CORE_EXPORT double qInf();
Q_CORE_EXPORT quint32 qFloatDistance(float a, float b);
Q_CORE_EXPORT quint64 qFloatDistance(double a, double b);
#define Q_INFINITY (QT_PREPEND_NAMESPACE(qInf)())
#define Q_SNAN (QT_PREPEND_NAMESPACE(qSNaN)())
#define Q_QNAN (QT_PREPEND_NAMESPACE(qQNaN)())

View File

@ -44,6 +44,7 @@
#include <QtGlobal>
#include <math.h>
#include <float.h>
class tst_QNumeric: public QObject
{
@ -53,6 +54,10 @@ private slots:
void fuzzyCompare_data();
void fuzzyCompare();
void qNan();
void floatDistance_data();
void floatDistance();
void floatDistance_double_data();
void floatDistance_double();
};
void tst_QNumeric::fuzzyCompare_data()
@ -121,5 +126,93 @@ void tst_QNumeric::qNan()
QVERIFY(qFuzzyCompare(1/inf, 0.0));
}
void tst_QNumeric::floatDistance_data()
{
QTest::addColumn<float>("val1");
QTest::addColumn<float>("val2");
QTest::addColumn<quint32>("expectedDistance");
// exponent: 8 bits
// mantissa: 23 bits
const quint32 number_of_denormals = (1 << 23) - 1; // Set to 0 if denormals are not included
quint32 _0_to_1 = quint32((1 << 23) * 126 + 1 + number_of_denormals); // We need +1 to include the 0
quint32 _1_to_2 = quint32(1 << 23);
// We don't need +1 because FLT_MAX has all bits set in the mantissa. (Thus mantissa
// have not wrapped back to 0, which would be the case for 1 in _0_to_1
quint32 _0_to_FLT_MAX = quint32((1 << 23) * 254) + number_of_denormals;
quint32 _0_to_FLT_MIN = 1 + number_of_denormals;
QTest::newRow("[0,FLT_MIN]") << 0.F << FLT_MIN << _0_to_FLT_MIN;
QTest::newRow("[0,FLT_MAX]") << 0.F << FLT_MAX << _0_to_FLT_MAX;
QTest::newRow("[1,1.5]") << 1.0F << 1.5F << quint32(1 << 22);
QTest::newRow("[0,1]") << 0.F << 1.0F << _0_to_1;
QTest::newRow("[0.5,1]") << 0.5F << 1.0F << quint32(1 << 23);
QTest::newRow("[1,2]") << 1.F << 2.0F << _1_to_2;
QTest::newRow("[-1,+1]") << -1.F << +1.0F << 2 * _0_to_1;
QTest::newRow("[-1,0]") << -1.F << 0.0F << _0_to_1;
QTest::newRow("[-1,FLT_MAX]") << -1.F << FLT_MAX << _0_to_1 + _0_to_FLT_MAX;
QTest::newRow("[-2,-1") << -2.F << -1.F << _1_to_2;
QTest::newRow("[-1,-2") << -1.F << -2.F << _1_to_2;
QTest::newRow("[FLT_MIN,FLT_MAX]") << FLT_MIN << FLT_MAX << _0_to_FLT_MAX - _0_to_FLT_MIN;
QTest::newRow("[-FLT_MAX,FLT_MAX]") << -FLT_MAX << FLT_MAX << (2*_0_to_FLT_MAX);
float denormal = FLT_MIN;
denormal/=2.0F;
QTest::newRow("denormal") << 0.F << denormal << _0_to_FLT_MIN/2;
}
void tst_QNumeric::floatDistance()
{
QFETCH(float, val1);
QFETCH(float, val2);
QFETCH(quint32, expectedDistance);
QCOMPARE(qFloatDistance(val1, val2), expectedDistance);
}
void tst_QNumeric::floatDistance_double_data()
{
QTest::addColumn<double>("val1");
QTest::addColumn<double>("val2");
QTest::addColumn<quint64>("expectedDistance");
// exponent: 11 bits
// mantissa: 52 bits
const quint64 number_of_denormals = (Q_UINT64_C(1) << 52) - 1; // Set to 0 if denormals are not included
quint64 _0_to_1 = (Q_UINT64_C(1) << 52) * ((1 << (11-1)) - 2) + 1 + number_of_denormals; // We need +1 to include the 0
quint64 _1_to_2 = Q_UINT64_C(1) << 52;
// We don't need +1 because DBL_MAX has all bits set in the mantissa. (Thus mantissa
// have not wrapped back to 0, which would be the case for 1 in _0_to_1
quint64 _0_to_DBL_MAX = quint64((Q_UINT64_C(1) << 52) * ((1 << 11) - 2)) + number_of_denormals;
quint64 _0_to_DBL_MIN = 1 + number_of_denormals;
QTest::newRow("[0,DBL_MIN]") << 0.0 << DBL_MIN << _0_to_DBL_MIN;
QTest::newRow("[0,DBL_MAX]") << 0.0 << DBL_MAX << _0_to_DBL_MAX;
QTest::newRow("[1,1.5]") << 1.0 << 1.5 << (Q_UINT64_C(1) << 51);
QTest::newRow("[0,1]") << 0.0 << 1.0 << _0_to_1;
QTest::newRow("[0.5,1]") << 0.5 << 1.0 << (Q_UINT64_C(1) << 52);
QTest::newRow("[1,2]") << 1.0 << 2.0 << _1_to_2;
QTest::newRow("[-1,+1]") << -1.0 << +1.0 << 2 * _0_to_1;
QTest::newRow("[-1,0]") << -1.0 << 0.0 << _0_to_1;
QTest::newRow("[-1,DBL_MAX]") << -1.0 << DBL_MAX << _0_to_1 + _0_to_DBL_MAX;
QTest::newRow("[-2,-1") << -2.0 << -1.0 << _1_to_2;
QTest::newRow("[-1,-2") << -1.0 << -2.0 << _1_to_2;
QTest::newRow("[DBL_MIN,DBL_MAX]") << DBL_MIN << DBL_MAX << _0_to_DBL_MAX - _0_to_DBL_MIN;
QTest::newRow("[-DBL_MAX,DBL_MAX]") << -DBL_MAX << DBL_MAX << (2*_0_to_DBL_MAX);
double denormal = DBL_MIN;
denormal/=2.0;
QTest::newRow("denormal") << 0.0 << denormal << _0_to_DBL_MIN/2;
}
void tst_QNumeric::floatDistance_double()
{
QFETCH(double, val1);
QFETCH(double, val2);
QFETCH(quint64, expectedDistance);
QCOMPARE(qFloatDistance(val1, val2), expectedDistance);
}
QTEST_APPLESS_MAIN(tst_QNumeric)
#include "tst_qnumeric.moc"