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
parent
88999e9ea0
commit
e37001aad7
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)())
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue