[PySide] Strange behaviour of QLocale.toString()

Alexey Vihorev vihorev at gmail.com
Sun Apr 14 21:02:40 CEST 2013


Two considerations:
1. No rounding should take place in this case - the decimal precision of the
input and output is exactly the same.
2. In PyQt it *does* work correctly, so fundamental lows of computer science
are certainly not the reason.
 

-----Original Message-----
From: Zak [mailto:pyside at m.allo.ws] 
Sent: Sunday, April 14, 2013 6:52 PM
To: pyside at qt-project.org; vihorev at gmail.com
Subject: Re: [PySide] Strange behaviour of QLocale.toString()

I wouldn't call it a bug. Floating point values get rounded, that is an
unavoidable part of computer science. There are two sources of rounding:

-----Original Message-----
From: Zak [mailto:pyside at m.allo.ws] 
Sent: Sunday, April 14, 2013 6:52 PM
To: pyside at qt-project.org; vihorev at gmail.com
Subject: Re: [PySide] Strange behaviour of QLocale.toString()

I wouldn't call it a bug. Floating point values get rounded, that is an
unavoidable part of computer science. There are two sources of rounding:

First, only a limited amount of memory is allocated for each floating point
value, usually 32 or 64 bits. 32 bits can hold about 6 or 7 decimal digits
of information. Your example number is 9 digits (9 significant figures), and
that is more information than you can store in just 32 bits. 64 bits can
hold about 15 to 17 decimal digits of information. For reference, Python
floats use 64 bits (it doesn't matter whether it is a 64-bit or 32-bit
version of the Python interpreter).

Second, floating point values are stored in binary (base 2) format, not in
decimal (base 10) format. This can cause strange imprecision. (Of note, it
is possible to store floating point numbers in base 10, see the formats
decimal32 and decimal64 on Wikipedia. These formats are not used by Python,
C, or C++, so this is not relevant to the present
discussion.) To see the problems inherent in converting between base 2 and
base 10, look at the following pure Python example:

 >>> 0.1 * 0.1
0.010000000000000002

Why did Python return 0.010000000000000002 and not the correct answer, which
is 0.01 exactly? The answer is that 0.01 (base 10) is only one significant
figure in base 10, but it is non-terminating in base 2. In base 2, it is
0b0.00000010100011110101110000101... and it goes on forever, although the
digits do repeat. This is similar to the base 10 number 1 / 3 = 0.3333... .
Rational numbers that terminate in base 10 may be non-terminating in base 2.
Because 0.01 is non-terminating in base 2, Python was forced to round the
number so that it will fit in a
64 bit bucket. When converted back to base 10, the rounded result is
0.010000000000000002. Notice that this is 17 significant figures, and
remember that 64 bits can hold 15 to 17 decimal digits. It has come
full-circle.

The matter is even more complicated in your example, because you are using
PySide and PyQt, which use C++ internally. The number must first be
converted from base 10 to base 2, then it must be rounded to fit in
64 bits for Python, then it must be converted from Python into C++. C++ has
several different floating point types, one allocates 32 bits, one allocates
64 bits. I don't know which type PySide and PyQt use, perhaps they only
allocate 32 bits. In that case, it is necessary to round down from the 64
bits in Python to 32 bits for C++.

After passing through so many non-exact steps, it is no wonder the value is
fudged slightly. Remember that in terms of percent, the change is very
small.

If even small rounding is not acceptable, I would recommend the Python
standard library decimal. decimal.Decimal objects store numbers in base 10,
not base 2, and you can choose the precision. It is pretty great.

Good luck,

Zak Fallows




More information about the PySide mailing list