#!/usr/bin/python """ ################################################################ # clac version 004 2009-10-16 (c) Mark Borgerding Usage: clac [options] [expr1 [expr2 ...] ] clac (Command Line Advanced Calculator) evaluates mathematical expressions given as arguments or as stdin and writes the answer(s) to stdout. Unlike other command line calculators, clac: * has infix (natural order) expression syntax * handles complex numbers. * defines a great many functions and constants by default * allows easy definition of new user functions and constants using python "But I don't know python". You don't need to know python to use clac. Expressions like "1+2*3" "sin(pi/4)" "exp(j*2*pi/100)" "round( degrees(phase( e**(2j))))" ... act pretty much as you would expect. Run the selftest (-T) for more examples. Knowing python will help you extend clac's functionality. Relax, it's not that hard. Everything in the python math, and cmath modules is available... cos cosh acos acosh sin sinh asin asinh tan tanh atan atan2 atanh floor ceil fabs abs fmod modf degrees radians exp frexp ldexp hypot pow sqrt log log10 ... plus a variety of other functions created to make your life easier phase angle mag2 cpx conj nfft gcd lcm sign log2 mag real imag For more info about a given func, run: clac "help(func)" "Oh but I really want the conversion from meters per second to furlongs per fortnight" New data, functions,and modules can be made via the user's .clacrc file. e.g. #sample .clacrc from random import * c=3e8 verbose=True binary_prefix=True # equivalent to using -k complex_tuple=True # equivalent to using -p def myfunc(x): 'a user defined function' return x+1 Options: -k : use k,m,g as binary prefix (kibibyte,mebibyte,gibibyte) -p : print complex numbers as (re,im) pairs,rather than re+imj -T : run a self-diagnostic test (which makes a nice syntax demo) -v : verbose -h : help message -c file: reads a config file other than $HOME/.clacrc -C : display copyright """ # __future__ division allows division of two integers to produce a float result from __future__ import division COPYRIGHT = """ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2009 Mark Borgerding email: mark at borgerding dot net This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. This program has an explicit linking exception for the extensions via the rc file interface. """ selftests=""" 1+2 == 3 sqrt(-1) == j -2*asin(-1) == pi abs(sin(pi)) < 1e-9 abs(1-cos(0)) < 1e-9 round( 3.1 + -4.8j) == (3-5j) ceil( 3.1 + -4.8j) == (4-4j) abs( 3-4j) == 5 degrees(pi) == 180 radians(180) == pi abs( exp(j*pi) + 1 ) < 1e-9 pow(1.2,3.4) == 1.2**3.4 ldexp(1.2,3) == 1.2 * 2 ** 3 modf(1.2)[1] == 1 log(81,3) == 4 gcd(6,8) == 2 lcm(6,8) == 24 angle( exp( j*pi ) ) == pi log(-1)**2 == -1*pow(pi,2) round( degrees(phase( e**(2j)))) == 115 sum( [ round(42 * exp(j*2*x*pi/4)) for x in range(4)] ) == 0 oct(8) == '010' 0x42-042-42 == -10 1k == 1024 1m == 2**20 1g == 2**30 2**10-1 == 1023 """ import math from math import e,pi,atan2,fmod,frexp,hypot,ldexp,modf def degrees(x): return x*180/pi def radians(x): return x*pi/180 import cmath from cmath import acosh,asinh,atanh # other nice-to-have constants j = i = cmath.sqrt(-1) # some applications might want (re,im) instead of re+imj __print_cpx_as_pairs__ = False def dip(x): 'demote, if possible, a complex to scalar' if type(x) == complex and x.imag == 0: return x.real else: return x def which_call( x , mathfunc,cmathfunc , allowNegative=True): x=dip(x) if type(x) == complex or (allowNegative == False and x<0): return cmathfunc(x) else: return mathfunc(x) # marshall between the math and cmath functions automatically def acos( x ): return which_call(x,math.acos,cmath.acos) def asin( x ): return which_call(x,math.asin,cmath.asin) def atan( x ): return which_call(x,math.atan,cmath.atan) def cos( x ): return which_call(x,math.cos,cmath.cos) def cosh( x ): return which_call(x,math.cosh,cmath.cosh) def sin( x ): return which_call(x,math.sin,cmath.sin) def sinh( x ): return which_call(x,math.sinh,cmath.sinh) def tan( x ): return which_call(x,math.tan,cmath.tan) def tanh( x ): return which_call(x,math.tanh,cmath.tanh) def exp( x ): return which_call(x,math.exp,cmath.exp) def log10( x ): return which_call(x,math.log10,cmath.log10,False) def sqrt( x ): return which_call(x,math.sqrt,cmath.sqrt,False) #steal the help strings from the cmath functions acos.__doc__ = cmath.acos.__doc__ asin.__doc__ = cmath.asin.__doc__ atan.__doc__ = cmath.atan.__doc__ cos.__doc__ = cmath.cos.__doc__ cosh.__doc__ = cmath.cosh.__doc__ sin.__doc__ = cmath.sin.__doc__ sinh.__doc__ = cmath.sinh.__doc__ tan.__doc__ = cmath.tan.__doc__ tanh.__doc__ = cmath.tanh.__doc__ exp.__doc__ = cmath.exp.__doc__ log10.__doc__ = cmath.log10.__doc__ sqrt.__doc__ = cmath.sqrt.__doc__ def log(x,b=e): 'log(x[, base]) -> the logarithm of x to the given base.\nIf the base not specified, returns the natural logarithm (base e) of x.' if type(x) == complex or x<0: return dip( cmath.log(x) / cmath.log(b) ) else: return math.log(x)/math.log(b) def real(x): 'return just the real portion' if type(x) == complex: return x.real else: return x def imag(x): 'return just the imaginary portion' if type(x) == complex: return x.imag else: return 0 def sign(x): 'returns -1,0,1 for negative,zero,positive numbers' if x == 0: return 0 elif x > 0: return 1 else: return -1 def log2(x): 'logarithm base 2' return log(x,2) def nfft( insize,direction=1, musthave=2,factors=(2,3,5) ): """find a 'good' fft size close to the desired size sign(direction) search directions -1 lower 1 higher 0 closest """ insize=round(insize) direction = sign(direction) offset=0 if type(factors) not in (tuple,list): factors=(factors,) while True: ntmp = insize+offset if musthave > 1 and (ntmp%musthave)==0: for f in factors: while (ntmp%f)==0: ntmp = ntmp / f if ntmp==1: return insize+offset if direction == 0: offset = (offset<=0) - offset else: offset += direction def gcd(x,y): 'greatest common denominator' while x>0: (x,y) = (y%x,x) # Guido showed me this one on the geek cruise return y def lcm(x,y): 'least common multiple' return x*y/gcd(x,y) def phase(z): 'phase of a complex in radians' z=cpx(z) return atan2( z.imag , z.real ) def cpx(x): 'convert a number or tuple to a complex' if type(x) == tuple: return complex( x[0] , x[1] ) else: return complex(x) def mag2(x): 'magnitude,squared' return abs(x*x) def conj( x ): 'complex conjugate' x = cpx( x ) return complex( x.real , -x.imag ) def complexify(x,func ): 'call func on the real and imaginary portions, creating a complex from the respective results' if type(x) == complex and x.imag != 0: return dip( complex( func(x.real) , func(x.imag) ) ) else: return func(x) # overwrite the builtin math functions that don't handle complex def round(x): 'nearest integer' if type(x) == complex: return complexify( x , round ) else: return math.floor(x+.5) def floor(x): 'round towards negative infinity' return complexify( x , math.floor ) def ceil(x): 'round towards positive infinity' return complexify( x , math.ceil ) def fabs(x): 'absolute value of real and imaginary parts' return complexify( x , math.fabs ) def pow(x,y): 'raise to a power' if type(x) == complex or type(y) == complex: return dip( exp( y * log( x ) ) ) # for some reason cmath does not define pow else: return math.pow(x,y) # some people may prefer these names mag=abs angle=phase def fmt_float(x): 'convert a single float to a string' return '%.17g' % x def fmt_cpx(x): 'convert a complex value to a string' if __print_cpx_as_pairs__ == True: return '(%s,%s)' % ( fmt_float( x.real) , fmt_float( x.imag) ) elif x.imag<0: return '%s %sj' % ( fmt_float( x.real) , fmt_float( x.imag) ) else: return '%s+%sj' % ( fmt_float( x.real) , fmt_float( x.imag) ) def fmt(x): 'convert the evaluated expression to a string' if type(x) == complex: if x.imag == 0: return fmt_float( x.real) else: return fmt_cpx(x) elif type(x) in (list,tuple): return ','.join( [fmt(item) for item in x ]) elif type(x) == float: return fmt_float(x) else: return '%s' % x class Session: 'clac sequence of commands' def __init__(self): import os self.rcfile = '%s/.clacrc' % os.getenv('HOME') self.env = {} self.env['verbose'] = False self.env['binary_prefix'] = False self.env['complex_tuple'] = False def read_cmdline( self): import sys import getopt try: opts,self.args = getopt.getopt(sys.argv[1:] , 'pvc:hCkT') opts = dict(opts) except getopt.GetoptError,e: opts={} opts['-h'] = True if opts.has_key('-C'): sys.stderr.write(COPYRIGHT) sys.exit(1) if opts.has_key('-h'): sys.stderr.write(__doc__) sys.exit(1) self.env['verbose'] = opts.has_key('-v') self.env['binary_prefix'] = opts.has_key('-k') self.env['complex_tuple'] = opts.has_key('-p') self.rcfile = opts.get('-c' , self.rcfile ) if opts.has_key('-T'): self.env['binary_prefix'] = True # needed for some of the selftests self.run_self_test() sys.exit(0) def eval_text( self,text,name="<text>" ): 'evaluate a set of commands in a file' global __print_cpx_as_pairs__ __print_cpx_as_pairs__ = self.env['complex_tuple'] eval( compile( text ,name,'exec') ,globals(),self.env ) def eval_file( self,file ): 'evaluate a set of commands in a file' self.eval_text( ''.join( open(file).readlines() ) , '<%s>' % file) def eval_to_string( self,s ): 'evaluate an expression and print the string' if s is None: return if s in ('','\n'): return if self.env['binary_prefix']: import re s = re.sub( r'(\b[\d+])[kK]\b' , '\\1*1024' , s ) s = re.sub( r'(\b[\d+])[mM]\b' , '\\1*1048576' , s ) s = re.sub( r'(\b[\d+])[gG]\b' , '\\1*1073741824' , s ) global __print_cpx_as_pairs__ __print_cpx_as_pairs__ = self.env['complex_tuple'] eval_rc = eval( s ,globals(),self.env ) if eval_rc is None: return return fmt( eval_rc ) def eval_expr( self,s ): s2 = self.eval_to_string(s) if s2: print s2 def run_self_test(self): for expr in selftests.split('\n'): if expr == "": continue result = self.eval_to_string(expr) if result == "True": print " %s" % expr else: raise AssertionError( "FAILED:%s" % expr ) def main(self): self.read_cmdline() import os import sys # read and evaluate the rc file in the current context if os.access( self.rcfile,os.F_OK): self.eval_file( self.rcfile ) if len( self.args ) < 1: #interactive mode while 1: line = sys.stdin.readline() if line == '': break self.eval_expr( line ) else: # arguments are expressions for expr in self.args: self.eval_expr( expr ) if __name__ == "__main__": ses=Session() ses.main()