Fix toFloat()s between float and double ranges and document

Revised some toFloat()s to be consistent with the matching
toDouble()s; previously, they would return infinity if toDouble() did
but return 0 if toDouble() got a finite value outside float's range.
That also applied to values that underflowed float's range, succeeding
and returning 0 as long as they were within double's range but failing
if toDouble() underflowed.  Now float-underflow also fails.  Amended
their documentation to reflect this more consistent reality.
Added some tests of out-of-range values, infinities and NaNs.

[ChangeLog][QtCore][toFloat] QString, QByteArray and QLocale returned
an infinity on double-overflow (since 5.7) but returned 0 on a finite
double outside float's range, while setting ok to false; this was at
odds with their documented behavior of returning 0 on any failure.
They also succeeded, returning zero, on underflow of float's range,
unless double underflowed, where they failed.  Changed the handling of
values outside float's range to match that of values outside double's
range: fail, returning an infinity on overflow or zero on underflow.
The documentation now reflects the revised behavior, which matches
toDouble().

Change-Id: Ia168bcacf7def0df924840d45d8edc5f850449d6
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
Edward Welbourne 2018-11-19 19:53:38 +01:00
parent a992367403
commit ce159d1a3e
5 changed files with 60 additions and 20 deletions

View File

@ -3899,7 +3899,8 @@ double QByteArray::toDouble(bool *ok) const
/*!
Returns the byte array converted to a \c float value.
Returns 0.0 if the conversion fails.
Returns an infinity if the conversion overflows or 0.0 if the
conversion fails for other reasons (e.g. underflow).
If \a ok is not \c nullptr, failure is reported by setting *\a{ok}
to \c false, and success by setting *\a{ok} to \c true.

View File

@ -1353,8 +1353,10 @@ qulonglong QLocale::toULongLong(const QString &s, bool *ok) const
}
/*!
Returns the float represented by the localized string \a s, or 0.0
if the conversion failed.
Returns the float represented by the localized string \a s.
Returns an infinity if the conversion overflows or 0.0 if the
conversion fails for any other reason (e.g. underflow).
If \a ok is not \c nullptr, failure is reported by setting *\a{ok}
to \c false, and success by setting *\a{ok} to \c true.
@ -1522,8 +1524,10 @@ qulonglong QLocale::toULongLong(const QStringRef &s, bool *ok) const
}
/*!
Returns the float represented by the localized string \a s, or 0.0
if the conversion failed.
Returns the float represented by the localized string \a s.
Returns an infinity if the conversion overflows or 0.0 if the
conversion fails for any other reason (e.g. underflow).
If \a ok is not \c nullptr, failure is reported by setting *\a{ok}
to \c false, and success by setting *\a{ok} to \c true.
@ -1696,8 +1700,10 @@ qulonglong QLocale::toULongLong(QStringView s, bool *ok) const
}
/*!
Returns the float represented by the localized string \a s, or 0.0
if the conversion failed.
Returns the float represented by the localized string \a s.
Returns an infinity if the conversion overflows or 0.0 if the
conversion fails for any other reason (e.g. underflow).
If \a ok is not \c nullptr, failure is reported by setting *\a{ok}
to \c false, and success by setting *\a{ok} to \c true.

View File

@ -249,7 +249,16 @@ public:
if (std::fabs(d) > std::numeric_limits<float>::max()) {
if (ok != 0)
*ok = false;
return 0.0f;
const float huge = std::numeric_limits<float>::infinity();
return d < 0 ? -huge : huge;
}
if (std::fabs(d) >= std::numeric_limits<double>::min() // i.e. d != 0
&& std::fabs(d) < std::numeric_limits<float>::min()) {
// Values smaller than std::numeric_limits<double>::min() have
// failed already; match them.
if (ok != 0)
*ok = false;
return 0;
}
return float(d);
}

View File

@ -7208,7 +7208,8 @@ double QString::toDouble(bool *ok) const
/*!
Returns the string converted to a \c float value.
Returns 0.0 if the conversion fails.
Returns an infinity if the conversion overflows or 0.0 if the
conversion fails for other reasons (e.g. underflow).
If \a ok is not \c nullptr, failure is reported by setting *\a{ok}
to \c false, and success by setting *\a{ok} to \c true.
@ -11821,7 +11822,8 @@ double QStringRef::toDouble(bool *ok) const
/*!
Returns the string converted to a \c float value.
Returns 0.0 if the conversion fails.
Returns an infinity if the conversion overflows or 0.0 if the
conversion fails for other reasons (e.g. underflow).
If \a ok is not \c nullptr, failure is reported by setting *\a{ok}
to \c false, and success by setting *\a{ok} to \c true.

View File

@ -877,6 +877,28 @@ void tst_QLocale::stringToDouble()
void tst_QLocale::stringToFloat_data()
{
toReal_data();
if (std::numeric_limits<float>::has_infinity) {
double huge = std::numeric_limits<float>::infinity();
QTest::newRow("C inf") << QString("C") << QString("inf") << true << huge;
QTest::newRow("C +inf") << QString("C") << QString("+inf") << true << +huge;
QTest::newRow("C -inf") << QString("C") << QString("-inf") << true << -huge;
// Overflow float, but not double:
QTest::newRow("C big") << QString("C") << QString("3.5e38") << false << huge;
QTest::newRow("C -big") << QString("C") << QString("-3.5e38") << false << -huge;
// Overflow double, too:
QTest::newRow("C huge") << QString("C") << QString("2e308") << false << huge;
QTest::newRow("C -huge") << QString("C") << QString("-2e308") << false << -huge;
}
if (std::numeric_limits<float>::has_quiet_NaN)
QTest::newRow("C qnan") << QString("C") << QString("NaN") << true << double(std::numeric_limits<float>::quiet_NaN());
// Underflow float, but not double:
QTest::newRow("C small") << QString("C") << QString("1e-45") << false << 0.;
QTest::newRow("C -small") << QString("C") << QString("-1e-45") << false << 0.;
// Underflow double, too:
QTest::newRow("C tiny") << QString("C") << QString("2e-324") << false << 0.;
QTest::newRow("C -tiny") << QString("C") << QString("-2e-324") << false << 0.;
}
void tst_QLocale::stringToFloat()
@ -904,24 +926,24 @@ void tst_QLocale::stringToFloat()
QCOMPARE(ok, good);
}
if (ok) {
if (ok || std::isinf(fnum)) {
// First use fuzzy-compare, then a more precise check:
QCOMPARE(f, fnum);
float diff = f - fnum;
if (diff < 0)
diff = -diff;
QVERIFY(diff <= MY_FLOAT_EPSILON);
if (std::isfinite(fnum)) {
float diff = f > fnum ? f - fnum : fnum - f;
QVERIFY(diff <= MY_FLOAT_EPSILON);
}
}
f = locale.toFloat(num_strRef, &ok);
QCOMPARE(ok, good);
if (ok) {
if (ok || std::isinf(fnum)) {
QCOMPARE(f, fnum);
float diff = f - fnum;
if (diff < 0)
diff = -diff;
QVERIFY(diff <= MY_FLOAT_EPSILON);
if (std::isfinite(fnum)) {
float diff = f > fnum ? f - fnum : fnum - f;
QVERIFY(diff <= MY_FLOAT_EPSILON);
}
}
#undef MY_FLOAT_EPSILON
}