PDA

View Full Version : Python math issue




mysterytramp
Jun 12, 2009, 07:52 AM
I'm doing an exercise in "Core Python Programming" where you calculate the coins necessary to make an amount of money. This works most of the time:


coins = [0,0,0,0,0,0]
# dollars, half dollars, quarters, dimes, nickels, pennies
values = [100,50,25,10,5,1]

amountString = raw_input('Enter amount: ')

amount = 100*float(amountString)

for x in range(0,6,1):
coins[x] = amount//values[x]
amount %= values[x]

print "Dollars: ", coins[0]
print "Half Dollars: ", coins[1]
print "Quarters: ", coins[2]
print "Dimes: ", coins[3]
print "Nickels: ", coins[4]
print "Pennies: ", coins[5]


but occasionally I get weird answers. 5.10 is five dollars, a nickel and four pennies. 4.56 is four dollars, a half dollar and a nickel. Weird

Python 2.5.4 on OS X 10.4.11

mt



wrldwzrd89
Jun 12, 2009, 08:05 AM
This is almost certainly caused by floating point representation errors. These can be avoided, if you're careful with exactly how you process the input.

The easiest way to deal with this problem is to use the round() function to chop the decimal to 2 digits after the decimal, before working with it. Here's an example of how it's used:

money = raw_input('Enter amount: ')
money = round(money, 2) # Chop money variable to last 2 digits after decimal

lee1210
Jun 12, 2009, 08:17 AM
I said this in another post recently, but it bears repeating. Almost every situation that is dealing with money calls for fixed point math on the cents.
I would get your input as a string, split it on the . then add 100*the first portion to the second portion as integers. Now you have a number of whole cents (if the 2nd part is more than 2 characters, throw out an error). You can then do exact integer math using each denomination's value in cents.

Floating point math in base 10 is inherently inexact, so if you are doing fixed point math, just use integers. For display you can simply format using / 100 and % 100.

-Lee

wrldwzrd89
Jun 12, 2009, 08:19 AM
I said this in another post recently, but it bears repeating. Almost every situation that is dealing with money calls for fixed point math on the cents.
I would get your input as a string, split it on the . then add 100*the first portion to the second portion as integers. Now you have a number of whole cents (if the 2nd part is more than 2 characters, throw out an error). You can then do exact integer math using each denomination's value in cents.

Floating point math in base 10 is inherently inexact, so if you are doing fixed point math, just use integers. For display you can simply format using / 100 and % 100.

-Lee
I was going to suggest something similar - you worded it better than I would have, though. I totally agree with you. Fixed point math is best done in the integer space, because that way you have no precision issues at all.

gibbz
Jun 12, 2009, 08:33 AM
I rewrote it using the ideas the other presented and it worked fine
#!/usr/bin/env python
coins = [0,0,0,0,0,0]
recs = len(coins)

# dollars, half dollars, quarters, dimes, nickels, pennies
values = [100,50,25,10,5,1]

amountString = raw_input('Enter amount: ')
amountList = amountString.split('.')
amount = 100*int(amountList[0]) + int(amountList[1])

for x in range(0,recs):
coins[x] = amount//values[x]
amount %= values[x]

print "Dollars: ", coins[0]
print "Half Dollars: ", coins[1]
print "Quarters: ", coins[2]
print "Dimes: ", coins[3]
print "Nickels: ", coins[4]
print "Pennies: ", coins[5]

mysterytramp
Jun 12, 2009, 08:52 AM
Thanks guys.

mt

rowsdower
Jun 12, 2009, 09:05 AM
Floating point math in base 10 is inherently inexact, so if you are doing fixed point math, just use integers.

Alternatively, in python >= 2.4, you can use the decimal (http://docs.python.org/library/decimal.html) module.


from decimal import Decimal

coins = [0,0,0,0,0,0]
# dollars, half dollars, quarters, dimes, nickels, pennies
values = [100,50,25,10,5,1]

amountString = raw_input('Enter amount: ')

amount = 100*Decimal(amountString)

for x in range(0,6,1):
coins[x] = amount//values[x]
amount %= values[x]

print "Dollars: ", coins[0]
print "Half Dollars: ", coins[1]
print "Quarters: ", coins[2]
print "Dimes: ", coins[3]
print "Nickels: ", coins[4]
print "Pennies: ", coins[5]


I haven't tested this extensively, but it works for your 5.10 example.

mysterytramp
Jun 12, 2009, 09:14 PM
Alternatively, in python >= 2.4, you can use the decimal (http://docs.python.org/library/decimal.html) module.

This is a good segue to a larger question I have on Python ... Sometimes these outside libraries are offering different ways to approach a problem that can be solved with the existing language. When is it smarter to take advantage of a library, and when is it smarter to use what the language offers built-in?

This thread generated two versions of the same code. I haven't done any performance testing, but assuming performance is similar, I'd lean toward the first example, which includes:

amountList = amountString.split('.')
amount = 100*int(amountList[0]) + int(amountList[1])

Granted, in no way is this "production" code, so the only real question is whether "I" have 2.4, not some other user. Still, I would think "real" programmers would have a preference.

(This reminds me of times when a Scripting Addition solves a problem that can already be solved using plain old AppleScript.)

mt

rowsdower
Jun 13, 2009, 09:59 AM
This is a good segue to a larger question I have on Python ... Sometimes these outside libraries are offering different ways to approach a problem that can be solved with the existing language. When is it smarter to take advantage of a library, and when is it smarter to use what the language offers built-in?


In this case, lee1210's solution is simpler, easy to understand, available in all versions of Python, and probably faster than using the decimal module. I pointed out the decimal module just so that you would know about it in case you need to do precise floating point math in the future.

In general, my opinion is that my time is more valuable than computing time. I hardly ever write programs that are really performance critical, but even when I do I normally write them in a way that makes sense to me first and then go back to do optimizations. Sometimes that means not using a library that I had used for convenience, but was bad for performance. Sometimes that means finding and using a library that I didn't know about, because it does what I need and the code is already optimized.

By the way, if you're interested in performance optimization, Python has some very easy to use profilers (http://docs.python.org/library/profile.html). They can tell you what parts of your program take the longest and give you a way to compare different solutions to see which is fastest.

mysterytramp
Jun 13, 2009, 10:37 AM
In this case, lee1210's solution is simpler, easy to understand, available in all versions of Python, and probably faster than using the decimal module. I pointed out the decimal module just so that you would know about it in case you need to do precise floating point math in the future. ...

This is really good advice. I'm still new to Python and I'm hip deep in Unix with the water rising.

mt