[PySide] Strange behaviour of QLocale.toString()

Zak pyside at m.allo.ws
Sun Apr 14 17:51:33 CEST 2013


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