diff options
author | Mårten Nordheim <marten.nordheim@qt.io> | 2021-07-27 12:47:30 +0200 |
---|---|---|
committer | Mårten Nordheim <marten.nordheim@qt.io> | 2021-08-17 00:31:26 +0200 |
commit | 2696d5a71b16e4288f4b071f9291cec7f719455a (patch) | |
tree | 96dd72a80fcb651170e9c8456d50c7c762925891 /src/corelib/text/qlocale_tools.cpp | |
parent | fe9ddbe197d6ce4ff2634415c621c8fd9fe5810a (diff) |
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>
Diffstat (limited to 'src/corelib/text/qlocale_tools.cpp')
-rw-r--r-- | src/corelib/text/qlocale_tools.cpp | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/src/corelib/text/qlocale_tools.cpp b/src/corelib/text/qlocale_tools.cpp index bdb00d3696..2826e8b10e 100644 --- a/src/corelib/text/qlocale_tools.cpp +++ b/src/corelib/text/qlocale_tools.cpp @@ -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 |