Float formatting in Python - python

While doing this exercise:
>>> amount = 24.325
>>> print("%7f" % amount)
>>> 24.325000
I didn't understand why instead of printing ' 24.325' (whith 1 space before the number) it just added three '0' and didn't move it towards the right at all.
So I thought that maybe, when you don't specify the precision, Python adds '0' until the number has at least 6 digits after the decimal point (if it doesn't already have them) and THAN takes in consideration the width I set (in this case, 7) and adds the needed spaces. In the exercise, with the extra '0's, it ends up having 9 digits, so it didn't add any. Is my hypothesis correct?
The question is, why 6 digits after the decimal point? Is it something that Python does by default?
Thank you.

The python docs exactly describe how the formatting operator % works.
In particular, you can have a minimum field with an a precision.
The former defines the minimum length of your resulting string, the latter gives the number of digits after the decimal point.
As the default precision is 6 for %f, you get what you get.

Related

Almost correct output, but not quite right. Math help in Python

I am trying to match an expected output of "13031.157014219536" exactly, and I have attempted 3 times to get the value with different methods, detailed below, and come extremely close to the value, but not close enough. What is happening in these code snippets that it causing the deviation? Is it rounding error in the calculation? Did I do something wrong?
Attempts:
item_worth = 8000
years = range(10)
for year in years:
item_worth += (item_worth*0.05)
print(item_worth)
value = item_worth * (1 + ((0.05)**10))
print(value)
cost = 8000
for x in range(10):
cost *= 1.05
print(cost)
Expected Output:
13031.157014219536
Actual Outputs:
13031.157014219529
13031.157014220802
13031.157014219538
On most machine, floats are represented by fp64, or double floats.
You can check the precision of those for your number that way (not a method to be used for real computation. Just out of curiosity):
import struct
struct.pack('d', 13031.157014219536)
# bytes representing that number in fp64 b'\xf5\xbc\n\x19\x94s\xc9#'
# or, in a more humanely understandable way
struct.unpack('q', struct.pack('d', 13031.157014219536))[0]
# 4668389568658717941
# That number has no meaning, except that this integer is represented by the same bytes as your float.
# Now, let's see what float is "next in line"
struct.unpack('d', struct.pack('q', 4668389568658717941+1))[0]
# 13031.157014219538
Note that this code works on most machine, but is not reliable. First of all, it relies on the fact that significant bits are not just all 1. Otherwise, it would give a totally unrelated number. Secondly, it makes assumption that ints are LE. But well, it gave me what I wanted.
That is the information that the smallest number bigger than 13031.157014219536 is 13031.157014219538.
(Or, said more accurately for this kind of conversation: the smallest float bigger than 13031.157014219536 whose representation is not the same as the representation of 13031.157014219536 has the same representation as 13031.157014219538)
So, my point is you are flirting with the representation limit. You can't expect the sum of 10 numbers to be more accurate.
I could also have said that saying that the biggest power of 2 smaller than your number is 8192=2¹³.
So, that 13 is the exponent of your float in its representation. And this you have 53 significant bits, the precision of such a number is 2**(13-53+1) = 1.8×10⁻¹² (which is indeed also the result of 13031.157014219538-13031.157014219536). Hence the reason why in decimal, 12 decimal places are printed. But not all combination of them can exist, and the last one is not insignificant, but not fully significant neither.
If your computation is the result of the sum of 10 such numbers, you could even have an error 10 times bigger than your last result the right to complain :D

Taking just two decimals without rounding it

Basically, I have a list of float numbers with too many decimals. So when I created a second list with two decimals, Python rounded them. I used the following:
g1= ["%.2f" % i for i in g]
Where g1 is the new list with two decimals, but rounded, and g is the list with float numbers.
How can I make one without rounding them?
I'm a newbie, btw. Thanks!
So, you want to truncate the numbers at the second digit?
Beware that rounding might be the better and more accurate solution anyway.
If you want to truncate the numbers, there are a couple of ways - one of them is to multiply the number by 10 elevated to the number of desired decimal places (100 for 2 places), apply "math.floor", and divide the total back by the same number.
However, as internal floating point arithmetic is not base 10, you'd risk getting more decimal places on the division to scale down.
Another way is to create a string with 3 digits after the "." and drop the last one - that'd be rounding proof.
And again, keep in mind that this converts the numbers to strings - what should be done for presentation purposes only. Also, "%" formatting is quite an old way to format parameters in a string. In modern Python, f-strings are the preferred way:
g1 = [f"{number:.03f}"[:-1] for number in g]
Another, more correct way, is, of course, treat numbers as numbers, and not play tricks on adding or removing digits on it. As noted in the comments, the method above would work for numbers like "1.227", that would be kept as "1.22", but not for "2.99999", which would be rounded to "3.000" and then truncated to "3.00".
Python has the decimal modules, which allows for arbitrary precision of decimal numbers - which includes less precision, if needed, and control of the way Python does the rounding - including rounding towards zero, instead of the nearest number.
Just set the decimal context to the decimal.ROUND_DOWN strategy, and then convert your numbers using either the round built-in (the exact number of digits is guaranteed, unlike using round with floating point numbers), or just do the rounding as part of the string formatting anyway. You can also convert your floats do Decimals in the same step:
from decimals import Decimal as D, getcontext, ROUND_DOWN
getcontext().rounding = ROUND_DOWN
g1 = [f"{D(number):.02f}" for number in g]
Again - by doing this, you could as well keep your numbers as Decimal objects, and still be able to perform math operations on them:
g2 = [round(D(number, 2)) for number in g]
Here is my solution where we don't even need to convert the number's to string to get the desired output:
def format_till_2_decimal(num):
return int(num*100)/100.0
g = [-5.427926, -12.222018, 7.214379, -16.771845, -6.1441464, 10.1383295, 14.740516, 5.9209185, -9.740783, -10.098338]
formatted_g = [format_till_2_decimal(num) for num in g]
print(formatted_g)
Hope this solution helps!!
Here might be the answer you are looking for:
g = [-5.427926, -12.222018, 7.214379, -16.771845, -6.1441464, 10.1383295, 14.740516, 5.9209185, -9.740783, -10.098338]
def trunc(number, ndigits=2):
parts = str(number).split('.') # divides number into 2 parts. for ex: -5, and 4427926
truncated_number = '.'.join([parts[0], parts[1][:ndigits]]) # We keep this first part, while taking only 2 digits from the second part. Then we concat it together to get '-5.44'
return round(float(truncated_number), 2) # This should return a float number, but to make sure it is roundded to 2 decimals.
g1 = [trunc(i) for i in g]
print(g1)
[-5.42, -12.22, 7.21, -16.77, -6.14, 10.13, 14.74, 5.92, -9.74, -10.09]
Hope this helps.
Actually if David's answer is what you are looking for, it can be done simply as following:
g = [-5.427926, -12.222018, 7.214379, -16.771845, -6.1441464, 10.1383295, 14.740516, 5.9209185, -9.740783, -10.098338]
g1 = [("%.3f" % i)[:-1] for i in g]
Just take 3 decimals, and remove the last chars from the result strings. (You may convert the result to float if you like)

How can I convert a float in a str without losing significant figures?

I want to convert the number 0.054000 in a str, but when I write srt(0.054000) I get '0.054'. I need to get '0.054000'. How can I do it?
I have a data file with numbers as my example (0.054000). I need to count the digits of each number. I don't know how to read that number in a way that I count seven digits, for instance.
I think that Dan Patterson's method is the only way to do it reliably - python makes no differentiation between .0054 and .054000: e.g.
>>> .0054 is .0054000
True
Thus you will probably have to simply specify the number of digits you have in sig figs, either using his method or (str(.0054000) + "0"*number_of_sig_figs).
A format specifier starts with a colon and then may contain any of the terms shown
in brackets in the following (each of the terms is optional)
: [[fill]align] [sign] [#] [0] [width] [,] [.precision] [type]
A brief description of the [.precision] is provided below.
.precision: Maximum number of characters for strings (integer); number of digits of
precision for floats. For f, F, e, and E type specifiers this is the number
of digits to the right of the decimal point.
We can use this to specify the precision of our float value:
a=0.540000
print("{:06f}".format(a))
This gives the desired output:
0.540000
Hope this was helpful!

How to make Python format floats with certain amount of significant digits?

I want my Python (2.4.3) output numbers to have a certain format. Specifically, if the number is a terminating decimal with <= 6 significant digits, show it all. However, if it has > 6 significant digits, then output only 6 significant digits.
"A" shows how Python is writing the floats. "B" shows how I want them written. How can I make Python format my numbers in that way?
A:
10188469102.605597
5.5657188485
3.539
22.1522612479
0
15.9638450858
0.284024
7.58096703786
24.3469152383
B:
1.01885e+10
5.56572
3.539
22.1523
0
15.9638
0.284024
7.58097
24.3469
You'll want the g modifier for format that drops insignificant zeroes;
>>> "{0:.6g}".format(5.5657188485)
'5.56572'
>>> "{0:.6g}".format(3.539)
'3.539'
Sorry, my update also includes the fact that I am restricted to using
Python 2.4.3, which does not have format() function.
The format specifiers work even without the .format() function:
>>> for i in a:
... print '%.6g' % (i,)
...
1.01885e+10
5.56572
3.539
22.1523
0
15.9638
0.284024
7.58097
24.3469
There is a way to retain trailing zeros so that it consistently shows the number of significant digits. Not exactly what OP wanted, but probably useful to many.
a = [10188469102.605597,5.5657188485,3.539,22.1522612479,0,15.9638450858,0.284024,7.58096703786,24.3469152383]
for i in a:
print("{:#.6g}".format(i))
Output
1.01885e+10
5.56572
3.53900
22.1523
0.00000
15.9638
0.284024
7.58097
24.3469
Note that this will only work with the format function and not with % operator.
According to the docs:
The '#' option causes the “alternate form” to be used for the conversion. The alternate form is defined differently for different types. This option is only valid for integer, float, complex and Decimal types.
'g': General format ... insignificant trailing zeros are removed from the significand, and the decimal point is also removed if there are no remaining digits following it, unless the '#' option is used.
try this way
a=[10188469102.605597,5.5657188485,3.539,22.1522612479,0,15.9638450858,0.284024,7.58096703786,24.3469152383]
for i in a:
if i >100:
print '{:.6e}'.format(i)
else:
print '{:.6f}'.format(i)
for lower version of python
for i in a:
if i >100:
print '%6e'%i
else:
print '%6f'%i
output
1.018847e+10
5.565719
3.539000
22.152261
0.000000
15.963845
0.284024
7.580967
24.346915

How do I preserve leading zeros in Python integers for string formatting

I have what I believe to be a simple question. I have the following code and the output is not what I expect:
import socket
import time
r=031508.78
h=373309.9
z=0
mes='"TSRA %s %s %s\r\n"'%(r,h,z)
print mes
My problem is that r prints as '31508.78' and I want '031508.78'. How I get preserve the leading zero?
The 0 in front of the number is not important for python, because numbers have no leading 0s. You can use:
print '%09.2f'%r
I prefer the new string format mini language. I find it more intuitive.
r = 31508.78
h = 373309.9
z = 0
print '"TSRA {0: 011.3f} {1: 011.3f} {2}"\r\n'.format(r, h, z)
print '{0: 011.3f}\n{1: 011.3f}\n{2: 011.3f}'.format(r, h, z)
{0: 011.3f} 0: indicates the first item (r). 011 tells the formatter that there will be 11 characters including the decimal point and a place holder for a negative sign. By placing a 0 in front the formatter will pad the space with leading zeros. Finally, .3f indicates the precision and a fixed number of places after the decimal point.
Yields:
"TSRA 031508.780 373309.900 0"
031508.780
373309.900
000000.000
I don't think there is any way to store a number with a leading zero in python.
This leaves you with two ways:
1) Make r a string i.e. r="031408.78"
I think this is the best method for you as you can do all the arithmetic you want to do (using the implicit typecasting of python) and see the number appended with a 0 wherever you use it.
2) If you simply want to print the number with leading zeros consider using this
print "%09.2f' % r #for this you need the length of the number
For Python, there is no difference in the following things:
31508.78
031508.78
0000000031508.78
31508.7800000000000
0000000000000031508.7800000000000
Those are all valid representations of the exact same number, which is canonically represented by 31508.78, which is essentially just a convention. Different representations are possible too, and actually, the number is represented in a way that’s described by the IEEE-754 standard.
So, when you want that leading zero, you have to tell Python explicitely to put it there because it simply doesn’t know about it. You just used one of many valid representations to specify that one number which default string representation does not include leading zeroes.
The best way to do this is using string formatting, which comes in two flavors. The old style flavor, using the % operator, and the newer str.format function:
>>> '%09.2f' % 31508.78
'031508.78'
>>> '{:09.2f}'.format(31508.78)
'031508.78'
Or, if you don’t actually want a number, you could also just specify the number as a string to begin with, which of course keeps the leading zero.

Categories