Can Decimal('5E+1') be simply converted to Decimal('50') in Python? - python

Context
We display percentage values to agents in our app without trailing zeros (50% is much easier to quickly scan than is 50.000%), and hitherto we've just used quantize to sort of brute force normalize the value to remove trailing zeros.
This morning I decided to look into using Decimal.normalize instead, but ran into this:
Given the decimal value:
>>> value = Decimal('50.000')
Normalizing that value:
>>> value = value.normalize()
Results in:
>>> value
Decimal('5E+1')
I understand the value is the same:
>>> Decimal('5E+1') == Decimal('50')
True
But from a non-technical user's perspective, 5E+1 is basically meaningless.
Question
Is there a way to convert Decimal('5E+1') to Decimal('50')?
Note
I'm not looking to do anything that would change the value of the Decimal (e.g., removing decimal places altogether), since the value could be e.g., Decimal('33.333'). IOW, don't confuse my 50.000 example as meaning that we're only dealing with whole numbers.

For the purposes of output formatting, you can print your normalized Decimal objects with the f format specifier. (While the format string docs say this defaults to a precision of 6, this does not appear to be the case for Decimal objects.)
>>> print('{:f}%'.format(decimal.Decimal('50.000').normalize()))
50%
>>> print('{:f}%'.format(decimal.Decimal('50.003').normalize()))
50.003%
>>> print('{:f}%'.format(decimal.Decimal('1.23456789').normalize()))
1.23456789%
If for some reason, you really want to make a new Decimal object with different precision, you can do that by just calling Decimal on the f format output, but it sounds like you're dealing with an output format problem, not something you should change the internal representation for.
>>> Decimal('{:f}'.format(Decimal('5E+1')))
Decimal('50')
>>>
>>> Decimal('{:f}'.format(Decimal('50.000').normalize()))
Decimal('50')
>>> Decimal('{:f}'.format(Decimal('50.003').normalize()))
Decimal('50.003')
>>> Decimal('{:f}'.format(Decimal('1.23456789').normalize()))
Decimal('1.23456789')

according to the python 3.9 docs the below is how to do it - https://docs.python.org/3.9/library/decimal.html#decimal-faq
def remove_exponent(d):
return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()

Add Decimal(0) to your result.
Decimal('50.000').normalize()
# Decimal('5E+1')
Decimal('50.000').normalize() + Decimal(0)
# Decimal('50')

Related

Format float with fixed amount of digits python

I would like to format my floats with a fixed amount of digits. Right now I'm doing the following
format="%6.6g"
print(format%0.00215165)
print(format%1.23260)
print(format%145.5655)
But this outputs
0.00215165
1.2326
145.565
I also tried format="%6.6f" but it doesn't really give what I want either...
0.002152
1.232600
145.565500
What would be a good way to format the numbers so that all of them have exactly width 6 (and no spaces) like so ?
0.002152
1.232600
145.5655
This is complicated because you want the precision (number of decimals) to depend on the available space, while the general thrust of floating-point formatting is to make the number of significant digits depend on the available space. To do what you want you need a function that computes the desired number of decimals from the log of the number. There isn't, so far as I know, a built-in function that will do this for you.
def decimals(v):
return max(0, min(6,6-int(math.log10(abs(v))))) if v else 6
This simply takes the log of number and truncates it to int. So 10-99 -> 1, 100-999 -> 2 etc. You then use that
result to work out the precision to which the number needs to be formatted. In practice the
function is more complex because of the corner cases: what to do with negative numbers, numbers that underflow, etc.
For simplicity I've deliberately left your figure of 6 decimals hard-coded 3 times in the function.
Then formatting isn't so hard:
>>> v = 0.00215165
>>> "{0:.{1}f}".format(v, decimals(v))
'0.002152'
>>> v2 = 1.23260
>>> "{0:.{1}f}".format(v2, decimals(v2))
'1.232600'
>>> v3 = 145.5655
>>> "{0:.{1}f}".format(v3, decimals(v3))
'145.5655'
>>> vz = 0e0 # behaviour with zero
>>> "{0:.{1}f}".format(vz, decimals(vz))
'0.000000'
>>> vu = 1e-10 # behaviour with underflow
>>> "{0:.{1}f}".format(vu, decimals(vu))
'0.000000'
>>> vo = 1234567 # behaviour when nearly out of space
>>> "{0:.{1}f}".format(vo, decimals(vo))
'1234567'
>>> voo = 12345678 # behaviour when all out of space
>>> "{0:.{1}f}".format(voo, decimals(voo))
'12345678'
You can use %-notation for this instead of a call to format but it is not very obvious or intuitive:
>>> "%.*f" % (decimals(v), v)
'0.002152'
You don't say what you want done with negative numbers. What this approach does is to take an extra
character to display the minus sign. If you don't want that then you need to reduce the number of
decimals for negative numbers.

How to place a number in the middle of string using python?

suppose a float number x=3.1234. I want to print this number in the middle of the string containing space in the left side and right side of x. string length will be variable. Precision of x will be variable. if string length=10 and precision=2 the output will be " 3.14 " Have any function in python that can return this?
This is really nicely documented at https://docs.python.org/3.6/library/string.html#format-specification-mini-language
But since you clearly didn't have time to google for it:
>>> x = 3.1234
>>> length=10
>>> precision=2
>>> f"{x:^{length}.{precision}}"
' 3.1 '
I'm afraid your notion of precision doesn't agree with Python's in the default case. You can fix it by specifying fixed point formatting instead of the default general formatting:
>>> f"{x:^{length}.{precision}f}"
' 3.12 '
This notation is more perspicuous than calling the method str.format(). But in Python 3.5 and earlier you need to do this instead:
>>> "{x:^{length}.{precision}f}".format(x=x, length=length, precision=precision)
But no amount of fiddling with the format is going to make 3.1234 come out as 3.14. I suspect that that was an error in the question, but if you really meant it, then there is no alternative but adjust the value of x before formatting it. Here is one way to do that:
>>> from decimal import *
>>> (Decimal(x) / Decimal ('0.02')).quantize(Decimal('1'), rounding=ROUND_UP) * Decimal('0.02')
Decimal('3.14')
This divides your number into a whole number of chunks of size 0.02, rounding up where necessary, then multiplies by 0.02 again to get the value you want.

Floor function is eliminating Integer scientific notation, Python

I will explain my problem by example:
>>> #In this case, I get unwanted result
>>> k = 20685671025767659927959422028 / 2580360422
>>> k
8.016582043889239e+18
>>> math.floor(k)
8016582043889239040
>>> #I dont want this to happen ^^, let it remain 8.016582043889239e+18
>>> #The following case though, is fine
>>> k2 = 5/6
>>> k2
0.8333333333333334
>>> math.floor(k2)
0
How do I make math.floor not flooring the scientific notated numbers? Is there a rule for which numbers are represented in a scientific notation (I guess it would be a certain boundry).
EDIT:
I first thought that the math.floor function was causing an accuracy loss, but it turns out that the first calculation itself lost the calculation's accuracy, which had me really confused, it can be easily seen here:
>>> 20685671025767659927959422028 / 2580360422
8016582043889239040
>>> 8016582043889239040 * 2580360422
20685671025767659370513274880
>>> 20685671025767659927959422028 - 20685671025767659370513274880
557446147148
>>> 557446147148 / 2580360422
216.0342184739958
>>> ##this is >1, meaning I lost quite a bit of information, and it was not due to the flooring
So now my problem is how to get the actual result of the division. I looked at the following thread:
How to print all digits of a large number in python?
But for some reason I didn't get the same result.
EDIT:
I found a simple solution for the division accuracy problem in here:
How to manage division of huge numbers in Python?
Apparently the // operator returns an int rather then float, which has no size limit apart to the machine's memory.
In Python 3, math.floor returns an integer. Integers are not displayed using scientific notation. Some floats are represented using scientific notation. If you want scientific notation, try converting back to float.
>>> float(math.floor(20685671025767659927959422028 / 2580360422))
8.016582043889239e+18
As Tadhg McDonald-Jensen indicates, you can also use str.format to get a string representation of your integer in scientific notation:
>>> k = 20685671025767659927959422028 / 2580360422
>>> "{:e}".format(k)
'8.016582e+18'
This may, in fact, be more practical than converting to float. As a general rule of thumb, you should choose a numeric data type based on the precision and range you require, without worrying about what it looks like when printed.

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])

Converting a float to a string without rounding it

I'm making a program that, for reasons not needed to be explained, requires a float to be converted into a string to be counted with len(). However, str(float(x)) results in x being rounded when converted to a string, which throws the entire thing off. Does anyone know of a fix for it?
Here's the code being used if you want to know:
len(str(float(x)/3))
Some form of rounding is often unavoidable when dealing with floating point numbers. This is because numbers that you can express exactly in base 10 cannot always be expressed exactly in base 2 (which your computer uses).
For example:
>>> .1
0.10000000000000001
In this case, you're seeing .1 converted to a string using repr:
>>> repr(.1)
'0.10000000000000001'
I believe python chops off the last few digits when you use str() in order to work around this problem, but it's a partial workaround that doesn't substitute for understanding what's going on.
>>> str(.1)
'0.1'
I'm not sure exactly what problems "rounding" is causing you. Perhaps you would do better with string formatting as a way to more precisely control your output?
e.g.
>>> '%.5f' % .1
'0.10000'
>>> '%.5f' % .12345678
'0.12346'
Documentation here.
len(repr(float(x)/3))
However I must say that this isn't as reliable as you think.
Floats are entered/displayed as decimal numbers, but your computer (in fact, your standard C library) stores them as binary. You get some side effects from this transition:
>>> print len(repr(0.1))
19
>>> print repr(0.1)
0.10000000000000001
The explanation on why this happens is in this chapter of the python tutorial.
A solution would be to use a type that specifically tracks decimal numbers, like python's decimal.Decimal:
>>> print len(str(decimal.Decimal('0.1')))
3
Other answers already pointed out that the representation of floating numbers is a thorny issue, to say the least.
Since you don't give enough context in your question, I cannot know if the decimal module can be useful for your needs:
http://docs.python.org/library/decimal.html
Among other things you can explicitly specify the precision that you wish to obtain (from the docs):
>>> getcontext().prec = 6
>>> Decimal('3.0')
Decimal('3.0')
>>> Decimal('3.1415926535')
Decimal('3.1415926535')
>>> Decimal('3.1415926535') + Decimal('2.7182818285')
Decimal('5.85987')
>>> getcontext().rounding = ROUND_UP
>>> Decimal('3.1415926535') + Decimal('2.7182818285')
Decimal('5.85988')
A simple example from my prompt (python 2.6):
>>> import decimal
>>> a = decimal.Decimal('10.000000001')
>>> a
Decimal('10.000000001')
>>> print a
10.000000001
>>> b = decimal.Decimal('10.00000000000000000000000000900000002')
>>> print b
10.00000000000000000000000000900000002
>>> print str(b)
10.00000000000000000000000000900000002
>>> len(str(b/decimal.Decimal('3.0')))
29
Maybe this can help?
decimal is in python stdlib since 2.4, with additions in python 2.6.
Hope this helps,
Francesco
I know this is too late but for those who are coming here for the first time, I'd like to post a solution. I have a float value index and a string imgfile and I had the same problem as you. This is how I fixed the issue
index = 1.0
imgfile = 'data/2.jpg'
out = '%.1f,%s' % (index,imgfile)
print out
The output is
1.0,data/2.jpg
You may modify this formatting example as per your convenience.

Categories

Resources