Python - Adding a decimal - python

Lets say I have the following function that converts ounces to lbs & oz
def oz2lboz(oz):
oz, lbs = oz - int(oz), int(oz)
l = lbs * 0.062500
o = oz
print "(%d,%d)" % (l,o)
Right now, if i was to compute oz2lboz(16), it would give me
(1,0)
However, I need it to give me,
(1,0.0)
How would i go about doing that?

First of all, the conversion algorithm you have is incorrect.
This line:
oz, lbs = oz - int(oz), int(oz)
Sets oz to be the value of the decimal portion and disguards the rest and lbs to the integer part. Then you multiply the pounds by 1/16, which in most cases will give you a decimal portion again, which is not usually whats wanted in this type of conversion.
Below we'll alter your original code to return a value to make a point:
def oz2lboz(oz):
oz, lbs = oz - int(oz), int(oz)
l = lbs * 0.062500
o = oz
return (l,o)
And call:
print "(%d,%.1f)" % oz2lboz(16)
>>> (1,0.0)
print "(%d,%.1f)" % oz2lboz(17)
>>> (1,0.0)
print "(%d,%.1f)" % oz2lboz(18)
>>> (1,0.0)
Well, that isn't right, 16, 17 and 18 oz are all equal to 1 pound! Lets try again and output the pounds using floats...
print "(%.1f,%.1f)" % oz2lboz(16)
>>> (1.0,0.0)
print "(%.1f,%.1f)" % oz2lboz(17)
>>> (1.1,0.0)
print "(%.1f,%.1f)" % oz2lboz(18)
>>> (1.1,0.0)
Better, but 17 and 18 are still the same... using more decimal points we get:
print "(%.4f,%.1f)" % oz2lboz(16)
>>> (1.0000,0.0)
print "(%.4f,%.1f)" % oz2lboz(17)
>>> (1.0625,0.0)
print "(%.4f,%.1f)" % oz2lboz(18)
>>> (1.1250,0.0)
So, basically unless there is a decimal portion you are going to be doing round behind the scenes, because the conversion is off.
Below I've rewritten your conversion in a correct and slightly simpler way that again returns the values rather than prints them, so you can output or reuse them as necessary.
def oz2lboz(oz):
lb=int(oz/16)
oz=float(oz%16)
return lb,oz
print oz2lboz(17)
Which gives this output:
(1, 1.0)
You can then use normal python string formatting to output it as needed, like so:
print "(%d,%.1f)" % oz2lboz(17) #using decimal integer and float formatting
>>> (1,1.0)
print "The baby weighed about, %d pounds and %d ounces." % oz2lboz(42) # just decimals
>>> The baby weighed about, 2 pounds and 10 ounces.
Or with our original "problematic" data we get the expected results:
print "(%d,%.1f)" % oz2lboz(16)
>>> (1,0.0)
print "(%d,%.1f)" % oz2lboz(17)
>>> (1,1.0)
print "(%d,%.1f)" % oz2lboz(18)
>>> (1,2.0)

Related

Format decimal without trailing zeros [duplicate]

I have a long list of Decimals and that I have to adjust by factors of 10, 100, 1000,..... 1000000 depending on certain conditions. When I multiply them there is sometimes a useless trailing zero (though not always) that I want to get rid of. For example...
from decimal import Decimal
# outputs 25.0, PROBLEM! I would like it to output 25
print Decimal('2.5') * 10
# outputs 2567.8000, PROBLEM! I would like it to output 2567.8
print Decimal('2.5678') * 1000
Is there a function that tells the decimal object to drop these insignificant zeros? The only way I can think of doing this is to convert to a string and replace them using regular expressions.
Should probably mention that I am using python 2.6.5
EDIT
senderle's fine answer made me realize that I occasionally get a number like 250.0 which when normalized produces 2.5E+2. I guess in these cases I could try to sort them out and convert to a int
You can use the normalize method to remove extra precision.
>>> print decimal.Decimal('5.500')
5.500
>>> print decimal.Decimal('5.500').normalize()
5.5
To avoid stripping zeros to the left of the decimal point, you could do this:
def normalize_fraction(d):
normalized = d.normalize()
sign, digits, exponent = normalized.as_tuple()
if exponent > 0:
return decimal.Decimal((sign, digits + (0,) * exponent, 0))
else:
return normalized
Or more compactly, using quantize as suggested by user7116:
def normalize_fraction(d):
normalized = d.normalize()
sign, digit, exponent = normalized.as_tuple()
return normalized if exponent <= 0 else normalized.quantize(1)
You could also use to_integral() as shown here but I think using as_tuple this way is more self-documenting.
I tested these both against a few cases; please leave a comment if you find something that doesn't work.
>>> normalize_fraction(decimal.Decimal('55.5'))
Decimal('55.5')
>>> normalize_fraction(decimal.Decimal('55.500'))
Decimal('55.5')
>>> normalize_fraction(decimal.Decimal('55500'))
Decimal('55500')
>>> normalize_fraction(decimal.Decimal('555E2'))
Decimal('55500')
There's probably a better way of doing this, but you could use .rstrip('0').rstrip('.') to achieve the result that you want.
Using your numbers as an example:
>>> s = str(Decimal('2.5') * 10)
>>> print s.rstrip('0').rstrip('.') if '.' in s else s
25
>>> s = str(Decimal('2.5678') * 1000)
>>> print s.rstrip('0').rstrip('.') if '.' in s else s
2567.8
And here's the fix for the problem that #gerrit pointed out in the comments:
>>> s = str(Decimal('1500'))
>>> print s.rstrip('0').rstrip('.') if '.' in s else s
1500
Answer from the Decimal FAQ in the documentation:
>>> def remove_exponent(d):
... return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
>>> remove_exponent(Decimal('5.00'))
Decimal('5')
>>> remove_exponent(Decimal('5.500'))
Decimal('5.5')
>>> remove_exponent(Decimal('5E+3'))
Decimal('5000')
Answer is mentioned in FAQ (https://docs.python.org/2/library/decimal.html#decimal-faq) but does not explain things.
To drop trailing zeros for fraction part you should use normalize:
>>> Decimal('100.2000').normalize()
Decimal('100.2')
>> Decimal('0.2000').normalize()
Decimal('0.2')
But this works different for numbers with leading zeros in sharp part:
>>> Decimal('100.0000').normalize()
Decimal('1E+2')
In this case we should use `to_integral':
>>> Decimal('100.000').to_integral()
Decimal('100')
So we could check if there's a fraction part:
>>> Decimal('100.2000') == Decimal('100.2000').to_integral()
False
>>> Decimal('100.0000') == Decimal('100.0000').to_integral()
True
And use appropriate method then:
def remove_exponent(num):
return num.to_integral() if num == num.to_integral() else num.normalize()
Try it:
>>> remove_exponent(Decimal('100.2000'))
Decimal('100.2')
>>> remove_exponent(Decimal('100.0000'))
Decimal('100')
>>> remove_exponent(Decimal('0.2000'))
Decimal('0.2')
Now we're done.
Use the format specifier %g. It seems remove to trailing zeros.
>>> "%g" % (Decimal('2.5') * 10)
'25'
>>> "%g" % (Decimal('2.5678') * 1000)
'2567.8'
It also works without the Decimal function
>>> "%g" % (2.5 * 10)
'25'
>>> "%g" % (2.5678 * 1000)
'2567.8'
I ended up doing this:
import decimal
def dropzeros(number):
mynum = decimal.Decimal(number).normalize()
# e.g 22000 --> Decimal('2.2E+4')
return mynum.__trunc__() if not mynum % 1 else float(mynum)
print dropzeros(22000.000)
22000
print dropzeros(2567.8000)
2567.8
note: casting the return value as a string will limit you to 12 significant digits
Slightly modified version of A-IV's answer
NOTE that Decimal('0.99999999999999999999999999995').normalize() will round to Decimal('1')
def trailing(s: str, char="0"):
return len(s) - len(s.rstrip(char))
def decimal_to_str(value: decimal.Decimal):
"""Convert decimal to str
* Uses exponential notation when there are more than 4 trailing zeros
* Handles decimal.InvalidOperation
"""
# to_integral_value() removes decimals
if value == value.to_integral_value():
try:
value = value.quantize(decimal.Decimal(1))
except decimal.InvalidOperation:
pass
uncast = str(value)
# use exponential notation if there are more that 4 zeros
return str(value.normalize()) if trailing(uncast) > 4 else uncast
else:
# normalize values with decimal places
return str(value.normalize())
# or str(value).rstrip('0') if rounding edgecases are a concern
You could use :g to achieve this:
'{:g}'.format(3.140)
gives
'3.14'
This should work:
'{:f}'.format(decimal.Decimal('2.5') * 10).rstrip('0').rstrip('.')
Just to show a different possibility, I used to_tuple() to achieve the same result.
def my_normalize(dec):
"""
>>> my_normalize(Decimal("12.500"))
Decimal('12.5')
>>> my_normalize(Decimal("-0.12500"))
Decimal('-0.125')
>>> my_normalize(Decimal("0.125"))
Decimal('0.125')
>>> my_normalize(Decimal("0.00125"))
Decimal('0.00125')
>>> my_normalize(Decimal("125.00"))
Decimal('125')
>>> my_normalize(Decimal("12500"))
Decimal('12500')
>>> my_normalize(Decimal("0.000"))
Decimal('0')
"""
if dec is None:
return None
sign, digs, exp = dec.as_tuple()
for i in list(reversed(digs)):
if exp >= 0 or i != 0:
break
exp += 1
digs = digs[:-1]
if not digs and exp < 0:
exp = 0
return Decimal((sign, digs, exp))
Why not use modules 10 from a multiple of 10 to check if there is remainder? No remainder means you can force int()
if (x * 10) % 10 == 0:
x = int(x)
x = 2/1
Output: 2
x = 3/2
Output: 1.5

Integer from tuple, divided, yields integer instead of float?

In the code below,
abc = (1,2,3)
a = abc[0]
b = abc[1]
c = abc[2]
print('%d, %d, %d' %(a,b,c)) # gives 1,2,3 as expected
a /= 3
b /= 3
c /= 3
print('%d, %d, %d' %(a,b,c)) # gives 0,0,1... which is NOT expected
I'm trying, however, to have a, b, c be 0.333, 0.6666, 1 respectively.
This problem persists even when I:
divide by 3.0
use a stupid abc = tuple([i/3.0 for i in abc]) workaround and then try to list all the elements again as above
even when I cast everything into a float like:
a = float(float(a)/float(3.0)) # still returns 0
I'm even using python 3, so an int/int division should return a float regardless.
Your print statements say to display the values as integers (%d); try using %f instead.
You are using %d so the output is printed as ints/decimal just use print:
print(a,b,c)
Use %f inside print statement
print('%f, %f, %f', %(a,b,c))

How can limit a certain number of decimal places being shown without rounding?

I would like to be able to limit the amount of decimal places that are shown from a print function without any sort of rounding system
v = 8.836333333333339
print ('%.2f' % v)
This code will print the value of v to two decimal places but also rounds it up or down, how could I make it stop this rounding please?
You could process it as a string:
v = 8.836333333333339
s = str(v)
print s[:s.find('.')+3]
# prints 8.83
If you know how long the number will be, you can easily accomplish this with string slicing.
>>> v = 8.836333333333339
>>> x = str(v) # get string representation of 'v'
>>> x
'8.836333333333339'
>>> y = x[0:4] # every character in 'x' between 0 and 4 but not including 4
>>> y
'8.83'
>>> v = float(y) # you can even convert it back to a number if you want
>>> v
8.83
How about using decimal module :
>>> help(Decimal.quantize)
Help on method quantize in module decimal:
quantize(self, exp, rounding=None, context=None, watchexp=True) unbound decimal.Decimal
method
Quantize self so its exponent is the same as that of exp.
Similar to self._rescale(exp._exp) but with error checking.
>>> from decimal import *
>>> v = 8.834333333333339
>>> print Decimal(v).quantize(Decimal('0.01'))
8.83
>>> print Decimal('8.8663').quantize(Decimal('0.01'))
8.87
>>> print Decimal('8.863').quantize(Decimal('0.01'))
8.86
>>>
A bit specific to your case, but you could also use int to truncate:
>>> print(int(v*100)/100.0)
8.83
It times at about 3x faster (310 ns vs 925 ns) than the string find-based approach.
<?php
$value_exp = explode(".", 245.788968);
$value_new = floatval($value_exp[0].'.'.substr($value_exp[1],0,4));
echo $value_new; //output will 245.7889
?>

How can I print any float, like 1.234e22, in a long, nicely rounded and localized format in python?

There are a few existing questions about float formatting, but none answer the following question, I think.
I'm looking for a way to print large floats in a long, nicely rounded and localized format:
>>> print magic_format(1.234e22, locale="en_US")
12,340,000,000,000,000,000,000
>>> print magic_format(1.234e22, locale="fr_FR")
12 340 000 000 000 000 000 000
Unfortunately, magic_format does not exist. ;-) How can I implement it?
Details
Here are a few ways to print floats. None of them produces the above output:
>>> x = 1.234e22
>>> print str(x)
1.234e+22
>>> print repr(x)
1.234e+22
>>> print "%f" % x
12339999999999998951424.000000
>>> print "%g" % x
1.234e+22
Fail: either I get the short version, or a non-grouping non-localized non-rounded output.
BTW, I understand that 1.234e22 cannot be stored exactly as a float, there's a necessary rounding error (that explains the odd output above). But since str, repr and "%g" % x are able to properly round that to the proper value, I would like to have the same friendly rounded number, but in a long and localized form.
Let's try localizing now...
>>> import locale
>>> locale.setlocale(locale.LC_ALL, "en_US")
'en_US'
>>> locale.format("%g", x, grouping = True)
'1.234e+22'
>>> locale.format("%f", x, grouping = True)
'12,339,999,999,999,998,951,424.000000'
>>> locale.setlocale(locale.LC_ALL, "fr_FR")
'fr_FR'
>>> locale.format("%g", x, grouping = True)
'1,234e+22'
>>> locale.format("%f", x, grouping = True)
'12339999999999998951424,000000'
Closer, but not ok. I still have the annoying rounding error, and the French localization sucks, it does not allow grouping at all.
So let's use the excellent Babel library, perhaps it can do everything I want:
>>> from babel.numbers import format_number
>>> format_number(x, locale = "en_US")
u'12,339,999,999,999,998,951,424'
>>> format_number(x, locale = "fr_FR")
u'12\xa0339\xa0999\xa0999\xa0999\xa0998\xa0951\xa0424'
Wow, really close. They even use non-breakable spaces for grouping in French, I love it. It's really too bad they still have the rounding issue.
Hey!? What if I used python Decimals?
>>> from decimal import Decimal
>>> Decimal(x)
Decimal('12339999999999998951424')
>>> Decimal("%g" % x)
Decimal('1.234E+22')
>>> "%g" % Decimal("%g" % x)
'1.234e+22'
>>> "%f" % Decimal("%g" % x)
'12339999999999998951424.000000'
Nope. I can get an exact representation of the number I want with Decimal("%g" % x), but whenever I try to display it, it's either short or converted to a bad float before it's printed.
But what if I mixed Babel and Decimals?
>>> Decimal("%g" % 1.234e22)
Decimal('1.234E+22')
>>> dx = _
>>> format_number(dx, locale = "en_US")
Traceback (most recent call last):
...
TypeError: bad operand type for abs(): 'str'
Ouch. But Babel's got a function called format_decimal, let's use that instead:
>>> from babel.numbers import format_decimal
>>> format_decimal(dx, locale = "en_US")
Traceback (most recent call last):
...
TypeError: bad operand type for abs(): 'str'
Oops, format_decimal can't format python Decimals. :-(
Ok, one last idea: I could try converting to a long.
>>> x = 1.234e22
>>> long(x)
12339999999999998951424L
>>> long(Decimal(x))
12339999999999998951424L
>>> long(Decimal("%g" % x))
12340000000000000000000L
Yes! I've got the exact number I want to format. Let's give that to Babel:
>>> format_number(long(Decimal("%g" % x)), locale = "en_US")
u'12,339,999,999,999,998,951,424'
Oh, no... Apparently Babel converts the long to a float before trying to format it. I'm out of luck, and out of ideas. :-(
If you think that this is tough, then try answering the same question for x = 1.234e-22. So far all I can print is either the short form 1.234e-22 or 0.0!
I would prefer this:
>>> print magic_format(1.234e-22, locale="en_US")
0.0000000000000000000001234
>>> print magic_format(1.234e-22, locale="fr_FR")
0,0000000000000000000001234
>>> print magic_format(1.234e-22, locale="en_US", group_frac=True)
0.000,000,000,000,000,000,000,123,400
>>> print magic_format(1.234e-22, locale="fr_FR", group_frac=True)
0,000 000 000 000 000 000 000 123 400
I can imagine writing a little function that would parse "1.234e-22" and format it nicely, but I would have to know all about the rules of number localization, and I'd rather not reinvent the wheel, Babel is supposed to do that. What should I do?
Thanks for your help. :-)
This takes a large chunk of code from selected answer from Nicely representing a floating-point number in python but incorporates Babel to handle L10N.
NOTE : Babel uses a weird unicode version of the space character for a lot of locales. Hence the if loop that mentions 'fr_FR' directly to convert it to a normal space character.
import locale
from babel.numbers import get_decimal_symbol,get_group_symbol
import decimal
# https://stackoverflow.com/questions/2663612/nicely-representing-a-floating-point-number-in-python/2663623#2663623
def float_to_decimal(f):
# http://docs.python.org/library/decimal.html#decimal-faq
"Convert a floating point number to a Decimal with no loss of information"
n, d = f.as_integer_ratio()
numerator, denominator = decimal.Decimal(n), decimal.Decimal(d)
ctx = decimal.Context(prec=60)
result = ctx.divide(numerator, denominator)
while ctx.flags[decimal.Inexact]:
ctx.flags[decimal.Inexact] = False
ctx.prec *= 2
result = ctx.divide(numerator, denominator)
return result
def f(number, sigfig):
assert(sigfig>0)
try:
d=decimal.Decimal(number)
except TypeError:
d=float_to_decimal(float(number))
sign,digits,exponent=d.as_tuple()
if len(digits) < sigfig:
digits = list(digits)
digits.extend([0] * (sigfig - len(digits)))
shift=d.adjusted()
result=int(''.join(map(str,digits[:sigfig])))
# Round the result
if len(digits)>sigfig and digits[sigfig]>=5: result+=1
result=list(str(result))
# Rounding can change the length of result
# If so, adjust shift
shift+=len(result)-sigfig
# reset len of result to sigfig
result=result[:sigfig]
if shift >= sigfig-1:
# Tack more zeros on the end
result+=['0']*(shift-sigfig+1)
elif 0<=shift:
# Place the decimal point in between digits
result.insert(shift+1,'.')
else:
# Tack zeros on the front
assert(shift<0)
result=['0.']+['0']*(-shift-1)+result
if sign:
result.insert(0,'-')
return ''.join(result)
def magic_format(num, locale="en_US", group_frac=True):
sep = get_group_symbol(locale)
if sep == get_group_symbol('fr_FR'):
sep = ' '
else:
sep = str(sep)
dec = str(get_decimal_symbol(locale))
n = float(('%E' % num)[:-4:])
sigfig = len(str(n)) - (1 if '.' in str(n) else 0)
s = f(num,sigfig)
if group_frac:
ans = ""
if '.' not in s:
point = None
new_d = ""
new_s = s[::-1]
else:
point = s.index('.')
new_d = s[point+1::]
new_s = s[:point:][::-1]
for idx,char in enumerate(new_d):
ans += char
if (idx+1)%3 == 0 and (idx+1) != len(new_d):
ans += sep
else: ans = ans[::-1] + (dec if point != None else '')
for idx,char in enumerate(new_s):
ans += char
if (idx+1)%3 == 0 and (idx+1) != len(new_s):
ans += sep
else:
ans = ans[::-1]
else:
ans = s
return ans
This chuck of code can be used as follows:
>>> magic_format(num2, locale = 'fr_FR')
'0,000 000 000 000 000 000 000 123 456 0'
>>> magic_format(num2, locale = 'de_DE')
'0,000.000.000.000.000.000.000.123.456.0'
>>> magic_format(num2)
'0.000,000,000,000,000,000,000,123,456'
>>> f(num,6)
'12345600000000000000000'
>>> f(num2,6)
'0.000000000000000000000123456'
with the f function coming from the link.

How to print negative zero in Python

I am converting decimal degrees to print as DMS. The conversion algorithm is what you would expect, using modf, with the addition that sign is taken out of the MS portion and left in only for the D portion.
Everything is fine except for the case where the Degree is negative zero, -0. An example is -0.391612 which should print as -0°23'29".
"%d" drops the negative sign. What format string can I use to print -0?
I've worked around it with a kludge that converts the numbers to strings and prepends a "-" if negative, then uses "%s" as the format. It's awkward and feels inelegant.
Here's the code:
def dec_to_DMS(decimal_deg):
deg = modf(decimal_deg)[1]
deg_ = fabs(modf(decimal_deg)[0])
min = modf(deg_ * 60)[1]
min_ = modf(deg_ * 60)[0]
sec = modf(min_ * 60)[1]
return deg,min,sec
def print_DMS(dms): # dms is a tuple
# make sure the "-" is printed for -0.xxx
format = ("-" if copysign(1,dms[0]) < 0 else "") + "%d°%02d'%02d\""
return format % dms
print print_DMS(dec_to_DMS(-0.391612))
>>> -0°23'29"
deg_ is to prevent the function returning (-0,-23,-29); it returns the correct (-0,23,29).
Use format()
>>> format(-0.0)
'-0.0'
>>> format(0.0)
'0.0'
>>> print '''{: g}°{}'{}"'''.format(-0.0, 23, 29)
-0°23'29"
You must print the degrees as a floating point value, not as an integer, as 2 complement integers (used on most platforms) do not have a negative zero. %d is for formatting integers.
print u'{:.0f}°{:.0f}\'{:.0f}\"'.format(deg, fabs(min), fabs(sec)).encode('utf-8')
deg, min and sec are all floats, the fabs calls are there so that the respective signs are not printed.
Alternatively using the old format string style:
print (u'%.0f°%.0f\'%.0f\"' % (deg, fabs(min), fabs(sec))).encode('utf-8')

Categories

Resources