/* 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 <stdio.h>
#include <math.h>
#include "compiler.h"
#ifdef __STDHEADERS__
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#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; i<MAXDIM; i++)
n->di[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; j<MAXDIM; j++)
newdim->dimension[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; j<MAXDIM; j++) /* set dimension */
newdim->dimension[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:
<name> <conversion factor> <dimension/unit specifier> */
{
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; j<MAXDIM; j++)
newunit->dimension[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; y<MAXDIM; y++)
n->di[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; y<MAXDIM; y++)
n->di[y] += (int)( pwr * per * io * u->dimension[y] );
break; /* continue with outer while loop */
} else { /* search list of prefixes */
int x;
for (x=0; x<PREFIXES; x++)
if ( strncmp(t,prefix[x].name,strlen(prefix[x].name)) ==0)
break;
if (x<PREFIXES) {
if (q==1) {
printf("Err: Dimension expected. [p]\n");
return (-1);
}
q=0;
strcpy(t, t + strlen(prefix[x].name));
/* was midstr(t,strlen(prefix[x].name),strlen(t),t);*/
pf *= prefix[x].scale;
} else { /* not a prefix or a unit */
return (q); /* unknown unit/prefix so stop */
} /* end else */
} /* end else */
} /* end else */
} /* end inner for */
} /* end outer for */
return -1;
} /* end evaldim() */
EXPORT struct dimstruct *dimension_number(i)
int i;
{ /* look up dimension number i (0 is the first one) */
struct dimstruct *d;
d = dimension_list;
while (d) {
if (!(i--))
return d;
d = d->next;
}
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; i<MAXDIM; i++) { /* Display positive powers */
if (d[i]<0)
flag = flag2 = TRUE; /* indicate a negative power exists */
else if (d[i]>0) {
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; i<MAXDIM; i++)
if (d[i]<0) {
fprintf(fp," %s",(dimension_number(i))->name);
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; j<MAXDIM; j++)
if (n->di[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; j<MAXDIM; j++)
if (u->dimension[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; j<MAXDIM; j++)
if (n->di[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; i<MAXDIM; i++) /* Generate a compound unit */
if (n->di[i]>0)
putunit(s,i,n->di[i]);
else
if (n->di[i]<0)
flag = TRUE;
if (flag) {
strcat(s, "PER ");
for (i=0; i<MAXDIM; i++)
if (n->di[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; j<MAXDIM; j++)
if ( n->di[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; i<MAXDIM; i++)
if (n->di[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; i<MAXDIM; i++)
if (a->di[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; i<MAXDIM; i++)
d->di[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; i<MAXDIM; i++)
if (d.di[i]!=0) {
printf("%s DIMENSION INCOMPATABILITY.\n",(dimension_number(i))->name);
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");
}
syntax highlighted by Code2HTML, v. 0.9.1