21

When converting a float to a str, I can specify the number of decimal points I want to display

'%.6f' % 0.1
> '0.100000'
'%.6f' % .12345678901234567890
> '0.123457'

But when simply calling str on a float in python 2.7, it seems to default to 12 decimal points max

str(0.1)
>'0.1'
str(.12345678901234567890)
>'0.123456789012'

Where is this max # of decimal points defined/documented? Can I programmatically get this number?

C_Z_
  • 7,427
  • 5
  • 44
  • 81
  • It might be better to demonstrate something in `< Python 2.7`, as the float represenation changes made in 3.1 were backported to 2.7 – user3483203 Aug 07 '18 at 14:20
  • 1
    FWIW, the "12" (which is specific to Python 2.x, Python 3.1, and earlier) comes from here: https://github.com/python/cpython/blob/d1c5e278a1a2458bc5efcdc300c17f9e39a59b6c/Include/floatobject.h#L28. This changed in Python 3.2. – Mark Dickinson Aug 08 '18 at 07:42
  • @MarkDickinson Thanks, now if only there was a way to check the PyFloat_STR_PRECISION value from Python itself (assuming we know we are using cpython) – C_Z_ Aug 08 '18 at 21:52
  • 1
    @C_Z_ Are there situations you have to deal with where just checking the version isn't good enough? The use of 12 digits has been fixed since the very beginning of Python (at least, it's there in v0.9.8 of Python, released in 1993). It didn't change until Python 3.2, where it became irrelevant because `str` no longer bases its output on a fixed number of significant digits. – Mark Dickinson Aug 09 '18 at 07:24
  • 1
    Note that there _was_ a change in Python 2.0 when the `repr` of the float changed to use 17 significant digits rather than 12. The `str` precision stayed the same, though. – Mark Dickinson Aug 09 '18 at 07:28

4 Answers4

6

The number of decimals displayed is going to vary greatly, and there won't be a way to predict how many will be displayed in pure Python. Some libraries like numpy allow you to set precision of output.

This is simply because of the limitations of float representation.

The relevant parts of the link talk about how Python chooses to display floats.

Python only prints a decimal approximation to the true decimal value of the binary approximation stored by the machine

Python keeps the number of digits manageable by displaying a rounded value instead

Now, there is the possibility of overlap here:

Interestingly, there are many different decimal numbers that share the same nearest approximate binary fraction

The method for choosing which decimal values to display was changed in Python 3.1 (But the last sentence implies this might be an implementation detail).

For example, the numbers 0.1 and 0.10000000000000001 are both approximated by 3602879701896397 / 2 ** 55. Since all of these decimal values share the same approximation, any one of them could be displayed while still preserving the invariant eval(repr(x)) == x

Historically, the Python prompt and built-in repr() function would choose the one with 17 significant digits, 0.10000000000000001. Starting with Python 3.1, Python (on most systems) is now able to choose the shortest of these and simply display 0.1.

Community
  • 1
  • 1
user3483203
  • 50,081
  • 9
  • 65
  • 94
  • 5
    Hmmm, "Python only prints a decimal approximation to the true decimal value of the binary _approximation_ stored by the machine" seems amiss. The value in the binary float I'd call exact, not a binary _approximation_. "...to the true decimal value of the binary stored by the machine" sounds clearer. The way the float was assigned may have incurred some rounding, yet the stored value itself is exact. – chux - Reinstate Monica Aug 07 '18 at 15:20
  • 1
    @chux I agree, the documentation seems a bit off in that regard. – user3483203 Aug 11 '18 at 03:35
3

I do not believe this exists in the python language spec. However, the cpython implementation does specify it. The float_repr() function, which turns a float into a string, eventually calls a helper function with the 'r' formatter, which eventually calls a utility function that hardcodes the format to what comes down to format(float, '.16g'). That code can be seen here. Note that this is for python3.6.

>>> import math
>>> str(math.pi*4)
12.5663706144

giving the maximum number of signification digits (both before and after the decimal) at 16. It appears that in the python2.7 implementation, this value was hardcoded to .12g. As for why this happened (and is somewhat lacking documentation, can be found here.)

So if you are trying to get how long a number will be formatted when printed, simply get it's length with .12g.

def len_when_displayed(n):
     return len(format(n, '.12g'))
modesitt
  • 7,052
  • 2
  • 34
  • 64
  • 1
    How does `'.16g'` equate to 12 decimal places? – bphi Aug 07 '18 at 14:04
  • 4
    @modesitt: I think you're misinterpreting the code. In Python versions >= 3.2 and Python 2.7, on most machines, `float_repr` doesn't use a fixed number of significant digits at all. There's no `.16g` anywhere, and never has been (in Python 2.6 and earlier, `.17g` was used). `repr` returns the shortest string of decimal digits that's guaranteed to round back to the original float under round-ties-to-even. You're right about the .12g being hardcoded for Python `str` (not `repr`) for Python 2.x, though. – Mark Dickinson Aug 08 '18 at 07:27
3

Well, if you're looking for a pure python way of accomplishing this, you could always use something like,

len(str(.12345678901234567890).split('.')[1])
>>>> 12

I couldn't find it in the documentation and will add it here if I do, but this is a work around that can at least always return the length of precision if you want to know before hand.

As you said, it always seems to be 12 even when feeding bigger floating-points.


From what I was able to find, this number can be highly variable and in these cases, finding it empirically seems to be the most reliable way of doing it. So, what I would do is define a simple method like this,

def max_floating_point():
    counter = 0
    current_length = 0
    str_rep = '.1'
    while(counter <= current_length):
        str_rep += '1'
        current_length = len(str(float(str_rep)).split('.')[1])
        counter += 1

    return current_length

This will return you the maximum length representation on your current system,

print max_floating_point()
>>>> 12
scharette
  • 9,437
  • 8
  • 33
  • 67
  • 2
    Why go through the trouble of a loop with a counter when you could just test a single super long float in the first place? Then the function could be a logic-free wrapper for the line of code you started with. – Alex Hurst Aug 14 '18 at 19:54
  • @AlexHurst My goal was to provide a general solution to OP's problem. I mean, what would you consider a _super long float_ ? Not to mention that I'm not sure that the workload of my loop is _trouble_ as you implied. OP could run this utility method once and store the returned value. I would actually argue that it represent a negligible computation. – scharette Aug 18 '18 at 00:47
0

By looking at the output of random numbers converted, I have been unable to understand how the length of the str() is determined, e.g. under Python 3.6.6:

>>> str(.123456789123456789123456789)
'0.12345678912345678'
>>> str(.111111111111111111111111111)
'0.1111111111111111'

You may opt for this code that actually simulates your real situation:

import random
maxdec=max(map(lambda x:len(str(x)),filter(lambda x:x>.1,[random.random() for i in range(99)])))-2

Here we are testing the length of ~90 random numbers in the (.1,1) open interval after conversion (and deducing the 0. from the left, hence the -2). Python 2.7.5 on a 64bit linux gives me 12, and Python 3.4.8 and 3.6.6 give me 17.

tif
  • 1,424
  • 10
  • 14