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:
parent
446afc1045
commit
6b3845320a
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -897,7 +897,9 @@ public:
|
||||
OmitGroupSeparator = 0x01,
|
||||
RejectGroupSeparator = 0x02,
|
||||
OmitLeadingZeroInExponent = 0x04,
|
||||
RejectLeadingZeroInExponent = 0x08
|
||||
RejectLeadingZeroInExponent = 0x08,
|
||||
IncludeTrailingZeroesAfterDot = 0x10,
|
||||
RejectTrailingZeroesAfterDot = 0x20
|
||||
};
|
||||
Q_DECLARE_FLAGS(NumberOptions, NumberOption)
|
||||
|
||||
|
@ -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()
|
||||
*/
|
||||
|
@ -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 };
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user