Python float to string formatting

Convert float to string in positional format (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. 12345000000000000000000.0 or 0.000000000000012345 , not in scientific notation, yet I’d want to the result to have the up to ~15.7 significant figures of a IEEE 754 double, and no more. What I want is ideally so that the result is the shortest string in positional decimal format that still results in the same value when converted to a float . It is well-known that the repr of a float 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 

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

>>> 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 , which gives decimal digits beyond the actual machine precision of float:

>>> format(0.1, '.20f') '0.10000000000000000555' 
>>> format(4.5678e-20, '.20f') '0.00000000000000000005' 
  • If performance is important and Python 2 compatibility is required; or if the decimal module cannot be used for some reason, then Karin’s approach using string manipulation is the way to do it.
  • 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.

Project for a rainy day: add a low-level library function to Python (possibly in the sys module) that returns the «raw» binary-to-decimal conversion result for a given finite float (i.e., string of digits, decimal exponent, sign). That would give people the freedom to format as they saw fit.

Short answer: no, there isn’t an easier way to do this; at least, not one that I’m aware of, and that also gives decently precise results. (Any solution that involves first pre-processing the number by scaling by powers of 10 is going to risk introducing numerical errors.)

since you required precision is 15.7 decimal digits ~= 16 decimal digits of precision why your examples request precision 20?

7 Answers 7

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.
Читайте также:  Expand text html css

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 .

Why don’t you use decimal.localcontext ? with localcontext() as ctx: ctx.prec = 20; d1 = Decimal(str(f))

I see precision loss in the output for 0.000000000123123123123123123123 — the float_to_str output cuts off at only 12 digits of precision, not enough to reconstruct the original float.

@user2357112 good catch. You’re using Python 2; in Python 2 str only has 12 digits of precision while repr uses the Python 3 compatible algorithm. In Python 3, both forms are similar, thus the confusion. I changed my code to use repr .

If you are satisfied with the precision in scientific notation, then could we just take a simple string manipulation approach? Maybe it’s not terribly clever, but it seems to work (passes all of the use cases you’ve presented), and I think it’s fairly understandable:

def float_to_str(f): float_string = repr(f) if 'e' in float_string: # detect scientific notation digits, exp = float_string.split('e') digits = digits.replace('.', '').replace('-', '') exp = int(exp) zero_padding = '0' * (abs(int(exp)) - 1) # minus 1 for decimal point in the sci notation sign = '-' if f < 0 else '' if exp >0: float_string = '<><><>.0'.format(sign, digits, zero_padding) else: float_string = '<>0.<><>'.format(sign, zero_padding, digits) return float_string n = 0.000000054321654321 assert(float_to_str(n) == '0.000000054321654321') n = 0.00000005 assert(float_to_str(n) == '0.00000005') n = 420000000000000000.0 assert(float_to_str(n) == '420000000000000000.0') n = 4.5678e-5 assert(float_to_str(n) == '0.000045678') n = 1.1 assert(float_to_str(n) == '1.1') n = -4.5678e-5 assert(float_to_str(n) == '-0.000045678') 

Performance:

I was worried this approach may be too slow, so I ran timeit and compared with the OP’s solution of decimal contexts. It appears the string manipulation is actually quite a bit faster. Edit: It appears to only be much faster in Python 2. In Python 3, the results were similar, but with the decimal approach slightly faster.

  • Python 2: using ctx.create_decimal() : 2.43655490875
  • Python 2: using string manipulation: 0.305557966232
  • Python 3: using ctx.create_decimal() : 0.19519368198234588
  • Python 3: using string manipulation: 0.2661344590014778
from timeit import timeit CODE_TO_TIME = ''' float_to_str(0.000000054321654321) float_to_str(0.00000005) float_to_str(420000000000000000.0) float_to_str(4.5678e-5) float_to_str(1.1) float_to_str(-0.000045678) ''' SETUP_1 = ''' 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') ''' SETUP_2 = ''' def float_to_str(f): float_string = repr(f) if 'e' in float_string: # detect scientific notation digits, exp = float_string.split('e') digits = digits.replace('.', '').replace('-', '') exp = int(exp) zero_padding = '0' * (abs(int(exp)) - 1) # minus 1 for decimal point in the sci notation sign = '-' if f < 0 else '' if exp >0: float_string = '<><><>.0'.format(sign, digits, zero_padding) else: float_string = '<>0.<><>'.format(sign, zero_padding, digits) return float_string ''' print(timeit(CODE_TO_TIME, setup=SETUP_1, number=10000)) print(timeit(CODE_TO_TIME, setup=SETUP_2, number=10000)) 

Источник

Читайте также:  Java json rpc spring boot

Format a Floating Number to String in Python

Format a Floating Number to String in Python

  1. Format Floating Numbers to a Fixed Width Using Format Specification and the format() Method
  2. Format Floating Numbers in a List to a Fixed Width
  3. Format a Floating Number to Fix Width Using the % Operator in Python
  4. Format a Floating Number to a Fix Width Using round() Function in Python

This article will introduce some methods to format a floating number to a fixed width in Python.

Format Floating Numbers to a Fixed Width Using Format Specification and the format() Method

Here, we will use the normal formatting process via format specification to fix the width of floating numbers.

We will create a variable num and assign a floating number to it. Then, we will print the floating number in the format we need. We are going to use the format() function to get our result.

#python 3.x num = 0.02893574 print (" ".format(num)) 

Here, .4f is called format specification, which denotes that the output should show only four places after the decimal. If we used .5f instead, we’d get up to five digits after the decimal point. The empty string before the colon : is placed so that the argument provided to the format() function takes that place. In the above program, the argument we have provided is num . So, whatever value we have in num will be passed to the empty string before the : and formatted accordingly.

In python 3.6, we can also use f’<>‘ to obtain the same output:

#python 3.x num = 0.02893574 print (f'') 

Format Floating Numbers in a List to a Fixed Width

We use similar syntaxes to fix the width of floating numbers up to a specific digit after the decimal.

First, we will create a list with several floating numbers. Then, we will use for loop to take every item from the list and format it accordingly. We will use the variable numbers to represent numbers inside the list. We will print the floating numbers in the list one by one with fixed numbers of digits after the decimal.

#python 3.x list = [18.292164, 52.452189, 999.1212732] for numbers in list:  print(" ".format(numbers)) 

In the above code, each number inside the list is sent one by one inside the for loop. The first element of the list, i.e., list[0] , gets assigned to the variable numbers , and its formatted value, i.e., 18.293 , is printed. It happened because we executed .3f , which represents digits up to three places after the decimal point. Similarly, the second element list[1] and the third list[2] is also passed to the loop as variable numbers and are printed accordingly.

This program runs until all the elements in the list are executed.

The list below contains the same items as the program above. We will use f’<>‘ instead of the format() function in this example.

#python 3.x list = [18.292164, 52.452189, 999.1212732] for numbers in list:  print(f'numbers:9.3f>') 

We can see that when we got the output when we used f’<>‘ . We also got the desired output in an aligned manner. For the same reason, it is generally better to use f’<>‘ if we want to have the floating digits after the decimal aligned.

Format a Floating Number to Fix Width Using the % Operator in Python

We can also set a fixed width for a floating number with the use of %v operator. The code might look similar to the printf() function in C programming.

We will assign a floating number to a variable num and print the value of num with decimal digits up to a fixed width. Notice that while passing the value of num to the print statement with format specifier %.4f , we are using %num . Missing % before num will be a syntax error.

#python 3.x num = 0.02893574 print ('%.4f'%num) 

Here the use of %num has allowed us to print the desired value without any function or string formatting.

Format a Floating Number to a Fix Width Using round() Function in Python

We can also use the round() function to fix the numbers of digits after the decimal point. This function limits the number of digits after the decimal point on the input number. It also rounds off the digit at which limit is set to its upper integral value if the digit is greater than value 5 .

Let’s take a floating-point number and assign it to a variable num . When we print, we will use the round() function to limit the number of digits after the decimal point.

#python 3.x num = 2.37682 print(round(num,3)) 

The syntax for the round function is round(number, digits) . Here the argument number is compulsory while the argument digits is optional. number can also be put as a variable. If nothing is passed to the argument digits , only the integral part of the number is taken as a result.

In the above program, we passed the value 2.37682 to the first argument num , and it was rounded to 3 places after the decimal point because we passed the value 3 to the second argument inside the round() method.

Related Article — Python Float

Copyright © 2023. All right reserved

Источник

Оцените статью