Olaf Japp
Olaf Japp

Reputation: 478

Qt qreal calculates wrong

I want to edit the opacity for a QGraphicsItem with a QSpinBox. The QSpinBox gives me the value 57 which I use to set the opacity of the item. Then I get the changed opacity back from the item and want to fill the QSpinBox. But setting the value of the box results in a mistake.

qDebug() << (int)(qreal)(0.57 * 100.0);

outputs 56

Is this a known bug? Is there a workaround?

Upvotes: 1

Views: 2171

Answers (3)

Integers that fit in the mantissa have exact representation in floating point, thus static_cast<qreal>(100.0) == 100 always holds and is represented as 100*2^0.

Rationals with denominators of the form 2^-n also have exact representation in floating point as long as the numerator fits in the mantissa, thus e.g. static_cast<qreal>(0.25*4) == 1 holds as long as your compiler doesn't use a brain-dead decimal-to-floating-point conversion function. When most compilers parse the code, they convert both 0.25 and 4 to a floating point representation, and then perform the multiplication to obtain the value of the constant expression.

But static_cast<qreal>(0.57) has no representation as m*2^-n, with sufficiently small integer m,n, and is necessarily represented inexactly. It can be represented as a bit less or more than 0.57. Thus when you multiply it by 100, it can be slightly less than 57 - in your case.

The simplest fix is to avoid the roundtrip: store the opacity everywhere as an integer, and only convert from integer to floating point when changing the value. In other words, only ever use the setOpacity() method, and never use the opacity() method. Store the integer-valued opacity using the item's data attribute:

void setOpacity(QGraphicsItem * item, int opacity) {
  item->setData(kOpacity, opacity);
  item->setOpacity(opacity / 100.0);
}

void getOpacity(QGraphicsItem * item) {
  auto data = item->data(kOpacity);
  if (! data.isNull())
    return data.toInt();
  int opacity = round(item->opacity() * 100.0);
  setOpacity(item, opacity);
  return opacity;
}

Upvotes: 3

kefir500
kefir500

Reputation: 4404

Reason

Do not rely on the precision of the floating point numbers due to the inaccuracy of its calculation. You can read more info on this issue here or here.

Demonstration

If you set the higher precision for the output using the qSetRealNumberPrecision, you'll see the actual root of the problem – the result of 0.57 * 100.0 is not exact 57 but something like 56.999999999999992895:

qDebug() << qSetRealNumberPrecision(20) << 0.57 * 100.0;

Solution

So it's better to simply round your number to the nearest integer instead of casting to int which simply omits the fraction number:

qDebug() << qRound(0.57 * 100.0);

Upvotes: 6

Olaf Japp
Olaf Japp

Reputation: 478

I found a temporary solution but I am not happy with that.

qDebug() << "qreal" << (int)(qreal)(0.57 * 100.0);
qDebug() << "double" << (int)(double)(0.57 * 100.0);
qDebug() << "float" << (int)(float)(0.57 * 100.0);

Output: qreal 56, double 56, float 57

Upvotes: -1

Related Questions