3

I have some simple codes like below

a = 0.8889
print repr(a)
print str(a)

I tried it on several different systems (including Python 2.7.12 ~ 2.7.14, as either prompt input or scripts), always got result as

0.8889
0.8889

Here is one example.

However, on App Engine (runtime Python 2.7.12), I got

0.88890000000000002
0.8889

I understand that repr() trends to be more precise. The question is why App Engine behaves differently. Is it dependency on underlying hardware?

Background:

This fact annoys me because I got unnecessarily loooong digits when converting floats to json.

Update:

This turned out to be a bug of GAE runtime. As Mark commented, the root cause is miss configuration of sys.float_repr_style. GAE support team created a bug report here.

Update2: It has been fixed as of 2018/08/19. Even though not announced yet.

user2686101
  • 352
  • 1
  • 3
  • 13
  • 3
    The GAE Python 2 runtime (unlike the Python 3 runtime) is not a standard build of Python; it intercepts a wide variety of different builtin and stdlib functions and redirects them to GAE services. And, since they've quasi-deprecated the Python 2 runtime, I don't know if they event document the full list of such things anywhere. So, my _guess_ is that `float.__repr__` is one of the things they capture and do via GAE instead of just letting Python take care of it, but I don't know how to tell for sure. – abarnert Aug 15 '18 at 01:07
  • 3
    What's the value of [`sys.float_repr_style`](https://docs.python.org/2/library/sys.html#sys.float_repr_style) on GAE? If it's `'legacy'` rather than `'short'`, that would explain this behaviour. – Mark Dickinson Aug 15 '18 at 15:04
  • @MarkDickinson, It is 'legacy', how can I override it? – user2686101 Aug 16 '18 at 04:05
  • 1
    You can't, I'm afraid. :-( It's an option that's set at configure and build time. But it does at least tell us that GAE probably isn't inventing its own float `repr` algorithm here: it's using what used to be the standard before Python 2.7, which is computing 17 significant digits, then basing the output on those 17 digits (stripping trailing zeros when appropriate). – Mark Dickinson Aug 16 '18 at 07:02
  • Got it. Would you re-post your comment as answer? I will accept it. – user2686101 Aug 16 '18 at 10:41

2 Answers2

1

Apparently, as @abarnert mentioned in the comment, the GAE env does some stuff on the background, and one of those things is increasing the precision of the float numbers used.

One of the first things you can notice is:

>>> a = 0.88890000000000002
>>> print a
0.8889

Which means that the extra digits are useless.

You can reproduce this situation using the Decimal module:

>>> from decimal import *
>>> Context(prec=17).create_decimal_from_float(0.899).__str__()
'0.89900000000000002'

Interestingly, it looks like GAE is trying to simulate the float precision of ~17 (float don't have a specific decimal precision, as they're represented as floating point numbers). If it were >17 you would get more decimals, if it were less, the numbers would not be precise enough.

The advantage of using Decimal is less floating point errors, although it looks like repr() is having some issues with that.

Check this more extended examples to give more context:

>>> from decimal import *
>>> Context(prec=16).create_decimal_from_float(0.899).__str__()
'0.8990000000000000'
>>> Context(prec=17).create_decimal_from_float(0.899).__str__()
'0.89900000000000002'
>>> Context(prec=18).create_decimal_from_float(0.899).__str__()
'0.899000000000000021'
>>> repr(float(1.0000000000000003)).__str__()
'1.0000000000000002'
>>> Context(prec=17).create_decimal_from_float(1.0000000000000003).__str__()
'1.0000000000000002'
>>> repr(float(1.0000000000000002)).__str__()
'1.0000000000000002'
>>> Context(prec=17).create_decimal_from_float(1.0000000000000002).__str__()
'1.0000000000000002'
>>> repr(float(1.0000000000000001)).__str__()
'1.0'
>>> Context(prec=17).create_decimal_from_float(1.0000000000000001).__str__()
'1'

Finally, the actual number represented by the python float 0.899 is:

>>> from decimal import *
>>> Decimal(float(0.899))
Decimal('0.89900000000000002131628207280300557613372802734375')

So, at the end, the representation provided by repr in GAE is quite precise.

Jofre
  • 3,718
  • 1
  • 23
  • 31
  • Thank you for detailed information. Even though the root cause turned out to be different. This post did help me understand it better. – user2686101 Aug 17 '18 at 03:38
1

The value of sys.float_repr_style is 'legacy', an option that is set at build time by GAE and can't be changed.

This version of the repr algorithm is what used to be the format used before Python 2.7, which is computing 17 significant digits, then basing the output on those 17 digits (stripping trailing zeros when appropriate

Mangu
  • 3,160
  • 2
  • 25
  • 42