QString::number(double): Disentangle from the QLocale path
By writing code to do formatting without considering locale The code tries to not do any unnecessary (re)allocations, and as such it reserves at the beginning and only appends. Cuts execution times of benchmarks by between 30% and 80%: PASS : tst_QString::initTestCase() PASS : tst_QString::number_double(0, format 'f', precision 0) RESULT : tst_QString::number_double():"0, format 'f', precision 0": - 0.0001774 msecs per iteration (total: 2,661, iterations: 15000000) + 0.0001238 msecs per iteration (total: 1,858, iterations: 15000000) PASS : tst_QString::number_double(0, format 'f', precision 0) RESULT : tst_QString::number_double():"0, format 'f', precision 0": - 0.0002472 msecs per iteration (total: 3,709, iterations: 15000000) + 0.0001407 msecs per iteration (total: 2,111, iterations: 15000000) PASS : tst_QString::number_double(0.12340, format 'f', precision 5) RESULT : tst_QString::number_double():"0.12340, format 'f', precision 5": - 0.0004769 msecs per iteration (total: 7,155, iterations: 15000000) + 0.0001638 msecs per iteration (total: 2,458, iterations: 15000000) PASS : tst_QString::number_double(-0.12340, format 'f', precision 5) RESULT : tst_QString::number_double():"-0.12340, format 'f', precision 5": - 0.0005759 msecs per iteration (total: 8,639, iterations: 15000000) + 0.0001664 msecs per iteration (total: 2,497, iterations: 15000000) PASS : tst_QString::number_double(1.618033988749895, format 'f', precision 15) RESULT : tst_QString::number_double():"1.618033988749895, format 'f', precision 15": - 0.0003644 msecs per iteration (total: 5,467, iterations: 15000000) + 0.0001869 msecs per iteration (total: 2,804, iterations: 15000000) PASS : tst_QString::number_double(2.220446049e-16, format 'g', precision 10) RESULT : tst_QString::number_double():"2.220446049e-16, format 'g', precision 10": - 0.00070580 msecs per iteration (total: 10,587, iterations: 15000000) + 0.0002277 msecs per iteration (total: 3,416, iterations: 15000000) PASS : tst_QString::number_double(1.0E-04, format 'E', precision 1) RESULT : tst_QString::number_double():"1.0E-04, format 'E', precision 1": - 0.00082213 msecs per iteration (total: 12,332, iterations: 15000000) + 0.0002018 msecs per iteration (total: 3,028, iterations: 15000000) PASS : tst_QString::number_double(1.0E+08, format 'E', precision 1) RESULT : tst_QString::number_double():"1.0E+08, format 'E', precision 1": - 0.00082459 msecs per iteration (total: 12,369, iterations: 15000000) + 0.0002016 msecs per iteration (total: 3,025, iterations: 15000000) PASS : tst_QString::number_double(-1.0E+08, format 'E', precision 1) RESULT : tst_QString::number_double():"-1.0E+08, format 'E', precision 1": - 0.00093840 msecs per iteration (total: 14,076, iterations: 15000000) + 0.0002074 msecs per iteration (total: 3,111, iterations: 15000000) PASS : tst_QString::cleanupTestCase() -Totals: 11 passed, 0 failed, 0 skipped, 0 blacklisted, 153777ms +Totals: 11 passed, 0 failed, 0 skipped, 0 blacklisted, 48753ms Task-number: QTBUG-88484 Change-Id: I23234467801243b163dff5cccf8a9fe9d90c3e2a Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>bb10
parent
fe9ddbe197
commit
2696d5a71b
|
|
@ -53,6 +53,8 @@
|
|||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(__UCLIBC__)
|
||||
# include <fenv.h>
|
||||
#endif
|
||||
|
|
@ -580,4 +582,190 @@ QString qdtoa(qreal d, int *decpt, int *sign)
|
|||
return QLatin1String(result, length);
|
||||
}
|
||||
|
||||
static QLocaleData::DoubleForm resolveFormat(int precision, int decpt, qsizetype length)
|
||||
{
|
||||
bool useDecimal;
|
||||
if (precision == QLocale::FloatingPointShortest) {
|
||||
// Find out which representation is shorter.
|
||||
// Set bias to everything added to exponent form but not
|
||||
// decimal, minus the converse.
|
||||
|
||||
// Exponent adds separator, sign and two exponents:
|
||||
int bias = 2 + 2;
|
||||
if (length <= decpt && length > 1)
|
||||
++bias;
|
||||
else if (length == 1 && decpt <= 0)
|
||||
--bias;
|
||||
|
||||
// When 0 < decpt <= length, the forms have equal digit
|
||||
// counts, plus things bias has taken into account;
|
||||
// otherwise decimal form's digit count is right-padded with
|
||||
// zeros to decpt, when decpt is positive, otherwise it's
|
||||
// left-padded with 1 - decpt zeros.
|
||||
if (decpt <= 0)
|
||||
useDecimal = 1 - decpt <= bias;
|
||||
else if (decpt <= length)
|
||||
useDecimal = true;
|
||||
else
|
||||
useDecimal = decpt <= length + bias;
|
||||
} else {
|
||||
// X == decpt - 1, POSIX's P; -4 <= X < P iff -4 < decpt <= P
|
||||
Q_ASSERT(precision >= 0);
|
||||
useDecimal = decpt > -4 && decpt <= (precision ? precision : 1);
|
||||
}
|
||||
return useDecimal ? QLocaleData::DFDecimal : QLocaleData::DFExponent;
|
||||
}
|
||||
|
||||
static constexpr int digits(int number)
|
||||
{
|
||||
Q_ASSERT(number >= 0);
|
||||
if (Q_LIKELY(number < 1000))
|
||||
return number < 10 ? 1 : number < 100 ? 2 : 3;
|
||||
int i = 3;
|
||||
for (number /= 1000; number; number /= 10)
|
||||
++i;
|
||||
return i;
|
||||
}
|
||||
|
||||
QString qdtoBasicLatin(double d, QLocaleData::DoubleForm form, int precision, bool uppercase)
|
||||
{
|
||||
// Undocumented: aside from F.P.Shortest, precision < 0 is treated as
|
||||
// default, 6 - same as printf().
|
||||
if (precision != QLocale::FloatingPointShortest && precision < 0)
|
||||
precision = 6;
|
||||
|
||||
using D = std::numeric_limits<double>;
|
||||
// 1 is for the null-terminator
|
||||
constexpr int MaxDigits = 1 + qMax(D::max_exponent10, D::digits10 - D::min_exponent10);
|
||||
|
||||
// "maxDigits" above is a reasonable estimate, though we may need more due to extra precision
|
||||
int bufSize = 1;
|
||||
if (precision == QLocale::FloatingPointShortest)
|
||||
bufSize += D::max_digits10;
|
||||
else if (form == QLocaleData::DFDecimal && qIsFinite(d))
|
||||
bufSize += wholePartSpace(qAbs(d)) + precision;
|
||||
else // Add extra digit due to different interpretations of precision.
|
||||
bufSize += qMax(2, precision) + 1; // Must also be big enough for "nan" or "inf"
|
||||
|
||||
// Reserve `MaxDigits` on the stack, which is a reasonable estimate;
|
||||
// but we may need more due to extra precision, which we cannot know at compile-time.
|
||||
QVarLengthArray<char, MaxDigits> buffer(bufSize);
|
||||
bool negative = false;
|
||||
int length = 0;
|
||||
int decpt = 0;
|
||||
qt_doubleToAscii(d, form, precision, buffer.data(), buffer.length(), negative, length, decpt);
|
||||
QLatin1String view(buffer.data(), buffer.data() + length);
|
||||
const bool succinct = form == QLocaleData::DFSignificantDigits;
|
||||
qsizetype total = (negative ? 1 : 0) + length;
|
||||
if (qIsFinite(d)) {
|
||||
if (succinct)
|
||||
form = resolveFormat(precision, decpt, view.size());
|
||||
|
||||
switch (form) {
|
||||
case QLocaleData::DFExponent:
|
||||
total += 3; // (.e+) The '.' may not be needed, but we would only overestimate by 1 char
|
||||
// Exponents: we guarantee at least 2
|
||||
total += std::max(2, digits(std::abs(decpt - 1)));
|
||||
// "length - 1" because one of the digits will always be before the decimal point
|
||||
if (int extraPrecision = precision - (length - 1); extraPrecision > 0 && !succinct)
|
||||
total += extraPrecision; // some requested zero-padding
|
||||
break;
|
||||
case QLocaleData::DFDecimal:
|
||||
if (decpt <= 0) // leading "0." and zeros
|
||||
total += 2 - decpt;
|
||||
else if (decpt < length) // just the dot
|
||||
total += 1;
|
||||
else // trailing zeros (and no dot, unless we require extra precision):
|
||||
total += decpt - length;
|
||||
|
||||
if (precision > 0 && !succinct) {
|
||||
// May need trailing zeros to satisfy precision:
|
||||
if (decpt < length)
|
||||
total += std::max(0, precision - length + decpt);
|
||||
else // and a dot to separate them:
|
||||
total += 1 + precision;
|
||||
}
|
||||
break;
|
||||
case QLocaleData::DFSignificantDigits:
|
||||
Q_UNREACHABLE(); // Handled earlier
|
||||
}
|
||||
}
|
||||
QString result;
|
||||
result.reserve(total);
|
||||
if (negative && !isZero(d)) // We don't return "-0"
|
||||
result.append(u'-');
|
||||
if (!qIsFinite(d)) {
|
||||
result.append(view);
|
||||
if (uppercase)
|
||||
result = std::move(result).toUpper();
|
||||
} else {
|
||||
switch (form) {
|
||||
case QLocaleData::DFExponent: {
|
||||
result.append(view.first(1));
|
||||
view = view.sliced(1);
|
||||
if (!view.isEmpty() || (!succinct && precision > 0)) {
|
||||
result.append(u'.');
|
||||
result.append(view);
|
||||
if (qsizetype pad = precision - view.size(); !succinct && pad > 0) {
|
||||
for (int i = 0; i < pad; ++i)
|
||||
result.append(u'0');
|
||||
}
|
||||
}
|
||||
int exponent = decpt - 1;
|
||||
result.append(uppercase ? u'E' : u'e');
|
||||
result.append(exponent < 0 ? u'-' : u'+');
|
||||
exponent = std::abs(exponent);
|
||||
Q_ASSUME(exponent <= D::max_exponent10 + D::max_digits10);
|
||||
int exponentDigits = digits(exponent);
|
||||
// C's printf guarantees a two-digit exponent, and so do we:
|
||||
if (exponentDigits == 1)
|
||||
result.append(u'0');
|
||||
result.resize(result.size() + exponentDigits);
|
||||
auto location = reinterpret_cast<char16_t *>(result.end());
|
||||
qulltoBasicLatin_helper(exponent, 10, location);
|
||||
break;
|
||||
}
|
||||
case QLocaleData::DFDecimal:
|
||||
if (decpt < 0) {
|
||||
result.append(u"0.0");
|
||||
while (++decpt < 0)
|
||||
result.append(u'0');
|
||||
result.append(view);
|
||||
if (!succinct) {
|
||||
auto numDecimals = result.size() - 2 - (negative ? 1 : 0);
|
||||
for (qsizetype i = numDecimals; i < precision; ++i)
|
||||
result.append(u'0');
|
||||
}
|
||||
} else {
|
||||
if (decpt > view.size()) {
|
||||
result.append(view);
|
||||
const int sign = negative ? 1 : 0;
|
||||
while (result.size() - sign < decpt)
|
||||
result.append(u'0');
|
||||
view = {};
|
||||
} else if (decpt) {
|
||||
result.append(view.first(decpt));
|
||||
view = view.sliced(decpt);
|
||||
} else {
|
||||
result.append(u'0');
|
||||
}
|
||||
if (!view.isEmpty() || (!succinct && view.size() < precision)) {
|
||||
result.append(u'.');
|
||||
result.append(view);
|
||||
if (!succinct) {
|
||||
for (qsizetype i = view.size(); i < precision; ++i)
|
||||
result.append(u'0');
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case QLocaleData::DFSignificantDigits:
|
||||
Q_UNREACHABLE(); // taken care of earlier
|
||||
break;
|
||||
}
|
||||
}
|
||||
Q_ASSERT(total >= result.size()); // No reallocations are needed
|
||||
return result;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ void qt_doubleToAscii(double d, QLocaleData::DoubleForm form, int precision, cha
|
|||
QString qulltoBasicLatin(qulonglong l, int base, bool negative);
|
||||
QString qulltoa(qulonglong l, int base, const QStringView zero);
|
||||
Q_CORE_EXPORT QString qdtoa(qreal d, int *decpt, int *sign);
|
||||
QString qdtoBasicLatin(double d, QLocaleData::DoubleForm form, int precision, bool uppercase);
|
||||
|
||||
inline bool isZero(double d)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7363,10 +7363,6 @@ QString QString::number(qulonglong n, int base)
|
|||
QString QString::number(double n, char format, int precision)
|
||||
{
|
||||
QLocaleData::DoubleForm form = QLocaleData::DFDecimal;
|
||||
uint flags = QLocaleData::ZeroPadExponent;
|
||||
|
||||
if (qIsUpper(format))
|
||||
flags |= QLocaleData::CapitalEorX;
|
||||
|
||||
switch (qToLower(format)) {
|
||||
case 'f':
|
||||
|
|
@ -7385,7 +7381,7 @@ QString QString::number(double n, char format, int precision)
|
|||
break;
|
||||
}
|
||||
|
||||
return QLocaleData::c()->doubleToString(n, precision, form, -1, flags);
|
||||
return qdtoBasicLatin(n, form, precision, qIsUpper(format));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
|
|
|||
Loading…
Reference in New Issue