Antti Haapala - 1 year ago 128

Python Question

I want to print some floating point numbers so that they're always written in decimal form (e.g.

`12345000000000000000000.0`

`0.000000000000012345`

It is well-known that the

`repr`

`float`

`>>> n = 0.000000054321654321`

>>> n

5.4321654321e-08 # scientific notation

If

`str`

`>>> str(n)`

'5.4321654321e-08'

It has been suggested that I can use

`format`

`f`

`>>> format(0.00000005, '.20f')`

'0.00000005000000000000'

It works for that number, though it has some extra trailing zeroes. But then the same format fails for

`.1`

`>>> format(0.1, '.20f')`

'0.10000000000000000555'

And if my number is

`4.5678e-20`

`.20f`

`>>> format(4.5678e-20, '.20f')`

'0.00000000000000000005'

Thus

This leads to the question: what is the easiest and also well-performing way to print arbitrary floating point number in decimal format, having the same digits as in

`repr(n)`

`str(n)`

That is, a function or operation that for example converts the float value

`0.00000005`

`'0.00000005'`

`0.1`

`'0.1'`

`420000000000000000.0`

`'420000000000000000.0'`

`420000000000000000`

`-4.5678e-5`

`'-0.000045678'`

After the bounty period: It seems that there are at least 2 viable approaches, as Karin demonstrated that using string manipulation one can achieve significant speed boost compared to my initial algorithm on Python 2.

Thus,

- If performance is important and Python 2 compatibility is required; or if the module cannot be used for some reason, then Karin's approach using string manipulation is the way to do it.
`decimal`

- On Python 3, my somewhat shorter code will also be faster.

Since I am primarily developing on Python 3, I will accept my own answer, and shall award Karin the bounty.

Answer Source

Unfortunately it seems that not even the new-style formatting with `float.__format__`

supports this. The default formatting of `float`

s is the same as with `repr`

; and with `f`

flag there are 6 fractional digits by default:

```
>>> format(0.0000000005, 'f')
'0.000000'
```

However there is a hack to get the desired result - not the fastest one, but relatively simple:

- first the float is converted to a string using
`str()`

or`repr()`

- then a new
`Decimal`

instance is created from that string. `Decimal.__format__`

supports`f`

flag which gives the desired result, and, unlike`float`

s it prints the actual precision instead of default precision.

Thus we can make a simple utility function `float_to_str`

:

```
import decimal
# create a new context for this task
ctx = decimal.Context()
# 20 digits should be enough for everyone :D
ctx.prec = 20
def float_to_str(f):
"""
Convert the given float to a string,
without resorting to scientific notation
"""
d1 = ctx.create_decimal(repr(f))
return format(d1, 'f')
```

Care must be taken to not use the global decimal context, so a new context is constructed for this function. This is the fastest way; another way would be to use `decimal.local_context`

but it would be slower, creating a new thread-local context and a context manager for each conversion.

This function now returns the string with all possible digits from mantissa, rounded to the shortest equivalent representation:

```
>>> float_to_str(0.1)
'0.1'
>>> float_to_str(0.00000005)
'0.00000005'
>>> float_to_str(420000000000000000.0)
'420000000000000000'
>>> float_to_str(0.000000000123123123123123123123)
'0.00000000012312312312312313'
```

The last result is rounded at the last digit

As @Karin noted, `float_to_str(420000000000000000.0)`

does not strictly match the format expected; it returns `420000000000000000`

without trailing `.0`

.