2

I don't understand why this happens, value l/m does not get truncated to two decimals after the dot...

root@OpenWrt:~# python3
Python 3.7.4 (default, Sep 15 2019, 18:13:03) 
[GCC 7.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> l = round((69.2222222/100),2)                  
>>> print(l)
0.68999999999999995
>>> type(l)
<class 'float'>
>>> m = 69.22222222/100
>>> print(m)
0.69222222220000007
>>> type(m)
<class 'float'>
>>> round(m,2)
0.68999999999999995

This happens on a custom openWRT 18.6 build with (almost) full python3 installation.

Why does that happen?

Am I missing any packages?

martineau
  • 119,623
  • 25
  • 170
  • 301
novski
  • 196
  • 1
  • 11
  • 2
    Short answer: Floating point numbers cannot represent fractions precisely if the denominator is not a power of two. The duplicate answer cited above suggests some work-arounds. – Tom Karzes Jan 26 '20 at 19:18
  • 1
    Hmm, I already get a different value for `m`: `0.6922222222000001`. What does `import sys; sys.float_info` give you? – Kelly Bundy Jan 26 '20 at 19:28
  • `>>> import sys >>> sys.float_info sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.2204460492503131e-16, radix=2, rounds=1) ` – novski Jan 26 '20 at 19:31
  • 1
    @novski: More importantly, what's `sys.float_repr_style`? My suspicion is that it's `'legacy'`, meaning that Python's build system was unable to figure out how to avoid x87-style double rounding at build time. See https://stackoverflow.com/q/29920294/270986 for more information. – Mark Dickinson Jan 26 '20 at 19:32
  • @TomKarzes: There's more going on here than the usual floating-point issues. This has to do with the way that Python was built. – Mark Dickinson Jan 26 '20 at 19:33
  • true! it's `'legacy'`. How avoid that, other than `l = '%.2f' % round((69.2222222/100),2)`? – novski Jan 26 '20 at 19:36
  • @novski: Ideally, you'd go talk to the people who built the Python executable (perhaps that's you?). If the platform has some method for emulating IEEE 754-compliant arithmetic, then it should be possible to configure Python's build system appropriately to use that method. The question I linked above has a few more details. – Mark Dickinson Jan 26 '20 at 19:38
  • @MarkDickinson I do get different results on my machine, although the underlying problem is still there. When I do it, it "appears" to give 0.69, and if I multiply by 100 it compares identical to the integer 69. But that's just due to the multiply resulting in an exact integer, not because it's truly representing 0.69. To see the problem on my machine, I just subtract 0.5 from it, which is an exact floating point value. Instead of 0.19, it now appears as 0.18999999999999995. Try this: `round(69.22222222/100, 2) - 0.5` I expect you'll see something similar. – Tom Karzes Jan 26 '20 at 21:19
  • This Question turns out to be a openWRT 18.6 and maybe prior problem that was now fixed for newer version 19.7. You can track it here: https://github.com/openwrt/packages/issues/11134 – novski Feb 02 '20 at 11:30
  • @novski Thanks for the followup. So it was a cross-compilation issue in CPython's build configuration; that fits. – Mark Dickinson Feb 06 '20 at 20:52
  • @TomKarzes: Yep; you're describing the usual binary floating-point what-you-see-is-not-what-you-get issues. Like I said, there's a bit more than that going on in this case; neither of the marked duplicates is appropriate here. – Mark Dickinson Feb 06 '20 at 20:54

1 Answers1

4

Summary: The answer you're getting is correct, however it is not printing as it should. Fortunately, there is an easy remediation without fixing the build.

1) The answer is correct. The underlying C double is identical for both 0.69 and the rounded answer in your session:

>>> (0.68999999999999995).hex()
'0x1.6147ae147ae14p-1'
>>> (0.69).hex()
'0x1.6147ae147ae14p-1'

2) The print routine in your build omits Python's efforts to show the shortest possible of the equivalent representations. Here's the same session on my system:

Python 3.8.1 (v3.8.1:1b293b6006, Dec 18 2019, 14:08:53) 
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license()" for more information.
>>> l = round((69.2222222/100),2)
>>> print(l)
0.69
>>> m = 69.22222222/100
>>> print(m)
0.6922222222000001
>>> round(m, 2)
0.69

3) For display purposes, just use the rounding in string formatting. This will work even if your build omits the shortest-output routine:

>>> m = 69.22222222/100
>>> format(m, '.2f')
'0.69'
Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485