Antti Haapala Antti Haapala - 1 year ago 181
Python Question

Convert float to string without scientific notation and false precision

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

, not in scientific notation, yet I'd want to keep the 15.7 decimal digits of precision and no more.

It is well-known that the
of a
is written in scientific notation if the exponent is greater than 15, or less than -4:

>>> n = 0.000000054321654321
>>> n
5.4321654321e-08 # scientific notation

is used, the resulting string again is in scientific notation:

>>> str(n)

It has been suggested that I can use
flag and sufficient precision to get rid of the scientific notation:

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

It works for that number, though it has some extra trailing zeroes. But then the same format fails for
, which gives decimal digits beyond the actual machine precision of float:

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

And if my number is
, using
would still lose relative precision:

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

Thus these approaches do not match my requirements.

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
on Python 3)
, but always using the decimal format, not the scientific notation.

That is, a function or operation that for example converts the float value
to string
and formats the float value

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.


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 floats is the same as with repr; and with f flag there are 6 fractional digits by default:

>>> format(0.0000000005, 'f')

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 floats 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)
>>> float_to_str(0.00000005)
>>> float_to_str(420000000000000000.0)
>>> float_to_str(0.000000000123123123123123123123)

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.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download