Python f-string surprising results on floats - python

I am trying to format float numbers in a fixed point notation: x.xxx, three digits following the decimal point regardless of the value of the number. I am getting surprising results. The first in particular would suggest that it is giving me three significant places rather than three digits after the decimal point. How do I tell it what I really want?
>>> print(f"{.0987:5.03}")
0.0987
*expected: 0.099*
>>> print(f"{0.0:05.03}")
000.0
*expected: 0.000*
>>> print(f"{0.0:5.3}")
0.0

# added "3f" to specify decimals places
print(f"{.0987:5.3f}")
#expected: 0.099*
print(f"{0.9687:05.3f}")
#expected: 0.000*
print(f"{0.0:5.3f}")

Related

Python decimal.Decimal producing result in scientific notation

I'm dividing a very long into much smaller number. Both are of type decimal.Decimal().
The result is coming out in scientific notation. How do I stop this? I need to print the number in full.
>>> decimal.getcontext().prec
50
>>> val
Decimal('1000000000000000000000000')
>>> units
Decimal('1500000000')
>>> units / val
Decimal('1.5E-15')
The precision is kept internally - you just have to explicitly call for the number of decimal places you want at the point you are exporting your decimal value to a string.
So, if you are going a print, or inserting the value in an HTML template, the first step is to use the string format method (or f-strings), to ensure the number is encompassed:
In [29]: print(f"{units/val:.50f}")
0.00000000000000150000000000000000000000000000000000
Unfortunatelly, the string-format minilanguage has no way to eliminate by itself the redundant zeroes on the right hand side. (the left side can be padded with "0", " ", custom characters, whatever one want, but all the precision after the decimal separator is converted to trailing 0s).
Since finding the least significant non-zero digit is complicated - otherwiser we could use a parameter extracted from the number instead of the "50" for precision in the format expression, the simpler thing is to remove those zeros after formatting take place, with the string .rstrip method:
In [30]: print(f"{units/val:.50f}".rstrip("0"))
0.0000000000000015
In short: this seems to be the only way to go: in all interface points, where the number is leaving the core to an output where it is representd as a string, you format it with an excess of precision with the fixed point notation, and strip out the tailing zeros with f-string:
return template.render(number=f"{number:.50f}".rstrip("0"), ...)
Render the decimal into a formatted string with a float type-indicator {:,f}, and it will display just the right number of digits to express the whole number, regardless of whether it is a very large integer or a very large decimal.
>>> val
Decimal('1000000000000000000000000')
>>> units
Decimal('1500000000')
>>> "{:,f}".format(units / val)
'0.0000000000000015'
# very large decimal integer, formatted as float-type string, appears without any decimal places at all when it has none! Nice!
>>> "{:,f}".format(units * val)
'1,500,000,000,000,000,000,000,000,000,000,000'
You don't need to specify the decimal places. It will display only as many as required to express the number, omitting that trail of useless zeros that appear after the final decimal digit when the decimal is shorter than a fixed format width. And you don't get any decimal places if the number has no fraction part.
Very large numbers are therefore accommodated without having to second guess how large they will be. And you don't have to second guess whether they will be have decimal places either.
Any specified thousands separator {:,f} will likewise only have effect if it turns out that the number is a large integer instead of a long decimal.
Proviso
Decimal(), however, has this idea of significant places, by which it will add trailing zeros if it thinks you want them.
The idea is that it intelligently handles situations where you might be dealing with currency digits such as £ 10.15. To use the example from the documentation:
>>> decimal.Decimal('1.30') + decimal.Decimal('1.20')
Decimal('2.50')
It makes no difference if you format the Decimal() - you still get the trailing zero if the Decimal() deems it to be significant:
>>> "{:,f}".format( decimal.Decimal('1.30') + decimal.Decimal('1.20'))
'2.50'
The same thing happens (perhaps for some good reason?) when you treat thousands and fractions together:
>>> decimal.Decimal(2500) * decimal.Decimal('0.001')
Decimal('2.500')
Remove significant trailing zeros with the Decimal().normalize() method:
>>> (2500 * decimal.Decimal('0.001')).normalize()
Decimal('2.5')

Understanding sys.float_info for maximum floats in python

I'm trying to understand the information given by sys.float_info to understand what the maximum floats in Python are. On my computer, this gives me the following:
>>> import sys
>>> sys.float_info
sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1)
The docs give the following example (https://docs.python.org/3/library/sys.html#sys.float_info):
>>> import sys
>>> sys.float_info.dig
15
>>> s = '3.14159265358979' # decimal string with 15 significant digits
>>> format(float(s), '.15g') # convert to float and back -> same value
'3.14159265358979'
>>> s = '9876543211234567' # 16 significant digits is too many!
>>> format(float(s), '.16g') # conversion changes value
'9876543211234568'
However, the following example works just fine for me, even though it also has 16 significant digits (?):
>>> s = '.9876543211234567'
>>> format(float(s), '.16g')
'0.9876543211234567'
Also, sys.float_info.min yields 2.2250738585072014e-308 which is is obviously a lot smaller and also has 17 significant digits, if I'm correct? How does that work when sys.float_info.dig = 15? Am I confusing something here?
How does sys.float_info.dig = 15 relate to the attributes sys.float_info.mant_dig and sys.float_info.radix? As far as I understand, if I were to represent some decimal number as a base-2 number (since sys.float_info.radix = 2), does sys.float_info.mant_dig then give me the maximum integer of the mantissa?
Sorry if I'm confusing some things, maybe I'm not firm enough the mathematical basics here. Any help is much appreciated!
However, the following example works just fine for me, even though it also has 16 significant digits (?):
The documentation says “If X, then Y,” where X is:
A decimal numeral with up to 15 significant digits is converted to your Python implementation’s float and back to the nearest decimal numeral with at most 15 significant digits.
and Y is:
The number represented by the original numeral equals the resulting numeral.
That says only what happens if X is true. It does not say what happens if X is false. You have given us a situation where Y is true (you got back the original number) and complained that X is false. That is consistent with the documentation.
Also, sys.float_info.min yields 2.2250738585072014e-308 which is is obviously a lot smaller and also has 17 significant digits, if I'm correct? How does that work when sys.float_info.dig = 15? Am I confusing something here?
Every floating-point datum other than a NaN (Not a Number) represents one number exactly. It is exactly that number, regardless of how many decimal digits are required to express that number. sys_float_info.dig tells you a property about how fine the floating-point representation is. It tells you the floating-point representation is so fine, meaning that the numbers it does represent are so close together, that they can be used to distinguish between 15-digit decimal numerals. That just tells you how close the representable numbers are to each other. It does not tell you how many decimal digits it takes to exactly express any of those numbers.
In fact, the smallest positive number representable in your Python implementation’s floating-point format is 2−1074, which is 4.940656458412465441765687928682213723650598026143247644255856825006755072702087518652998363616359923797965646954457177309266567103559397963987747960107818781263007131903114045278458171678489821036887186360569987307230500063874091535649843873124733972731696151400317153853980741262385655911710266585566867681870395603106249319452715914924553293054565444011274801297099995419319894090804165633245247571478690147267801593552386115501348035264934720193790268107107491703332226844753335720832431936092382893458368060106011506169809753078342277318329247904982524730776375927247874656084778203734469699533647017972677717585125660551199131504891101451037862738167250955837389733598993664809941164205702637090279242767544565229087538682506419718265533447265625•10−324.
You can learn more about the specifics of floats for your runtime environment, by checking the value of sys.float_info. This will also tell you what's the largest and smallest number that can be represented with them.

What is difference between {:.4e} and {:2.4} in Python scientific notation

I can't quite understand what the difference is between the two print statements below for the number I am trying to express in scientific notation. I thought the the bottom one is supposed to allow 2 spaces for the printed result, and move the decimal place 4 times, but the result I get does not corroborate that understanding. As far as the first one, What does 4e mean?
>>> print('{:.4e}'.format(3454356.7))
3.4544e+06
>>> print('{:2.4}'.format(3454356.7))
3.454e+06
All help greatly appreciated.
In the first example , 4e means, 4 decimal places in scientific notation. You can come to know that by doing
>>> print('{:.4e}'.format(3454356.7))
3.4544e+06
>>> print('{:.5e}'.format(3454356.7))
3.45436e+06
>>> print('{:.6e}'.format(3454356.7))
3.454357e+06
In the second example, .4 , means 4 significant figures. And 2 means to fit the whole data into 2 characters
>>> print('{:2.4}'.format(3454356.7))
3.454e+06
>>> print('{:2.5}'.format(3454356.7))
3.4544e+06
>>> print('{:2.6}'.format(3454356.7))
3.45436e+06
Testing with different value of 2
>>> print('-{:20.6}'.format(3454356.7))
- 3.45436e+06
You can learn more from the python documentation on format
If you want to produce a float, you will have to specify the float type:
>>> '{:2.4f}'.format(3454356.7)
'3454356.7000'
Otherwise, if you don’t specify a type, Python will choose g as the type for which precision will mean the precision based on its significant figures, the digits before and after the decimal point. And since you have a precision of 4, it will only display 4 digits, falling back to scientific notation so it doesn’t add false precision.
The precision is a decimal number indicating how many digits should be displayed after the decimal point for a floating point value formatted with 'f' and 'F', or before and after the decimal point for a floating point value formatted with 'g' or 'G'. For non-number types the field indicates the maximum field size - in other words, how many characters will be used from the field content. The precision is not allowed for integer values.
(source, emphasis mine)
Finally, note that the width (the 2 in above format string) includes the full width, including digits before the decimal point, digits after it, the decimal point itself, and the components of the scientific notation. The above result would have a width of 12, so in this case, the width of the format string is simply ignored.

Python extending decimals

Why is it that python sometimes extends numbers and is there a way to stop it? For example 1.7 may turn into 1.70000005.
Specifically I'm encountering this while taking in a list of floats and trying to populate a new list.
newList = []
for value in myList:
print value
newList.append(value)
return newList
The console will print out numbers containing no more than 2 decimal places while the newList being returned will have 17 places and oftentimes include a non-zero in the last digit. It does this even if I attempt to round(value,2) inside the loop.
That's just the representation! Actually, the contents of the list is still the same.
To show it properly, you can format it in a string:
print("{.17f}".format(my_float_value))
Alternatively, you can use the decimals module:
>>> import decimal
>>> my_float = decimal.Decimal("0.2342134235")
>>> my_float
Decimal('0.2342134235')
Hope this helps!
CPython uses C doubles, and C doubles are typically implemented in hardware for speed.
Hardware floating point is precise to only a limited number of digits. Also, it's stored base 2, and we think mostly in base 10, and some numbers that have a finite expression in base 10, don't have one in base 2, and vice versa.
So you should:
Never compare a floating point value for equality to another.
Instead, subtract them, take the absolute value, and compare that
result to a small positive number like 1e-8.
Round your floating point values to a palatable number of places
after the decimal point, using string formatting or the
http://docs.python.org/3/library/functions.html#round function.
You can use the decimal module to get caller-specified precision. Or if you have rational values, you can use the fractions module.

Is there a more readable or Pythonic way to format a Decimal to 2 places?

What the heck is going on with the syntax to fix a Decimal to two places?
>>> from decimal import Decimal
>>> num = Decimal('1.0')
>>> num.quantize(Decimal(10) ** -2) # seriously?!
Decimal('1.00')
Is there a better way that doesn't look so esoteric at a glance? 'Quantizing a decimal' sounds like technobabble from an episode of Star Trek!
Use string formatting:
>>> from decimal import Decimal
>>> num = Decimal('1.0')
>>> format(num, '.2f')
'1.00'
The format() function applies string formatting to values. Decimal() objects can be formatted like floating point values.
You can also use this to interpolate the formatted decimal value is a larger string:
>>> 'Value of num: {:.2f}'.format(num)
'Value of num: 1.00'
See the format string syntax documentation.
Unless you know exactly what you are doing, expanding the number of significant digits through quantisation is not the way to go; quantisation is the privy of accountancy packages and normally has the aim to round results to fewer significant digits instead.
Quantize is used to set the number of places that are actually held internally within the value, before it is converted to a string. As Martijn points out this is usually done to reduce the number of digits via rounding, but it works just as well going the other way. By specifying the target as a decimal number rather than a number of places, you can make two values match without knowing specifically how many places are in them.
It looks a little less esoteric if you use a decimal value directly instead of trying to calculate it:
num.quantize(Decimal('0.01'))
You can set up some constants to hide the complexity:
places = [Decimal('0.1') ** n for n in range(16)]
num.quantize(places[2])

Categories

Resources