Welcome, guest | Sign In | My Account | Store | Cart

One of the signs that you love Python is when you start to use it as a simple calculator. The problem with that is beyond the usefulness of 'sum' the interactive interpreter is not optimal for any calculations beyond a few numbers. This mostly seems to stem from the numbers not being formatted in a nice fashion; 2345634+2894756-2345823 is not the easiest thing to read. That's where an accountant's calculator comes in handy; the tape presents numbers in a column view that is very uncluttered. And thanks to the decimal package a very simple one can be implemented quickly.

To use this recipe you input the number, an optional space, and then the operator (/, *, -, or +; everything you would find on the numeric keypad on your keyboard) and then press return. This will apply the number to the running total using the operator. To output the total just enter a blank line. To quit enter the letter 'q' and press return. This simple interface matches the output of a typical accountant's calculator, removing the need to have some other form of output.

Python, 40 lines
 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 """A *very* simple command-line accountant's calculator. Uses the decimal package (introduced in Python 2.4) for calculation accuracy. Input should be a number (decimal point is optional), an optional space, and an operator (one of /, *, -, or +). A blank line will output the total. Inputting just 'q' will output the total and quit the program. """ import decimal import re parse_input = re.compile(r'(?P\d*(\.\d*)?)\s*(?P[/*\-+])') total = decimal.Decimal('0') total_line = lambda val: ''.join(('=' * 5, '\n', str(val))) while True: tape_line = raw_input() if not tape_line: print total_line(total) continue elif tape_line is 'q': print total_line(total) break try: num_text, space, op = parse_input.match(tape_line).groups() except AttributeError: raise ValueError("invalid input") num = decimal.Decimal(num_text) if op is '/': total /= num elif op is '*': total *= num elif op is '-': total -= num elif op is '+': total += num else: raise ValueError("unsupported operator: %s" % op)

This recipe is only possible thanks to the decimal package introduced in Python 2.4 . It allows for very high-precision decimal arithmetic that is just not possible using binary floating point numbers; no more lost pennies thanks to binary floating point not being able to represent exact values.

A nice improvement would be to remove the need to press return after each operator. That would require having stdin be unbuffered and reading in every character to detect when it is entered. Also a GUI version that visually looked like a calculator would be nice.

1 comment Jason Whitlark 17 years, 2 months ago

Improvements - no return needed, clear function, arbitrary decimal places. I was very pleased with this recipe, but wanted it to do a little more. Here is my version, which only works on windows; you'll need to override getchar for other operating systems. See Recipe 134892 for more on how to do that.

from decimal import *
import msvcrt     #Windows only!
import sys

class TenKey:
def __init__(self):
#set up for fixed decimal places (change .01)
getcontext().rounding = ROUND_HALF_UP
self.zero = Decimal('0').quantize(Decimal('.01'))

self.total = self.zero
self.EnteredOnce = False
self.currLine = ''

def clear(self):
self.total = self.zero
self.currLine = ''
print "c"

def onOperator(self, char):
print char
self.EnteredOnce = False
self.currLine = ''

def getchar(self):
"""Override or replace this function for Unix or MacOS
see http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/134892
for example"""
self.lineDelim = '\r'
return msvcrt.getch()

def run(self):
while True:
char = self.getchar()
if char == 'q':
break
elif char == 'c':
self.clear()
elif char == self.lineDelim:
if self.EnteredOnce:
self.clear()
else:
sys.stdout.write('=======\n' + str(self.total) + '\n')
self.EnteredOnce = True
continue
elif char == '+':
self.total += Decimal(self.currLine)
self.onOperator(char)
elif char == '-':
self.total -= Decimal(self.currLine)
self.onOperator(char)
elif char == '*':
self.total *= Decimal(self.currLine)
self.onOperator(char)
elif char == '/':
self.total /= Decimal(self.currLine)
self.onOperator(char)
elif char in '0123456789':
self.EnteredOnce = False
sys.stdout.write(char)
self.currLine += char
elif char == '.':
if char not in self.currLine:
self.currLine += char
sys.stdout.write(char)

if __name__ == "__main__":
calc = TenKey()
calc.run() Created by Brett Cannon on Thu, 2 Sep 2004 (PSF)