Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

mysterytramp

macrumors 65816
Original poster
Jul 17, 2008
1,334
4
Maryland
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:

Code:
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

macrumors G5
Jun 6, 2003
12,110
77
Solon, OH
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:

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

lee1210

macrumors 68040
Jan 10, 2005
3,182
3
Dallas, TX
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

macrumors G5
Jun 6, 2003
12,110
77
Solon, OH
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

macrumors 68030
May 31, 2007
2,701
100
Norman, OK
I rewrote it using the ideas the other presented and it worked fine
Code:
#!/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]
 

rowsdower

macrumors 6502
Jun 2, 2009
269
1
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 module.

Code:
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

macrumors 65816
Original poster
Jul 17, 2008
1,334
4
Maryland
Alternatively, in python >= 2.4, you can use the decimal 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:

Code:
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

macrumors 6502
Jun 2, 2009
269
1
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. 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

macrumors 65816
Original poster
Jul 17, 2008
1,334
4
Maryland
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
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.