[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