/* * math.c - mathematics functions for a hand calculator under X * * Author: John H. Bradley, University of Pennsylvania * (bradley@cis.upenn.edu) * March, 1987 * * RPN mode added and port to X11 by Mark Rosenstein, MIT Project Athena * * Modified to be a client of the Xt toolkit and the Athena widget set by * Donna Converse, MIT X Consortium. This is all that remains of the * original calculator, and it still needs to be rewritten. The HP * functionality should be separated from the TI functionality. * Beware the HP functions: there are still errors here. * * Geoffrey Coram fixed most of the HP mode bugs. */ #include "xcalc.h" #ifndef M_PI /* sometimes defined in math.h */ #define M_PI 3.14159265358979323846 #endif #ifndef M_E /* sometimes defined in math.h */ #define M_E 2.7182818284590452354 #endif #define MAXDISP 11 #define DEG 0 /* DRG mode. used for trig calculations */ #define RAD 1 #define GRAD 2 #define True 1 #define False 0 #ifndef IEEE jmp_buf env; #endif /* This section is all of the state machine that implements the calculator * functions. Much of it is shared between the infix and rpn modes. */ static int flagINV, flagPAREN, flagM, drgmode, numbase; /* display flags */ static double drg2rad=M_PI/180.0; /* Conversion factors for trig funcs */ static double rad2drg=180.0/M_PI; static int entered=1; /* true if display contains a valid number. if==2, then use 'dnum', rather than the string stored in the display. (for accuracy) if==3, then error occurred, only CLR & AC work */ /* entered seems to be overloaded - dmc */ static int lift_enabled = 0; /* for rpn mode only */ static int CLR =0; /* CLR clears display. if 1, clears acc, also */ static int Dpoint=0; /* to prevent using decimal pt twice in a # */ static int clrdisp=1; /* if true clears display before entering # */ static int lastop =kCLR; static int memop =kCLR; static int exponent=0; static double acc =0.0; static double dnum=0.0; #define XCALC_MEMORY 10 static double mem[XCALC_MEMORY] = { 0.0 }; static void DrawDisplay(void); static void PushOp(int op); static int PopOp(void); static int isopempty(void); #ifdef DEBUG static void showstack(char *string); #endif static void PushNum(double num); static double PopNum(void); static void RollNum(int dir); static void ClearStacks(void); static int priority(int op); #ifndef HAVE_STRLCPY /* Close enough for the short strings copied in xcalc */ static inline size_t strlcpy(char *dst, const char *src, size_t size) { strncpy(dst, src, size); dst[size - 1] = '\0'; return strlen(src); } #endif /* * The following is to deal with the unfortunate assumption that if errno * is non-zero then an error has occurred. On some systems (e.g. Ultrix), * sscanf will call lower level routines that will set errno. */ static void parse_double(double *dp) { unsigned long n = 0; int olderrno = errno; switch (numbase) { case 8: sscanf(dispstr, "%lo", &n); *dp = (double)n; break; case 16: sscanf(dispstr, "%lX", &n); *dp = (double)n; break; default: sscanf(dispstr, "%lf", dp); } errno = olderrno; return; } /** * Format the given double according to the * selected number base. */ static void format_double(double n) { switch (numbase) { case 8: snprintf(dispstr, sizeof(dispstr), "%lo", (long)n); break; case 16: snprintf(dispstr, sizeof(dispstr), "%lX", (long)n); break; default: snprintf(dispstr, sizeof(dispstr), "%.8g", n); } } /*********************************/ int pre_op(int keynum) { if (keynum==-1) return(0); errno = 0; /* for non-IEEE machines */ if ( (entered==3) && !(keynum==kCLR || keynum==kOFF)) { if (rpn) { clrdisp++; } else { ringbell(); return(1); /* the intent was probably not to do the operation */ } } if (keynum != kCLR) CLR=0; return(0); } #ifndef IEEE /* cannot assign result of setjmp under ANSI C, use global instead */ static volatile int SignalKind; void fail_op(void) { if (SignalKind == SIGFPE) strlcpy(dispstr, "math error", sizeof(dispstr)); else if (SignalKind == SIGILL) strlcpy(dispstr, "illegal operand", sizeof(dispstr)); entered=3; DrawDisplay(); return; } /*ARGSUSED*/ void fperr(int sig) { #if defined(SYSV) || defined(SVR4) || defined(linux) signal(SIGFPE, fperr); #endif SignalKind = sig; longjmp(env,1); } /* for VAX BSD4.3 */ /*ARGSUSED*/ void illerr(int sig) { /* not reset when caught? */ signal(SIGILL, illerr); SignalKind = sig; longjmp(env,1); } #endif /* not IEEE */ void post_op(void) { #ifdef DEBUG showstack("\0"); #endif #ifndef IEEE if (errno) { strlcpy(dispstr, "error", sizeof(dispstr)); DrawDisplay(); entered=3; errno=0; } #endif } /*-------------------------------------------------------------------------*/ static void DrawDisplay(void) { if (strlen(dispstr) >= MAXDISP) { /* strip out some decimal digits */ char *estr = index(dispstr,'e'); /* search for exponent part */ if (!estr) dispstr[12]='\0'; /* no exp, just trunc. */ else { char tmp[32]; if (strlen(estr) <= 4) /* leftmost 8 chars plus exponent */ snprintf(tmp, sizeof(tmp), "%.8s%s", dispstr, estr); else /* leftmost 7 chars plus exponent */ snprintf(tmp, sizeof(tmp), "%.7s%s", dispstr, estr); strlcpy(dispstr, tmp, sizeof(dispstr)); } } draw(dispstr); setflag(XCalc_MEMORY, (flagM)); setflag(XCalc_INVERSE, (flagINV)); setflag(XCalc_DEGREE, (drgmode==DEG)); setflag(XCalc_RADIAN, (drgmode==RAD)); setflag(XCalc_GRADAM, (drgmode==GRAD)); setflag(XCalc_PAREN, (flagPAREN)); setflag(XCalc_HEX, (numbase==16)); setflag(XCalc_DEC, (numbase==10)); setflag(XCalc_OCT, (numbase==8)); } /*-------------------------------------------------------------------------*/ void change_base(void) { parse_double(&dnum); if (dnum >= 0) { switch (numbase) { case 8: numbase = 10; break; case 10: numbase = 16; break; case 16: numbase = 8; break; } format_double(dnum); } else strlcpy(dispstr, "error", sizeof(dispstr)); DrawDisplay(); } /*-------------------------------------------------------------------------*/ void numeric(int keynum) { char st[2]; flagINV=0; if (rpn && (memop == kSTO || memop == kRCL || memop == kSUM)) { int cell = 0; switch (keynum) { case kONE: cell = 1; break; case kTWO: cell = 2; break; case kTHREE: cell = 3; break; case kFOUR: cell = 4; break; case kFIVE: cell = 5; break; case kSIX: cell = 6; break; case kSEVEN: cell = 7; break; case kEIGHT: cell = 8; break; case kNINE: cell = 9; break; case kZERO: cell = 0; break; } switch (memop) { case kSTO: mem[cell] = dnum; lift_enabled = 1; entered = 2; clrdisp++; break; case kRCL: PushNum(dnum); dnum = mem[cell]; format_double(dnum); lift_enabled = 1; entered = 1; clrdisp++; break; case kSUM: mem[cell] += dnum; lift_enabled = 1; entered = 2; clrdisp++; break; } memop = kCLR; DrawDisplay(); return; } if (clrdisp) { dispstr[0]='\0'; exponent=Dpoint=0; /* if (rpn && entered==2) PushNum(dnum); */ if (rpn & lift_enabled) PushNum(dnum); } if ((int) strlen(dispstr) >= MAXDISP) return; st[0] = '\0'; switch (keynum){ case kZERO: st[0] = '0'; break; case kONE: st[0] = '1'; break; case kTWO: st[0] = '2'; break; case kTHREE: st[0] = '3'; break; case kFOUR: st[0] = '4'; break; case kFIVE: st[0] = '5'; break; case kSIX: st[0] = '6'; break; case kSEVEN: st[0] = '7'; break; case kEIGHT: if (numbase > 8) st[0] = '8'; break; case kNINE: if (numbase > 8) st[0] = '9'; break; case kxA: if (numbase > 10) st[0] = 'A'; break; case kxB: if (numbase > 10) st[0] = 'B'; break; case kxC: if (numbase > 10) st[0] = 'C'; break; case kxD: if (numbase > 10) st[0] = 'D'; break; case kxE: if (numbase > 10) st[0] = 'E'; break; case kxF: if (numbase > 10) st[0] = 'F'; break; } if (st[0] == '\0') return; st[1] = '\0'; strcat(dispstr,st); DrawDisplay(); if (clrdisp && keynum != kZERO) clrdisp=0; /*no leading 0s*/ memop = keynum; entered=1; lift_enabled = 0; } void bkspf(void) { lift_enabled = 0; if (! flagINV) { if (entered!=1) { clearf(); return; } if (clrdisp) return; if ((int) strlen(dispstr) > 0) { #ifndef X_LOCALE const char *dp = localeconv()->decimal_point; size_t dp_len = strlen(dp); size_t ds_len = strlen(dispstr); if (ds_len >= dp_len && strcmp(dispstr + ds_len - dp_len, dp) == 0) Dpoint=0; #else if (dispstr[strlen(dispstr)-1] == '.') Dpoint=0; #endif dispstr[strlen(dispstr)-1] = 0; } if (strlen(dispstr) == 0) { strcat(dispstr, "0"); clrdisp++; } } else { strlcpy(dispstr, "0", sizeof(dispstr)); dnum = 0.0; clrdisp++; flagINV = 0; } DrawDisplay(); } void decf(void) { flagINV=0; if (clrdisp) { if (rpn && lift_enabled) PushNum(dnum); strlcpy(dispstr, "0", sizeof(dispstr)); } if (!Dpoint) { #ifndef X_LOCALE strcat(dispstr, localeconv()->decimal_point); #else strcat(dispstr, "."); #endif DrawDisplay(); Dpoint++; } clrdisp=0; entered=1; } void eef(void) { flagINV=0; if (clrdisp) { if (rpn && lift_enabled) PushNum(dnum); strlcpy(dispstr, rpn ? "1" : "0", sizeof(dispstr)); } if (!exponent) { strcat(dispstr,"E+"); DrawDisplay(); exponent=strlen(dispstr)-1; /* where the '-' goes */ } clrdisp=0; entered=1; } void clearf(void) { flagINV=0; if (CLR && !rpn) { /* clear all */ ClearStacks(); flagPAREN=0; } CLR++; exponent=Dpoint=0; clrdisp=1; entered=1; strlcpy(dispstr, "0", sizeof(dispstr)); DrawDisplay(); } void negf(void) { flagINV=0; if (exponent) { /* neg the exponent */ if (dispstr[exponent]=='-') dispstr[exponent]='+'; else dispstr[exponent]='-'; DrawDisplay(); return; } if (strcmp("0",dispstr)==0) return; /* don't neg a zero */ if (dispstr[0]=='-') /* already neg-ed */ strcpy(dispstr,dispstr+1); /* move str left once */ else { /* not neg-ed. add a '-' */ char tmp[LCD_STR_LEN + 1]; snprintf(tmp, sizeof(tmp), "-%s", dispstr); strlcpy(dispstr, tmp, sizeof(dispstr)); } if (entered==2) dnum = -1.0 * dnum; DrawDisplay(); } /* Two operand functions for infix calc */ void twoop(int keynum) { if (flagINV) { flagINV=0; DrawDisplay(); } if (!entered) { /* something like "5+*" */ if (!isopempty()) PopOp(); /* replace the prev op */ PushOp(keynum); /* with the new one */ return; } if (entered==1) parse_double(&dnum); clrdisp=CLR=1; entered=Dpoint=exponent=0; if (!isopempty()) { /* there was a previous op */ lastop=PopOp(); /* get it */ if (lastop==kLPAR) { /* put it back */ PushOp(kLPAR); PushOp(keynum); PushNum(dnum); return; } /* now, if the current op (keynum) is of higher priority than the lastop, the current op and number are just pushed on top Priorities: (Y^X) > *,/ > +,- > >>,<< > & > ^ > ~ */ if (priority(keynum) > priority(lastop)) { PushNum(dnum); PushOp(lastop); PushOp(keynum); } else { /* execute lastop on lastnum and dnum, push result and current op on stack */ acc=PopNum(); switch (lastop) { /* perform the operation */ case kADD: acc += dnum; break; case kSUB: acc -= dnum; break; case kMUL: acc *= dnum; break; case kDIV: acc /= dnum; break; case kPOW: acc = pow(acc,dnum); break; case kMOD: acc = (long)acc % (long)dnum; break; case kAND: acc = (long)acc & (long)dnum; break; case kOR: acc = (long)acc | (long)dnum; break; case kXOR: acc = (long)acc ^ (long)dnum; break; case kSHL: acc = (long)acc << (long)dnum; break; case kSHR: acc = (long)acc >> (long)dnum; break; } PushNum(acc); PushOp(keynum); format_double(acc); DrawDisplay(); dnum=acc; } } else { /* op stack is empty, push op and num */ PushOp(keynum); PushNum(dnum); } } /* Two operand functions for rpn calc */ void twof(int keynum) { if (flagINV) { flagINV=0; DrawDisplay(); } if (!entered) return; if (entered==1) parse_double(&dnum); acc = PopNum(); switch(keynum) { case kADD: acc += dnum; break; case kSUB: acc -= dnum; break; case kMUL: acc *= dnum; break; case kDIV: acc /= dnum; break; case kPOW: acc = pow(acc,dnum); break; case kXXY: PushNum(dnum); break; case kMOD: acc = (long)acc % (long)dnum; break; case kAND: acc = (long)acc & (long)dnum; break; case kOR: acc = (long)acc | (long)dnum; break; case kXOR: acc = (long)acc ^ (long)dnum; break; case kSHL: acc = (long)acc << (long)dnum; break; case kSHR: acc = (long)acc >> (long)dnum; break; } format_double(acc); DrawDisplay(); clrdisp++; Dpoint = exponent = 0; entered = 2; lift_enabled = 1; dnum = acc; } void entrf(void) { flagINV=0; if (!entered) return; clrdisp=CLR=1; Dpoint=exponent=0; if (entered==1) parse_double(&dnum); entered=2; memop = kENTR; PushNum(dnum); lift_enabled = 0; } void equf(void) { flagINV=0; if (!entered) return; clrdisp=CLR=1; Dpoint=exponent=0; if (entered==1) parse_double(&dnum); entered=2; PushNum(dnum); while (!isopempty()) { /* do all pending ops */ dnum=PopNum(); acc=PopNum(); lastop=PopOp(); switch (lastop) { case kADD: acc += dnum; break; case kSUB: acc -= dnum; break; case kMUL: acc *= dnum; break; case kDIV: acc /= dnum; break; case kPOW: acc = pow(acc,dnum); break; case kLPAR: flagPAREN--; PushNum(acc); break; case kMOD: acc = (long)acc % (long)dnum; break; case kAND: acc = (long)acc & (long)dnum; break; case kOR: acc = (long)acc | (long)dnum; break; case kXOR: acc = (long)acc ^ (long)dnum; break; case kSHL: acc = (long)acc << (long)dnum; break; case kSHR: acc = (long)acc >> (long)dnum; break; } dnum=acc; PushNum(dnum); } format_double(dnum); DrawDisplay(); } void lparf(void) { flagINV=0; PushOp(kLPAR); flagPAREN++; DrawDisplay(); } void rollf(void) { if (!entered) return; if (entered==1) parse_double(&dnum); entered = 2; lift_enabled = 1; RollNum(flagINV); flagINV=0; clrdisp++; format_double(dnum); DrawDisplay(); } void rparf(void) { flagINV=0; if (!entered) return; if (!flagPAREN) return; clrdisp++; Dpoint=exponent=0; if (entered==1) parse_double(&dnum); entered=2; PushNum(dnum); while (!isopempty() && (lastop=PopOp())!=kLPAR) { /* do all pending ops, back to left paren */ dnum=PopNum(); acc=PopNum(); switch (lastop) { case kADD: acc += dnum; break; case kSUB: acc -= dnum; break; case kMUL: acc *= dnum; break; case kDIV: acc /= dnum; break; case kPOW: acc = pow(acc,dnum); break; case kMOD: acc = (long)acc % (long)dnum; break; case kAND: acc = (long)acc & (long)dnum; break; case kOR: acc = (long)acc | (long)dnum; break; case kXOR: acc = (long)acc ^ (long)dnum; break; case kSHL: acc = (long)acc << (long)dnum; break; case kSHR: acc = (long)acc >> (long)dnum; break; } dnum=acc; PushNum(dnum); } PopNum(); flagPAREN--; entered=2; format_double(dnum); DrawDisplay(); } void drgf(void) { if (flagINV) { if (entered==1) parse_double(&dnum); switch (drgmode) { case DEG: dnum=dnum*M_PI/180.0; break; case RAD: dnum=dnum*200.0/M_PI; break; case GRAD: dnum=dnum*90.0/100.0; break; } entered=2; clrdisp=1; flagINV=0; format_double(dnum); } flagINV=0; drgmode = (drgmode + 1) % 3; switch (drgmode) { case DEG: drg2rad=M_PI / 180.0; rad2drg=180.0 / M_PI; break; case RAD: drg2rad=1.0; rad2drg=1.0; break; case GRAD: drg2rad=M_PI / 200.0; rad2drg=200.0 / M_PI; break; } DrawDisplay(); } void invf(void) { flagINV = ~flagINV; DrawDisplay(); } void memf(int keynum) { memop = keynum; if (entered==1) parse_double(&dnum); entered = 2; clrdisp++; lift_enabled = 0; } void oneop(int keynum) { int i,j; double dtmp; if (entered==1) parse_double(&dnum); entered = 2; switch (keynum) { /* do the actual math fn. */ case kE: if (rpn && memop != kENTR) PushNum(dnum); dnum=M_E; break; case kPI: if (rpn && memop != kENTR) PushNum(dnum); dnum=M_PI; break; case kRECIP: dnum=1.0/dnum; break; case kSQR: flagINV = !flagINV; /* fall through */ case kSQRT: if (flagINV) dnum=dnum*dnum; else dnum=sqrt(dnum); break; case k10X: flagINV = !flagINV; /* fall through */ case kLOG: if (flagINV) dnum=pow(10.0,dnum); else dnum=log10(dnum); break; case kEXP: flagINV = !flagINV; /* fall through */ case kLN: if (flagINV) dnum=exp(dnum); else dnum=log(dnum); break; case kSIN: if (flagINV) dnum=asin(dnum)*rad2drg; else dnum=sin(dnum*drg2rad); break; case kCOS: if (flagINV) dnum=acos(dnum)*rad2drg; else dnum=cos(dnum*drg2rad); break; case kTAN: if (flagINV) dnum=atan(dnum)*rad2drg; else dnum=tan(dnum*drg2rad); break; case kSTO: mem[0]=dnum; flagM=!(mem[0]==0.0); break; case kRCL: if (rpn && lift_enabled) PushNum(dnum); dnum=mem[0]; flagM=!(mem[0]==0.0); break; case kSUM: mem[0]+=dnum; flagM=!(mem[0]==0.0); break; case kEXC: dtmp=dnum; dnum=mem[0]; mem[0]=dtmp; flagM=!(mem[0]==0.0); break; case kFACT: if (floor(dnum)!=dnum || dnum<0.0 || dnum>500.0) { strlcpy(dispstr, "error", sizeof(dispstr)); entered=3; break; } dtmp = floor(dnum); i = dtmp; for (j=1,dnum=1.0; j<=i; j++) dnum*=(float) j; break; case kNOT: dnum = ~(long)dnum; break; case kTRUNC: dnum = trunc(dnum); break; } if (entered==3) { /* error */ DrawDisplay(); return; } memop = keynum; entered=2; clrdisp=1; flagINV=0; lift_enabled = 1; format_double(dnum); DrawDisplay(); } void offf(void) { /* full reset */ ResetCalc(); entered=clrdisp=1; lift_enabled = 0; dnum=mem[0]=0.0; if (rpn) for (int i=1; i < XCALC_MEMORY; i++) mem[i]=0.0; exponent=Dpoint=0; DrawDisplay(); } #define STACKMAX 32 static int opstack[STACKMAX]; static int opsp; static double numstack[STACKMAX]; static int numsp; /*******/ static void PushOp(int op) /*******/ { if (opsp==STACKMAX) { strlcpy(dispstr, "stack error", sizeof(dispstr)); entered=3; } else opstack[opsp++]=op; } /*******/ static int PopOp(void) /*******/ { if (opsp==0) { strlcpy(dispstr, "stack error", sizeof(dispstr)); entered=3; return(kNOP); } else return(opstack[--opsp]); } /*******/ static int isopempty(void) /*******/ { return( opsp ? 0 : 1 ); } #ifdef DEBUG static void showstack(char *string) { fprintf(stderr, "%s: %lf %lf %lf\n", string, numstack[0], numstack[1], numstack[2]); } #endif /*******/ static void PushNum(double num) /*******/ { if (rpn) { numstack[2] = numstack[1]; numstack[1] = numstack[0]; numstack[0] = num; return; } if (numsp==STACKMAX) { strlcpy(dispstr, "stack error", sizeof(dispstr)); entered=3; } else numstack[numsp++]=num; } /*******/ static double PopNum(void) /*******/ { if (rpn) { double tmp = numstack[0]; numstack[0] = numstack[1]; numstack[1] = numstack[2]; return(tmp); } if (numsp==0) { strlcpy(dispstr, "stack error", sizeof(dispstr)); entered=3; return 0.0; } else return(numstack[--numsp]); } /*******/ static void RollNum(int dir) /*******/ { double tmp; if (dir) { /* roll up */ tmp = dnum; dnum = numstack[2]; numstack[2] = numstack[1]; numstack[1] = numstack[0]; numstack[0] = tmp; } else { /* roll down */ tmp = dnum; dnum = numstack[0]; numstack[0] = numstack[1]; numstack[1] = numstack[2]; numstack[2] = tmp; } } /*******/ static void ClearStacks(void) /*******/ { if (rpn) numstack[0] = numstack[1] = numstack[2] = 0.; opsp=numsp=0; } /*******/ static int priority(int op) /*******/ { switch (op) { case kPOW: return(6); case kMUL: case kDIV: case kMOD: return(5); case kADD: case kSUB: return(4); case kSHL: case kSHR: return(3); case kAND: return(2); case kXOR: return(1); case kOR: return(0); } return 0; } /********/ void ResetCalc(void) /********/ { flagM=flagINV=flagPAREN=0; drgmode=DEG; numbase=(!numbase ? 10 : numbase); setflag(XCalc_MEMORY, False); setflag(XCalc_INVERSE, False); setflag(XCalc_PAREN, False); setflag(XCalc_RADIAN, False); setflag(XCalc_GRADAM, False); setflag(XCalc_DEGREE, True); setflag(XCalc_HEX, False); setflag(XCalc_DEC, True); setflag(XCalc_OCT, False); strlcpy(dispstr, "0", sizeof(dispstr)); draw(dispstr); ClearStacks(); drg2rad=M_PI/180.0; rad2drg=180.0/M_PI; }