''' HP 12-C look-alike Designed for PythonWin running Tk/Tcl License: Public Domain Author: Raymond Hettinger Updates: http://users.rcn.com/python/download/python.htm ''' # Revision In Use: 'File %n, Ver %v, Date %f' version = 'File TWELVEC.PYW, Ver 35, Date 27-Mar-2002,3:20:02' # TODO list: # add scientific features: trig, log, complex # try to remove engine's dependencies on specific keys # Put borders around displays, colors on keys # try to generalize to work with non-stack calculators # catch mathErrors like log(-1) # figure out how to display UniCode through TCL # idea to use State Pattern to handle digit entry # implications of Inventors Paradox Pattern (generalized Display,Keyboard,CalcMachine) #---------- Engine: do() called by Interface, calls updateDisplay, calls keyDefinitions ZERO = 0.0 stack = [] prevSym = prevPrev = None lastx = ZERO deci = 1.0 mem = {} finReg = [0,0,0,0,0] def last( symbol ): if prevSym in ['Enter', 'CLx']: stack.pop() return lastx def dig( symbol ): global deci if symbol in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] and prevSym in ['RCL', 'STO']: if prevSym == 'RCL': stack.append( mem[symbol] ) elif prevSym == 'STO': mem[symbol] = stack[-1] return if prevSym in ['Enter', 'CLx']: stack[-1] = ZERO elif prevPrev in ['RCL', 'STO'] and prevSym in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']: stack.append(ZERO) elif prevSym not in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '\261', 'EEx']: stack.append(ZERO) if symbol == '.': deci = 0.1 return if stack[-1] < 0.0: sign = -1.0 else: sign = 1.0 if deci < 1.0: stack[-1] += float(symbol) * deci * sign deci /= 10.0 else: stack[-1] = stack[-1] * 10.0 + float(symbol) * sign def do( symbol ): global prevSym, lastx, deci, prevPrev while( len(stack) < 6 ): stack[:0] = [ZERO] if function.has_key(symbol): if symbol not in ['LSTx', 'CHS', 'Enter', 'Swap', 'Roll', 'CLx']: lastx = stack[-1] itemsToRemove, commStr = function[symbol] (t, z, y, x) = stack[-4:] del stack[-itemsToRemove:] for command in commStr.split(';'): stack.append( eval(command)+0.0 ) deci = 1.0 else: dig(symbol) updateDisplay( stack ) prevPrev = prevSym prevSym = symbol def solve( objfun, arg, argref ): 'Zero the objective function by applying Newtons method to arg tuple varying arg[argref]' x0 = arg[argref] + 1.0 y0 = objfun( tuple( list(arg[:argref]) + [x0] + list(arg[argref+1:]) ) ) x1 = arg[argref] for i in range(25): y1 = objfun( tuple( list(arg[:argref]) + [x1] + list(arg[argref+1:]) ) ) if abs(y1-y0) < 1E-5: break x0, y0, x1 = x1, y1, x1-y1*(x1-x0)/(y1-y0) return x1 #---------- User Interface calls do(symbol) for each keypress and accepts calls to updateDisplay(stack) and launch() from Tkinter import * display = [] def activate( event ): do( event.widget['text'] ) def makeButton( win, symbol, row, col ): b = Button( win, bg='LightBlue', fg='Black', font='Lucinda 11 bold', cursor='crosshair', width=4, text=symbol ) b.bind( '', activate ) b.grid( row=row, col=col, sticky=W+E ) def addcommas( n ): 'Converts float -234567890.1234 to string "-234,567,890.1234 "' l = list(str(n)) limit = 4 if '-' in l: limit = 5 if '.' not in l: l.append( '.' ) pos = l.index('.') while pos >= limit: pos -= 3 l[pos:pos] = [','] return ''.join(l) def updateDisplay( results ): offset = len(results) - len(display) assert offset >= 0, 'Result stack is to short to fully display' for i in range(len(display)): display[i].set( addcommas(results[i+offset]) ) def launch( layout, numDispLines=1 ): root= Tk() root.title('HP-12C') dispFrame = Frame( root ) dispFrame.pack( side=TOP ) for i in range( numDispLines ): display.append( StringVar(root) ) Label( dispFrame, textvariable=display[-1], font='Arial 20 bold' ).grid(row=i,col=0) keyFrame = Frame( root ) keyFrame.pack( side=TOP ) for row in range(len(layout)): colkeys = layout[row].split(' ') for col in range(len(colkeys)): makeButton( keyFrame, colkeys[col], row, col ) root.mainloop() #--------- Resource Entries and Launch Command from math import * def fact(n): result = 1 while n>1: result *= n n -= 1 return result def tvm( aTup ): 'Objective function for Time Value of Money calculations. Evaluates to zero for valid solutions' n, i, pv, pmt, fv = aTup i /= 100 fv1 = (1.0+i)**n if abs(i) > 1E-10: return pv*fv1 + pmt*(fv1 - 1.0)/i + fv elif abs(i) > 1E-11: a1 = pv*fv1 + pmt*(fv1 - 1.0)/i + fv a2 = pv*fv1 + pmt*n*(1.0-(n+1.0)*i/2.0*(1.0-(n+2.0)*i/3.0)) + fv w = ( abs(i) - 1E-11 ) / ( 1E-10 - 1E-11 ) return w * a1 + (1-w) * a2 else: return pv*fv1 + pmt*n*(1.0-(n+1.0)*i/2.0*(1.0-(n+2.0)*i/3.0)) + fv def fin(var, x): if prevSym in ['n', 'i', 'pv', 'pmt', 'fv']: x = solve( tvm, tuple(finReg), var ) finReg[var] = x return x function = { '+': (2,'y+x'), '-': (2,'y-x'), '\367': (2,'y/x'), 'x': (2,'y*x'), '\261': (1,'-x'), 'Enter':(1,'x;x'), 'Swap':(2,'x;y'), 'Roll':(2,'y'), 'CLx': (1,'0.0'), 'LSTx': (1,'x;last(x)'), '%': (2,'y;x*y/100.0'), '%T': (2,'y;x/y*100.0'), '%Chg':(2,'y;(x-y)/y*100.0'), '1/x': (1,'1/x'), 'y^x': (2,'y**x'), 'Sqrt':(1,'sqrt(x)'), 'Ln': (1,'log(x)'), 'e^x': (1,'exp(x)'), 'STO': (1,'x'), 'RCL': (1,'x'), 'n': (1,'fin(0,x)'), 'i': (1,'fin(1,x)'), 'pv': (1,'fin(2,x)'), 'pmt': (1,'fin(3,x)'), 'fv': (1,'fin(4,x)'), 'n!': (1,'fact(x)') } layout = [ 'n i pv pmt fv', '1/x % LSTx 7 8 9 \367 STO', 'y^x %T CLx 4 5 6 x RCL', 'e^x %Chg Swap 1 2 3 - n!', 'Ln \261 Roll 0 . Enter + Sqrt' ] launch(layout, numDispLines=1)