Android A11Y: Fix value rounding

QAccessibleInterface stores values as QVariants. When dealing with
double values, as a result of some calculation (for example, Slider
value update), rounding errors can be introduced.
When converting double values to QString using QVariant::toString(),
these rounding errors result in strings like
0.30000000000000004 instead of 0.3 or
2.7755575615628914e-17 instead of 0.3 - 3 * 0.1 and similar zeroes.

To fix this issue, this patch introduces a custom conversion for
floating-point values. The idea is to convert QVariant to double,
and then convert double to QString using 'f' format and a suitable
precision, determined from the UI element's minimumStepSize(), if
it has one, otherwise falling back to QString::number()'s default
(which is 6).

Task-number: QTBUG-93396
Pick-to: 6.3 6.2 5.15
Change-Id: Ia5ca7345812e39629e9c191b6d8b896a8f51de80
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
bb10
Ivan Solovev 2022-02-10 16:43:38 +01:00
parent a374d59abc
commit 60799fc141
1 changed files with 44 additions and 2 deletions

View File

@ -358,8 +358,50 @@ if (!clazz) { \
QString valueStr;
QAccessibleValueInterface *valueIface = iface->valueInterface();
if (valueIface) {
// TODO: fix double-to-string conversion
valueStr = valueIface->currentValue().toString();
const QVariant valueVar = valueIface->currentValue();
const auto type = valueVar.typeId();
if (type == QMetaType::Double || type == QMetaType::Float) {
// QVariant's toString() formats floating-point values with
// FloatingPointShortest, which is not an accessible
// representation; nor, in many cases, is it suitable to the UI
// element whose value we're looking at. So roll our own
// A11Y-friendly conversion to string.
const double val = valueVar.toDouble();
// Try to use minimumStepSize() to determine precision
bool stepIsValid = false;
const double step = qAbs(valueIface->minimumStepSize().toDouble(&stepIsValid));
if (!stepIsValid || qFuzzyIsNull(step)) {
// Ignore step, use default precision
valueStr = qFuzzyIsNull(val) ? u"0"_qs : QString::number(val, 'f');
} else {
const int precision = [](double s) {
int count = 0;
while (s < 1. && !qFuzzyCompare(s, 1.)) {
++count;
s *= 10;
}
// If s is now 1.25, we want to show some more digits,
// but don't want to get silly with a step like 1./7;
// so only include a few extra digits.
const int stop = count + 3;
const auto fractional = [](double v) {
double whole = 0.0;
std::modf(v + 0.5, &whole);
return qAbs(v - whole);
};
s = fractional(s);
while (count < stop && !qFuzzyIsNull(s)) {
++count;
s = fractional(s * 10);
}
return count;
}(step);
valueStr = qFuzzyIsNull(val / step) ? u"0"_qs
: QString::number(val, 'f', precision);
}
} else {
valueStr = valueVar.toString();
}
}
return valueStr;
}