xenocara/app/xcalc/math.c
2013-03-09 15:04:53 +00:00

941 lines
19 KiB
C

/*
* 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"
#ifdef _CRAY /* kludge around Cray STDC compiler */
double (*log_p)() = log;
#define log ((*log_p))
double (*exp_p)() = exp;
#define exp ((*exp_p))
double (*sqrt_p)() = sqrt;
#define sqrt ((*sqrt_p))
double (*log10_p)() = log10;
#define log10 ((*log10_p))
double (*atan2_p)() = atan2;
#define atan2 ((*atan2_p))
double (*asin_p)() = asin;
#define asin ((*asin_p))
double (*acos_p)() = acos;
#define acos ((*acos_p))
double (*atan_p)() = atan;
#define atan ((*atan_p))
double (*sin_p)() = sin;
#define sin ((*sin_p))
double (*cos_p)() = cos;
#define cos ((*cos_p))
double (*tan_p)() = tan;
#define tan ((*tan_p))
double (*pow_p)() = pow;
#define pow ((*pow_p))
#endif /* _CRAY */
#ifndef PI /* sometimes defined in math.h */
#define PI 3.14159265358979
#endif
#define E 2.71828182845904
#define MAXDISP 11
#define DEG 0 /* DRG mode. used for trig calculations */
#define RAD 1
#define GRAD 2
#define min(a,b) ((a) < (b) ? (a) : (b))
#define max(a,b) ((a) > (b) ? (a) : (b))
#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; /* display flags */
static double drg2rad=PI/180.0; /* Conversion factors for trig funcs */
static double rad2drg=180.0/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);
/*
* 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 (const char *src, const char *fmt, double *dp)
{
int olderrno = errno;
(void) sscanf (src, fmt, dp);
errno = olderrno;
return;
}
/*********************************/
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)
strcpy(dispstr, "math error");
else if (SignalKind == SIGILL)
strcpy(dispstr, "illegal operand");
entered=3;
DrawDisplay();
return;
}
/*ARGSUSED*/
signal_t fperr(int sig)
{
#if defined(SYSV) || defined(SVR4) || defined(linux)
signal(SIGFPE, fperr);
#endif
SignalKind = sig;
longjmp(env,1);
}
/* for VAX BSD4.3 */
/*ARGSUSED*/
signal_t 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) {
strcpy(dispstr,"error");
DrawDisplay();
entered=3;
errno=0;
}
#endif
}
/*-------------------------------------------------------------------------*/
static void
DrawDisplay(void)
{
if ((int) strlen(dispstr) > 12) { /* strip out some decimal digits */
char tmp[32];
char *estr = index(dispstr,'e'); /* search for exponent part */
if (!estr) dispstr[12]='\0'; /* no exp, just trunc. */
else {
if ((int) strlen(estr) <= 4)
sprintf(tmp,"%.8s",dispstr); /* leftmost 8 chars */
else
sprintf(tmp,"%.7s",dispstr); /* leftmost 7 chars */
strcat (tmp,estr); /* plus exponent */
strcpy (dispstr,tmp);
}
}
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));
}
/*-------------------------------------------------------------------------*/
void
numeric(int keynum)
{
char st[2];
int cell = 0;
flagINV=0;
if (rpn && (memop == kSTO || memop == kRCL || memop == kSUM)) {
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];
sprintf(dispstr, "%.8g", 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;
switch (keynum){
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: st[0] = '8'; break;
case kNINE: st[0] = '9'; break;
case kZERO: st[0] = '0'; break;
}
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
{
strcpy(dispstr, "0");
dnum = 0.0;
clrdisp++;
flagINV = 0;
}
DrawDisplay();
}
void
decf(void)
{
flagINV=0;
if (clrdisp) {
if (rpn && lift_enabled)
PushNum(dnum);
strcpy(dispstr,"0");
}
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);
strcpy(dispstr, rpn ? "1" : "0");
}
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;
strcpy(dispstr,"0");
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[32];
sprintf(tmp,"-%s",dispstr);
strcpy(dispstr,tmp);
}
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())
(void) PopOp(); /* replace the prev op */
PushOp(keynum); /* with the new one */
return;
}
if (entered==1)
parse_double(dispstr,"%lf",&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;
}
PushNum(acc);
PushOp(keynum);
sprintf(dispstr,"%.8g",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(dispstr, "%lf", &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);
}
sprintf(dispstr, "%.8g", 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(dispstr,"%lf",&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(dispstr,"%lf",&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;
}
dnum=acc;
PushNum(dnum);
}
sprintf(dispstr,"%.8g",dnum);
DrawDisplay();
}
void
lparf(void)
{
flagINV=0;
PushOp(kLPAR);
flagPAREN++;
DrawDisplay();
}
void
rollf(void)
{
if (!entered)
return;
if (entered==1)
parse_double(dispstr, "%lf", &dnum);
entered = 2;
lift_enabled = 1;
RollNum(flagINV);
flagINV=0;
clrdisp++;
sprintf(dispstr, "%.8g", dnum);
DrawDisplay();
}
void
rparf(void)
{
flagINV=0;
if (!entered)
return;
if (!flagPAREN)
return;
clrdisp++;
Dpoint=exponent=0;
if (entered==1)
parse_double(dispstr,"%lf",&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;
}
dnum=acc;
PushNum(dnum);
}
(void) PopNum();
flagPAREN--;
entered=2;
sprintf(dispstr,"%.8g",dnum);
DrawDisplay();
}
void
drgf(void)
{
if (flagINV) {
if (entered==1)
parse_double(dispstr,"%lf",&dnum);
switch (drgmode) {
case DEG: dnum=dnum*PI/180.0; break;
case RAD: dnum=dnum*200.0/PI; break;
case GRAD: dnum=dnum*90.0/100.0; break;
}
entered=2;
clrdisp=1;
flagINV=0;
sprintf(dispstr,"%.8g",dnum);
}
flagINV=0;
drgmode = (drgmode + 1) % 3;
switch (drgmode) {
case DEG: drg2rad=PI / 180.0;
rad2drg=180.0 / PI;
break;
case RAD: drg2rad=1.0;
rad2drg=1.0;
break;
case GRAD: drg2rad=PI / 200.0;
rad2drg=200.0 / PI;
break;
}
DrawDisplay();
}
void
invf(void)
{
flagINV = ~flagINV;
DrawDisplay();
}
void
memf(int keynum)
{
memop = keynum;
if (entered==1)
parse_double(dispstr,"%lf",&dnum);
entered = 2;
clrdisp++;
lift_enabled = 0;
}
void
oneop(int keynum)
{
int i,j;
double dtmp;
if (entered==1)
parse_double(dispstr,"%lf",&dnum);
entered = 2;
switch (keynum) { /* do the actual math fn. */
case kE: if (rpn && memop != kENTR) PushNum(dnum); dnum=E; break;
case kPI: if (rpn && memop != kENTR) PushNum(dnum); dnum=PI; break;
case kRECIP: dnum=1.0/dnum; break;
case kSQR: flagINV = !flagINV; /* fall through to */
case kSQRT: if (flagINV) dnum=dnum*dnum;
else dnum=sqrt(dnum);
break;
case k10X: flagINV = !flagINV; /* fall through to */
case kLOG: if (flagINV) dnum=pow(10.0,dnum);
else dnum=log10(dnum);
break;
case kEXP: flagINV = !flagINV; /* fall through to */
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) {
strcpy(dispstr,"error");
entered=3;
break;
}
dtmp = floor(dnum); i = dtmp;
for (j=1,dnum=1.0; j<=i; j++)
dnum*=(float) j;
break;
}
if (entered==3) { /* error */
DrawDisplay();
return;
}
memop = keynum;
entered=2;
clrdisp=1;
flagINV=0;
lift_enabled = 1;
sprintf(dispstr,"%.8g",dnum);
DrawDisplay();
}
void
offf(void)
{
/* full reset */
int i;
ResetCalc();
entered=clrdisp=1;
lift_enabled = 0;
dnum=mem[0]=0.0;
if (rpn)
for (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) {strcpy(dispstr,"stack error"); entered=3;}
else opstack[opsp++]=op;
}
/*******/
static int
PopOp(void)
/*******/
{
if (opsp==0) {
strcpy(dispstr,"stack error");
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) {
strcpy(dispstr,"stack error");
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) {
strcpy(dispstr,"stack error");
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(2);
case kMUL:
case kDIV: return(1);
case kADD:
case kSUB: return(0);
}
return 0;
}
/********/
void
ResetCalc(void)
/********/
{
flagM=flagINV=flagPAREN=0; drgmode=DEG;
setflag(XCalc_MEMORY, False);
setflag(XCalc_INVERSE, False);
setflag(XCalc_PAREN, False);
setflag(XCalc_RADIAN, False);
setflag(XCalc_GRADAM, False);
setflag(XCalc_DEGREE, True);
strcpy(dispstr,"0");
draw(dispstr);
ClearStacks();
drg2rad=PI/180.0;
rad2drg=180.0/PI;
}