/* Convert - unit conversion module This module handles unit conversion & manipulation. Copyright (C) 1990 Marty White 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include "compiler.h" #ifdef __STDHEADERS__ #include #include #include #endif #include "physcalc.h" #include "physdecl.h" #ifdef __PROTOTYPES__ LOCAL void getline(FILE *fp, char *buf); LOCAL int evalxpon(char *s); LOCAL void fixplural(char *s); LOCAL int output_dims(FILE *fp, int const d[MAXDIM]); LOCAL void putunit(char *s, int d, int p); #endif #define RESERVESIZE 10000 /* size of buffer for loaded data */ #define PREFIXES 25 /* Number of prefixes */ /* Prefixes are private to this file */ LOCAL struct { /* Prefixes data */ char *name; double scale; } prefix[PREFIXES] = { "EXA", 1E18, "PETA", 1E15, "TERA", 1E12, "GIGA", 1E9, "MEGA", 1E6, "MYRIA",1E4, "KILO", 1000, "HECTO",100, "DECA", 10, "DECI", 0.1, "CENTI",0.01, "MILLI",0.001, "MICRO",1E-6, "NANO", 1E-9, "PICO", 1E-12, "FEMTO",1E-15, "ATTO", 1E-18, "E", 1E18, /* Abbreviations (M not used - too ambiguous) */ "T", 1E12, "G", 1E9, "K", 1000, "C", 0.01, "N", 1E-9, "F", 1E-15, "A", 1E-18 }; LOCAL struct unitstruct { /* Unit list (private to this file) */ struct unitstruct *next; char *name; double factor; /* conversion factor */ int dimension[ MAXDIM ]; /* Dimensionality */ } *units=NULL, *lastunit=NULL; EXPORT struct dimstruct *dimension_list=NULL,*last_fundamental_dim=NULL, *last_compound_dim=NULL; /*-----Functions-----*/ LOCAL void getline(fp,buf) /* Get a line from the file & doctor it up */ FILE *fp; char *buf; { char *c; int noerr; do { noerr = (int) fgets(buf,198,fp); c=buf; while (*c) { if (*c==';') { /* Chop line off at a semicolon */ *c='\0'; break; } c++; } trimspc(buf); } while (*buf=='\0' && noerr); #if 0 if (noerr) strupr(buf); else *buf = '\0'; #endif } EXPORT void erasednum(n) /* erase number & it's dimensions */ DNUM *n; { int i; n->num = 1.0; for (i=0; idi[i]=0; } EXPORT void define_dimension(s) char const *s; { /* Define a dimension contained in string s. One of 2 kinds: Fundamental - just a name Compound - a name followed by a definition in terms of previously defined dimensions */ /* Spaces have been trimmed from s */ static char memerr[]="Out of memory. Dimension not defined.\n"; struct dimstruct *newdim; char buf[SMALLBUF]; int j; static int fundamental_cnt = 0; strupr(s); scan_symbol(&s,buf); newdim = dimension_list; while (newdim) { if (!strcmp(buf,newdim->name)) { if (echo) printf("Duplicate dimension name.\n"); return; } newdim = newdim->next; } if ( (newdim=malloc(sizeof(struct dimstruct))) == NULL ) { printf(memerr); return; } if ( (newdim->name=strdup(buf))==NULL ) { printf(memerr); free(newdim); return; } skipspc(s); if (*s) { /* This must be a compound dimension definition */ DNUM n; erasednum(&n); evaldim(&s,1,&n); for (j=0; jdimension[j] = n.di[j]; newdim -> next = NULL; if (last_compound_dim) last_compound_dim->next = newdim; last_compound_dim = newdim; if (last_fundamental_dim && !last_fundamental_dim->next) last_fundamental_dim->next = newdim; } else { /* This is a simple definition */ if (fundamental_cnt == MAXDIM) { printf("There are too many fundamental dimensions. Dimension not defined.\n"); free(newdim->name); free(newdim); return; } for (j=0; jdimension[j] = 0; newdim->dimension[fundamental_cnt++] = 1; if (last_fundamental_dim) { newdim->next = last_fundamental_dim->next; last_fundamental_dim->next = newdim; } else newdim->next = NULL; last_fundamental_dim = newdim; } if (!dimension_list) dimension_list = newdim; if (echo) printf("Dimension defined.\n"); } EXPORT void define_unit(s) char const *s; /* Define unit as contained in s: */ { static char memerr[]="Out of memory. Unit not defined.\n"; struct unitstruct *newunit; char buf[SMALLBUF]; int j; DNUM n; strupr(s); scan_symbol(&s,buf); newunit = units; while (newunit) { if (!strcmp(buf,newunit->name)) { if (echo) printf("Duplicate unit.\n"); return; } newunit = newunit->next; } if ( (newunit=malloc(sizeof(struct unitstruct)))==NULL ) { printf(memerr); return; } if ( (newunit->name=strdup(buf))==NULL ) { printf(memerr); free(newunit); return; } skipspc(s); if (! *s) { if (echo) printf("Missing conversion factor.\n"); free(newunit->name); free(newunit); return; } erasednum(&n); sscanf(s,"%lf%*s",&n.num); /* read in factor */ /* n.num = 1.0 / n.num; */ /* Oct-94 */ while (!isspace(*s)) s++; s++; if (! *s) { if (echo) printf("Missing dimension specifier.\n"); free(newunit->name); free(newunit); return; } evaldim(&s,1,&n); newunit->factor = 1.0 / /* Oct-94 */ n.num; for (j=0; jdimension[j] = n.di[j]; newunit->next = NULL; if (lastunit) { lastunit->next = newunit; lastunit = newunit; } else lastunit = newunit; if (!units) units = newunit; if (echo) printf("Unit defined.\n"); } EXPORT void load_data(s) char const *s; { /* read in lines from fp and do each as if typed from console */ FILE *fp; char buf[SMALLBUF]; char *fullpath; int oldecho; #ifdef TRACE int oldtrace; #endif fullpath = malloc( strlen( SHAREDIR ) + strlen( s ) + 1 ); bcopy(SHAREDIR, fullpath, strlen(SHAREDIR)); (void) strcat(fullpath, s); if ((fp = fopen(fullpath,"r"))==NULL) { printf("Can't open %s\n",s); return; } oldecho = echo; #ifdef TRACE oldtrace = trace; /*trace = FALSE;*/ #endif echo = FALSE; while (TRUE) { getline(fp,buf); if (!buf[0]) break; do_cmd(buf); } echo = oldecho; #ifdef TRACE trace = trace || oldtrace; #endif fclose(fp); } /* These pointers are used to mark the end of auto-loaded data, so that stuff defined since calling remember_old() can be saved independent of previously loaded information. */ LOCAL struct dimstruct *old_last_fund_dim, *old_last_compound_dim; LOCAL struct unitstruct *old_lastunit; LOCAL struct varstruct *old_var; LOCAL struct ufstruct *old_userfunc; EXPORT void remember_old() { /* Set pointers to the current ends of the 4 data lists - dimensions, units, variables, and functions */ struct varstruct *v; struct ufstruct *u; old_last_fund_dim = last_fundamental_dim; old_last_compound_dim = last_compound_dim; old_lastunit = lastunit; v = var; while (v->next) v = v->next; old_var = v; u = userfunc; while (u) u = u->next; old_userfunc = u; } EXPORT void save_data(fname, all) char const *fname; int all; /* if all, then save all data. If not, save only data entered after the default data */ { FILE *fp; struct dimstruct *d; struct unitstruct *u; int compound = FALSE; if ( (fp = fopen(fname,"w"))==NULL ) { printf("Can't open %s\n",fname); return; } /* Write Dimensions */ d = all ? dimension_list : old_last_fund_dim->next; while (d) { if (d == last_fundamental_dim->next) { compound = TRUE; if (!all) { d = old_last_compound_dim -> next; continue; } } fprintf(fp,"\nDIMENSION %s",d->name); if (compound) output_dims(fp,d->dimension); d = d->next; } /* Write Units */ u = all ? units : old_lastunit->next; while (u) { fprintf(fp,"\nUNIT %s %lg",u->name,u->factor); output_dims(fp,u->dimension); u = u->next; } fprintf(fp,"\n"); /* Write Variables & functions */ output_list(fp, all ? var->next : old_var->next, (all || !old_userfunc) ? userfunc : old_userfunc, TRUE); fclose(fp); } LOCAL int evalxpon(s) char *s; /* Returns integer exponent, & truncates string if there is one */ { int p; char *t; p=1; if (t=strchr(s,'^')) { sscanf(t+1,"%d",&p); *t = '\0'; } return p; } /* end evalxpon */ LOCAL void fixplural(s) /* Change word to singular */ char *s; { /* Change the given word to singular (if it is plural). Make exceptions for the words "INCHES" and "CALORIES" */ int i; i=strlen(s); if ( (i>5) && (strcmp(&s[i-6],"INCHES")==0) ) { s[i-2]='\0'; return; } if ( (i>3) && (strcmp(&s[i-3],"IES")==0) && strcmp(&s[i-8],"CALORIES")!=0) { s[i-3]='Y'; s[i-2]='\0'; return; } if (s[i-1]=='S' && s[i-2]!='S' && i>1) s[i-1]='\0'; } /* end fixplural() */ EXPORT int evaldim(s,io,n) /* evaluate a unit or dimension, return error code */ char const **s; /* Ptr to string ptr of str to eval */ int io; /* flag for converting to or from given dimension (-1 or 1) */ DNUM *n; /* adjust n accordingly */ { /* This function is the guts of unit conversion. It takes a handle to a string and tries to determine what kind of unit or dimension was given. It can evaluate METERS^2 PER SEC or DISTANCE PER TIME, but not a mixed expression like METERS PER TIME. When searching for a match to a word, search for dimensions, then units, then prefixes. "PER" is a special keyword which reverses whether units are multiplied or divided. */ int q, per; char const *s2; q = -1; /* q is type of expression evaluated: 0 = a specific unit (like meters), 1 = a dimension (like distance per time) -1 = an error occured */ per = 1; /* PER flag (1 or -1) */ s2 = *s; for (;;) { /* Outer dimension-processing loop */ char t[NAMELEN]; double pf, pwr; *s = s2; skipspc(*s); if (**s=='\0') /* Check for end-of-string */ return (q); /* Strip dimension off of s into t */ scan_symbol(&s2,t); if (*s2=='^') { strcat(t,"^"); s2++; scan_symbol(&s2,t+strlen(t)); } strupr(t); if (strncmp(t,"PER",3)==0 /*|| t[0]=='/'*/) { /* Evaluate 'PER' */ per = -per; continue; } if ((pwr=evalxpon(t))==0) { /* Evaluate exponent if any */ printf("Err: Zero exponent.\n"); return (-1); } pf = 1.0; for (;;) { /* inner prefix-proccessing loop */ struct dimstruct *d; d = dimension_list; /* search list of dimensions */ while (d) { if (strcmp(t,d->name)==0) break; d = d->next; } if (d) { /* found a dimension */ int y; if (q==0) { printf("Err: Unit expected. [d]\n"); return (-1); } q=1; for (y=0; ydi[y] += (int) ( pwr * per * io * d->dimension[y]); break; /* continue with outer while loop */ } else { struct unitstruct *u; char t1[NAMELEN]; /* t1 is depluraized version of t */ strcpy(t1, t); fixplural(t1); u = units; /* search list of units */ while (u) { if ( strcmp(t,u->name)==0 || strcmp(t1, u->name)==0 ) break; u = u->next; } if (u) { /* found a unit */ double z1,z2,z3; int y; if (q==1) { printf("Err: Dimension expected. [u]\n"); return (-1); } q=0; z1 = u->factor / pf; z2 = pwr * per * io; z3 = pow(z1,z2); n->num /= z3; for (y=0; ydi[y] += (int)( pwr * per * io * u->dimension[y] ); break; /* continue with outer while loop */ } else { /* search list of prefixes */ int x; for (x=0; xnext; } return NULL; } LOCAL int output_dims(fp,d) /* Output dimension list to a stream */ FILE *fp; int const d[MAXDIM]; { int i,flag,flag2; flag = flag2 = FALSE; for (i=0; i0) { fprintf(fp," %s",(dimension_number(i))->name); if (d[i]>1) fprintf(fp,"^%d",d[i]); flag2 = TRUE; } } if (flag) { /* display negative powers */ fprintf(fp," PER"); for (i=0; iname); if (d[i]<(-1)) fprintf(fp,"^%d",- d[i]); } } return flag2; } EXPORT void showdims(n) /* Display fundamental dimensions of a unit */ DNUM const *n; { int j,flag,flag2; struct dimstruct *d; flag2 = output_dims(stdout,n->di); /* Display compund unit (if any) */ if (d = last_fundamental_dim) { while (d = d->next) { for (j=flag=0; jdi[j]!=d->dimension[j]) { flag=1; break; } if (!flag) { printf(" (%s)",d->name); break; } } } if (!flag2) printf("No dimension."); printf("\n"); } LOCAL void putunit(s,d,p) /* Append the name for a unit of sole dimension d to string s (used only by generate_unit) */ char *s; /* Buffer for name */ int d,p; /* d = dimension, p = power */ { int j,flag; char buf[NAMELEN]; struct unitstruct *u; u = units; while (u) { flag = TRUE; for (j=0; jdimension[j] != (j==d) ) { flag = FALSE; break; } if (flag && u->factor==1.0) break; u = u->next; } if (!flag) printf("Error in putunit()\n"); strcat(s, u->name); if (p>1) { sprintf(buf,"^%d",p); strcat(s,buf); } strcat(s," "); } EXPORT void generate_unit(n,s) /* Create a unit specifier of the given dimensions */ DNUM const *n; /* Type of unit to generate */ char *s; /* String to store results in */ { /* Will return a null string if there are no dimensions */ int i,j,flag; struct unitstruct *u; *s = '\0'; u = units; while (u) { /* Search for a compound unit that matches */ flag = TRUE; for (j=0; jdi[j] != u->dimension[j]) { flag = FALSE; break; } if (flag && u->factor==1.0) { strcpy(s, u->name); return; } u = u->next; } flag = FALSE; for (i=0; idi[i]>0) putunit(s,i,n->di[i]); else if (n->di[i]<0) flag = TRUE; if (flag) { strcat(s, "PER "); for (i=0; idi[i]<0) putunit(s,i,- n->di[i]); } } EXPORT void showunits(n) /* display all units of type n */ DNUM const *n; { int j,flag; struct unitstruct *u; u = units; while (u) { flag=0; for (j=0; jdi[j] != u->dimension[j] ) { flag=1; break; } if (!flag) printf("%-20s",u->name); u = u->next; } printf("\n"); } EXPORT void show_all_units() /* dump a list of all dimensions & units to screen */ { int j=0; struct dimstruct *d; struct unitstruct *u; printf("Dimensions:\n"); d = dimension_list; while (d) { printf("%-20s",d->name); if (++j==88) { if (pause_for_user()==27) break; j=0; } d = d->next; } j += 8 - (j%4); printf("\nUnits:\n"); u = units; while (u) { printf("%-20s",u->name); if (++j>=88) { /* Pause for a screenfull */ if (pause_for_user()==27) break; j=0; } u = u->next; } printf("\n"); } EXPORT int has_dims(n) /* Does a dimensional number actually have dimension? */ DNUM const *n; { int i; for (i=0; idi[i]) return TRUE; return FALSE; } EXPORT int check_equ_dim(a,b) /* Are a & b the same dimension? */ DNUM const *a,*b; { int i; for (i=0; idi[i] != b->di[i]) return FALSE; return TRUE; } EXPORT void copydnum(d,s) /* copy the dimensions of s to d */ DNUM *d; DNUM const *s; { int i; d->num = s->num; for (i=0; idi[i] = s->di[i]; } EXPORT void querry(s) /* Called when user types a '?' */ char const *s; /* Display dimensions & units as requested */ { DNUM n; erasednum(&n); if (*s) { evaldim(&s,1,&n); showdims(&n); showunits(&n); } else show_all_units(); } EXPORT void printanswer(n,s) /* Print answer, asking for unit conversion if needed */ NODEP n; char const *s; { /* Use regular expression printer except for dimensioned numbers */ if (n->type==NNODE) { if (has_dims((DNUM *)(n->data))) { int i,flag; char buf[SMALLBUF],*t; DNUM d; for (;;) { copydnum(&d,&n->data->dmn); if (s) { strcpy(buf,s); s=NULL; } else { showdims(&d); printf("Convert to: "); fgets(buf, sizeof(buf), stdin); trimspc(buf); if (buf[0]=='?') { showdims(&d); showunits(&d); generate_unit(&d,buf); printf("Default unit is %s\n",buf); continue; } } if (buf[0]=='\0') { /* use default */ generate_unit(&d,buf); break; } else { /* convert to specified unit */ t = buf; i = evaldim(&t,-1,&d); /* Evaluates destination unit & ajusts d */ skipspc(t); if (i || *t) { printf("Invalid unit specifier.\n"); continue; } flag = TRUE; for (i=0; iname); flag = FALSE; } if (flag) /* if conversion succesfull, exit while */ break; } } printf(/*"Answer: "*/ "%lg %s\n",d.num,buf); return; } /* end if has dim's */ } /* if NNODE, but no dims, fall thru */ /*printf("Answer: ");*/ /* Not an NNODE */ printexpr(n); printf("\n"); }