QLocale: Add option to pad numbers with trailing zeroes

EcmaScript mandates that number-to-string functions pad the resulting
strings with zeroes, up to the requested precision. QLocale actually
supports this, under the disguise of the "Alternate" flag, used by
QString::asprintf().

We split this flag into the three options it actually represents and
make IncludeTrailingZeroesAfterDot available as a NumberOption. This
allows us to generate numbers in an EcmaScript compliant way.

In addition, a symmetrical option to reject trailing zeroes when
parsing strings to numbers is added.

[ChangeLog][QtCore][QLocale] Additional flags in QLocale::NumberOption
allow generating strings from doubles in accordance to EcmaScript's
Number.toPrecision(n).

Change-Id: If1090d5a0364a29811011a472afc8b75d0af0a8f
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Ulf Hermann 2016-08-19 12:22:31 +02:00
parent 446afc1045
commit 6b3845320a
7 changed files with 100 additions and 38 deletions

View File

@ -2547,6 +2547,7 @@ QTextStream &QTextStream::operator<<(double f)
}
uint flags = 0;
const QLocale::NumberOptions numberOptions = locale().numberOptions();
if (numberFlags() & ShowBase)
flags |= QLocaleData::ShowBase;
if (numberFlags() & ForceSign)
@ -2555,12 +2556,18 @@ QTextStream &QTextStream::operator<<(double f)
flags |= QLocaleData::UppercaseBase;
if (numberFlags() & UppercaseDigits)
flags |= QLocaleData::CapitalEorX;
if (numberFlags() & ForcePoint)
flags |= QLocaleData::Alternate;
if (locale() != QLocale::c() && !(locale().numberOptions() & QLocale::OmitGroupSeparator))
if (numberFlags() & ForcePoint) {
flags |= QLocaleData::ForcePoint;
// Only for backwards compatibility
flags |= QLocaleData::AddTrailingZeroes | QLocaleData::ShowBase;
}
if (locale() != QLocale::c() && !(numberOptions & QLocale::OmitGroupSeparator))
flags |= QLocaleData::ThousandsGroup;
if (!(locale().numberOptions() & QLocale::OmitLeadingZeroInExponent))
if (!(numberOptions & QLocale::OmitLeadingZeroInExponent))
flags |= QLocaleData::ZeroPadExponent;
if (numberOptions & QLocale::IncludeTrailingZeroesAfterDot)
flags |= QLocaleData::AddTrailingZeroes;
const QLocaleData *dd = d->locale.d->m_data;
QString num = dd->doubleToString(f, d->params.realNumberPrecision, form, -1, flags);

View File

@ -2024,6 +2024,8 @@ QString QLocale::toString(double i, char f, int prec) const
flags |= QLocaleData::ThousandsGroup;
if (!(d->m_numberOptions & OmitLeadingZeroInExponent))
flags |= QLocaleData::ZeroPadExponent;
if (d->m_numberOptions & IncludeTrailingZeroesAfterDot)
flags |= QLocaleData::AddTrailingZeroes;
return d->m_data->doubleToString(i, prec, form, -1, flags);
}
@ -2785,7 +2787,7 @@ QString QLocaleData::doubleToString(const QChar _zero, const QChar plus, const Q
reinterpret_cast<ushort *>(digits.data())[i] += z;
}
bool always_show_decpt = (flags & Alternate || flags & ForcePoint);
bool always_show_decpt = (flags & ForcePoint);
switch (form) {
case DFExponent: {
num_str = exponentForm(_zero, decimal, exponential, group, plus, minus,
@ -2800,7 +2802,7 @@ QString QLocaleData::doubleToString(const QChar _zero, const QChar plus, const Q
break;
}
case DFSignificantDigits: {
PrecisionMode mode = (flags & Alternate) ?
PrecisionMode mode = (flags & AddTrailingZeroes) ?
PMSignificantDigits : PMChopTrailingZeros;
int cutoff = precision < 0 ? 6 : precision;
@ -2905,7 +2907,7 @@ QString QLocaleData::longLongToString(const QChar zero, const QChar group,
for (int i = num_str.length()/* - cnt_thousand_sep*/; i < precision; ++i)
num_str.prepend(base == 10 ? zero : QChar::fromLatin1('0'));
if ((flags & Alternate || flags & ShowBase)
if ((flags & ShowBase)
&& base == 8
&& (num_str.isEmpty() || num_str[0].unicode() != QLatin1Char('0')))
num_str.prepend(QLatin1Char('0'));
@ -2926,10 +2928,10 @@ QString QLocaleData::longLongToString(const QChar zero, const QChar group,
--num_pad_chars;
// leave space for optional '0x' in hex form
if (base == 16 && (flags & Alternate || flags & ShowBase))
if (base == 16 && (flags & ShowBase))
num_pad_chars -= 2;
// leave space for optional '0b' in binary form
else if (base == 2 && (flags & Alternate || flags & ShowBase))
else if (base == 2 && (flags & ShowBase))
num_pad_chars -= 2;
for (int i = 0; i < num_pad_chars; ++i)
@ -2939,9 +2941,9 @@ QString QLocaleData::longLongToString(const QChar zero, const QChar group,
if (flags & CapitalEorX)
num_str = num_str.toUpper();
if (base == 16 && (flags & Alternate || flags & ShowBase))
if (base == 16 && (flags & ShowBase))
num_str.prepend(QLatin1String(flags & UppercaseBase ? "0X" : "0x"));
if (base == 2 && (flags & Alternate || flags & ShowBase))
if (base == 2 && (flags & ShowBase))
num_str.prepend(QLatin1String(flags & UppercaseBase ? "0B" : "0b"));
// add sign
@ -2988,7 +2990,7 @@ QString QLocaleData::unsLongLongToString(const QChar zero, const QChar group,
for (int i = num_str.length()/* - cnt_thousand_sep*/; i < precision; ++i)
num_str.prepend(base == 10 ? zero : QChar::fromLatin1('0'));
if ((flags & Alternate || flags & ShowBase)
if ((flags & ShowBase)
&& base == 8
&& (num_str.isEmpty() || num_str[0].unicode() != QLatin1Char('0')))
num_str.prepend(QLatin1Char('0'));
@ -3003,10 +3005,10 @@ QString QLocaleData::unsLongLongToString(const QChar zero, const QChar group,
int num_pad_chars = width - num_str.length();
// leave space for optional '0x' in hex form
if (base == 16 && flags & Alternate)
if (base == 16 && flags & ShowBase)
num_pad_chars -= 2;
// leave space for optional '0b' in binary form
else if (base == 2 && flags & Alternate)
else if (base == 2 && flags & ShowBase)
num_pad_chars -= 2;
for (int i = 0; i < num_pad_chars; ++i)
@ -3016,9 +3018,9 @@ QString QLocaleData::unsLongLongToString(const QChar zero, const QChar group,
if (flags & CapitalEorX)
num_str = num_str.toUpper();
if (base == 16 && (flags & Alternate || flags & ShowBase))
if (base == 16 && flags & ShowBase)
num_str.prepend(QLatin1String(flags & UppercaseBase ? "0X" : "0x"));
else if (base == 2 && (flags & Alternate || flags & ShowBase))
else if (base == 2 && flags & ShowBase)
num_str.prepend(QLatin1String(flags & UppercaseBase ? "0B" : "0b"));
// add sign
@ -3079,25 +3081,37 @@ bool QLocaleData::numberToCLocale(const QChar *str, int len, QLocale::NumberOpti
out = in.toLatin1();
else
break;
} else if (out == '.') {
// Fail if more than one decimal point or point after e
if (decpt_idx != -1 || exponent_idx != -1)
return false;
decpt_idx = idx;
} else if (out == 'e' || out == 'E') {
exponent_idx = idx;
}
if (number_options & QLocale::RejectLeadingZeroInExponent) {
if (out == 'e' || out == 'E') {
exponent_idx = idx;
} else if (exponent_idx != -1) {
if (out >= '1' && out <= '9')
exponent_idx = -1; // leading digit is not 0, forget exponent_idx
else if (out == '0' && idx < l - 1)
if (exponent_idx != -1 && out == '0' && idx < l - 1) {
// After the exponent there can only be '+', '-' or digits.
// If we find a '0' directly after some non-digit, then that is a leading zero.
if (result->last() < '0' || result->last() > '9')
return false;
}
}
if (number_options & QLocale::RejectTrailingZeroesAfterDot) {
// If we've seen a decimal point and the last character after the exponent is 0, then
// that is a trailing zero.
if (decpt_idx >= 0 && idx == exponent_idx && result->last() == '0')
return false;
}
if (!(number_options & QLocale::RejectGroupSeparator)) {
if (start_of_digits_idx == -1 && out >= '0' && out <= '9') {
start_of_digits_idx = idx;
} else if (out == ',') {
// Don't allow group chars after the decimal point
if (decpt_idx != -1)
// Don't allow group chars after the decimal point or exponent
if (decpt_idx != -1 || exponent_idx != -1)
return false;
// check distance from the last separator or from the beginning of the digits
@ -3114,12 +3128,6 @@ bool QLocaleData::numberToCLocale(const QChar *str, int len, QLocale::NumberOpti
++idx;
continue;
} else if (out == '.' || out == 'e' || out == 'E') {
// Fail if more than one decimal point
if (out == '.' && decpt_idx != -1)
return false;
if (decpt_idx == -1)
decpt_idx = idx;
// check distance from the last separator
// ### FIXME: Some locales allow other groupings! See https://en.wikipedia.org/wiki/Thousands_separator
if (last_separator_idx != -1 && idx - last_separator_idx != 4)
@ -3145,6 +3153,12 @@ bool QLocaleData::numberToCLocale(const QChar *str, int len, QLocale::NumberOpti
return false;
}
if (number_options & QLocale::RejectTrailingZeroesAfterDot) {
// In decimal form, the last character can be a trailing zero if we've seen a decpt.
if (decpt_idx != -1 && exponent_idx == -1 && result->last() == '0')
return false;
}
result->append('\0');
return idx == l;
}

View File

@ -897,7 +897,9 @@ public:
OmitGroupSeparator = 0x01,
RejectGroupSeparator = 0x02,
OmitLeadingZeroInExponent = 0x04,
RejectLeadingZeroInExponent = 0x08
RejectLeadingZeroInExponent = 0x08,
IncludeTrailingZeroesAfterDot = 0x10,
RejectTrailingZeroesAfterDot = 0x20
};
Q_DECLARE_FLAGS(NumberOptions, NumberOption)

View File

@ -969,7 +969,8 @@
setNumberOptions().
\value DefaultNumberOptions This option represents the default behavior, with
group separators and with one leading zero in single digit exponents.
group separators, with one leading zero in single digit exponents, and
without trailing zeroes after the decimal dot.
\value OmitGroupSeparator If this option is set, the number-to-string functions
will not insert group separators in their return values. The default
is to insert group separators.
@ -984,6 +985,14 @@
functions will fail if they encounter an exponent padded with zeroes when
parsing a floating point number in scientific notation. The default is to
accept such padding.
\value IncludeTrailingZeroesAfterDot If this option is set, the number-to-string
functions will pad numbers with zeroes to the requested precision in "g"
or "most concise" mode, even if the number of significant digits is lower
than the requested precision. The default is to omit trailing zeroes.
\value RejectTrailingZeroesAfterDot If this option is set, the string-to-number
functions will fail if they encounter trailing zeroes after the decimal
dot when parsing a number in scientific or decimal representation. The
default is to accept trailing zeroes.
\sa setNumberOptions(), numberOptions()
*/

View File

@ -193,7 +193,7 @@ public:
enum Flags {
NoFlags = 0,
Alternate = 0x01,
AddTrailingZeroes = 0x01,
ZeroPadded = 0x02,
LeftAdjusted = 0x04,
BlankBeforePositive = 0x08,
@ -204,7 +204,7 @@ public:
ShowBase = 0x80,
UppercaseBase = 0x100,
ZeroPadExponent = 0x200,
ForcePoint = Alternate
ForcePoint = 0x400
};
enum NumberMode { IntegerMode, DoubleStandardMode, DoubleScientificMode };

View File

@ -5908,7 +5908,10 @@ static uint parse_flag_characters(const char * &c) Q_DECL_NOTHROW
uint flags = QLocaleData::ZeroPadExponent;
while (true) {
switch (*c) {
case '#': flags |= QLocaleData::Alternate; break;
case '#':
flags |= QLocaleData::ShowBase | QLocaleData::AddTrailingZeroes
| QLocaleData::ForcePoint;
break;
case '0': flags |= QLocaleData::ZeroPadded; break;
case '-': flags |= QLocaleData::LeftAdjusted; break;
case ' ': flags |= QLocaleData::BlankBeforePositive; break;
@ -6166,7 +6169,7 @@ QString QString::vasprintf(const char *cformat, va_list ap)
case 'p': {
void *arg = va_arg(ap, void*);
const quint64 i = reinterpret_cast<quintptr>(arg);
flags |= QLocaleData::Alternate;
flags |= QLocaleData::ShowBase;
subst = QLocaleData::c()->unsLongLongToString(i, precision, 16, width, flags);
++c;
break;
@ -7741,10 +7744,13 @@ QString QString::arg(double a, int fieldWidth, char fmt, int prec, QChar fillCha
if (d.locale_occurrences > 0) {
QLocale locale;
if (!(locale.numberOptions() & QLocale::OmitGroupSeparator))
const QLocale::NumberOptions numberOptions = locale.numberOptions();
if (!(numberOptions & QLocale::OmitGroupSeparator))
flags |= QLocaleData::ThousandsGroup;
if (!(locale.numberOptions() & QLocale::OmitLeadingZeroInExponent))
if (!(numberOptions & QLocale::OmitLeadingZeroInExponent))
flags |= QLocaleData::ZeroPadExponent;
if (numberOptions & QLocale::IncludeTrailingZeroesAfterDot)
flags |= QLocaleData::AddTrailingZeroes;
locale_arg = locale.d->m_data->doubleToString(a, prec, form, fieldWidth, flags);
}

View File

@ -1754,6 +1754,30 @@ void tst_QLocale::numberOptions()
QVERIFY(ok);
locale.toDouble(QString("1.24e+01"), &ok);
QVERIFY(!ok);
QCOMPARE(locale.toString(12.4, 'g', 5), QString("12.4"));
locale.setNumberOptions(QLocale::IncludeTrailingZeroesAfterDot);
QCOMPARE(locale.numberOptions(), QLocale::IncludeTrailingZeroesAfterDot);
QCOMPARE(locale.toString(12.4, 'g', 5), QString("12.400"));
locale.toDouble(QString("1.24e+01"), &ok);
QVERIFY(ok);
locale.toDouble(QString("1.2400e+01"), &ok);
QVERIFY(ok);
locale.toDouble(QString("12.4"), &ok);
QVERIFY(ok);
locale.toDouble(QString("12.400"), &ok);
QVERIFY(ok);
locale.setNumberOptions(QLocale::RejectTrailingZeroesAfterDot);
QCOMPARE(locale.numberOptions(), QLocale::RejectTrailingZeroesAfterDot);
locale.toDouble(QString("1.24e+01"), &ok);
QVERIFY(ok);
locale.toDouble(QString("1.2400e+01"), &ok);
QVERIFY(!ok);
locale.toDouble(QString("12.4"), &ok);
QVERIFY(ok);
locale.toDouble(QString("12.400"), &ok);
QVERIFY(!ok);
}
void tst_QLocale::negativeNumbers()