Python math issue

Discussion in 'Mac Programming' started by mysterytramp, Jun 12, 2009.

  1. macrumors 65816

    mysterytramp

    Joined:
    Jul 17, 2008
    Location:
    Maryland
    #1
    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
     
  2. macrumors G4

    wrldwzrd89

    Joined:
    Jun 6, 2003
    Location:
    Solon, OH
    #2
    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
    
     
  3. macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #3
    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
     
  4. macrumors G4

    wrldwzrd89

    Joined:
    Jun 6, 2003
    Location:
    Solon, OH
    #4
    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.
     
  5. macrumors 68030

    gibbz

    Joined:
    May 31, 2007
    Location:
    National Weather Center
    #5
    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]
     
  6. thread starter macrumors 65816

    mysterytramp

    Joined:
    Jul 17, 2008
    Location:
    Maryland
  7. macrumors 6502

    Joined:
    Jun 2, 2009
    #7
    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.
     
  8. thread starter macrumors 65816

    mysterytramp

    Joined:
    Jul 17, 2008
    Location:
    Maryland
    #8
    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
     
  9. macrumors 6502

    Joined:
    Jun 2, 2009
    #9
    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.
     
  10. thread starter macrumors 65816

    mysterytramp

    Joined:
    Jul 17, 2008
    Location:
    Maryland
    #10
    This is really good advice. I'm still new to Python and I'm hip deep in Unix with the water rising.

    mt
     

Share This Page