Home | History | Annotate | Line # | Download | only in units
units.c revision 1.2
      1 /*
      2  * units.c   Copyright (c) 1993 by Adrian Mariano (adrian (at) cam.cornell.edu)
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. The name of the author may not be used to endorse or promote products
     10  *    derived from this software without specific prior written permission.
     11  * Disclaimer:  This software is provided by the author "as is".  The author
     12  * shall not be liable for any damages caused in any way by this software.
     13  *
     14  * I would appreciate (though I do not require) receiving a copy of any
     15  * improvements you might make to this program.
     16  *
     17  *	$Id: units.c,v 1.2 1994/01/29 01:43:47 jtc Exp $
     18  */
     19 
     20 #include <stdio.h>
     21 #include <string.h>
     22 #include <stdlib.h>
     23 
     24 #include "pathnames.h"
     25 
     26 #define VERSION "1.0"
     27 
     28 #ifndef UNITSFILE
     29 #define UNITSFILE _PATH_UNITSLIB
     30 #endif
     31 
     32 #define MAXUNITS 1000
     33 #define MAXPREFIXES 50
     34 
     35 #define MAXSUBUNITS 500
     36 
     37 #define PRIMITIVECHAR '!'
     38 
     39 char *powerstring = "^";
     40 
     41 struct {
     42 	char *uname;
     43 	char *uval;
     44 }      unittable[MAXUNITS];
     45 
     46 struct unittype {
     47 	char *numerator[MAXSUBUNITS];
     48 	char *denominator[MAXSUBUNITS];
     49 	double factor;
     50 };
     51 
     52 struct {
     53 	char *prefixname;
     54 	char *prefixval;
     55 }      prefixtable[MAXPREFIXES];
     56 
     57 
     58 char *NULLUNIT = "";
     59 
     60 int unitcount;
     61 int prefixcount;
     62 
     63 
     64 char *
     65 dupstr(char *str)
     66 {
     67 	char *ret;
     68 
     69 	ret = malloc(strlen(str) + 1);
     70 	if (!ret) {
     71 		fprintf(stderr, "Memory allocation error\n");
     72 		exit(3);
     73 	}
     74 	strcpy(ret, str);
     75 	return (ret);
     76 }
     77 
     78 
     79 void
     80 readerror(int linenum)
     81 {
     82 	fprintf(stderr, "Error in units file '%s' line %d\n", UNITSFILE,
     83 	    linenum);
     84 }
     85 
     86 
     87 void
     88 readunits(char *userfile)
     89 {
     90 	FILE *unitfile;
     91 	char line[80], *lineptr;
     92 	int len, linenum, i;
     93 
     94 	unitcount = 0;
     95 	linenum = 0;
     96 
     97 	if (userfile) {
     98 		unitfile = fopen(userfile, "rt");
     99 		if (!unitfile) {
    100 			fprintf(stderr, "Unable to open units file '%s'\n",
    101 			    userfile);
    102 			exit(1);
    103 		}
    104 	}
    105 	else {
    106 		unitfile = fopen(UNITSFILE, "rt");
    107 		if (!unitfile) {
    108 			char *direc, *env;
    109 			char filename[1000];
    110 			char separator[2];
    111 
    112 			env = getenv("PATH");
    113 			if (env) {
    114 				if (strchr(env, ';'))
    115 					strcpy(separator, ";");
    116 				else
    117 					strcpy(separator, ":");
    118 				direc = strtok(env, separator);
    119 				while (direc) {
    120 					strcpy(filename, "");
    121 					strncat(filename, direc, 999);
    122 					strncat(filename, "/",
    123 					    999 - strlen(filename));
    124 					strncat(filename, UNITSFILE,
    125 					    999 - strlen(filename));
    126 					unitfile = fopen(filename, "rt");
    127 					if (unitfile)
    128 						break;
    129 					direc = strtok(NULL, separator);
    130 				}
    131 			}
    132 			if (!unitfile) {
    133 				fprintf(stderr, "Can't find units file '%s'\n",
    134 				    UNITSFILE);
    135 				exit(1);
    136 			}
    137 		}
    138 	}
    139 	while (!feof(unitfile)) {
    140 		if (!fgets(line, 79, unitfile))
    141 			break;
    142 		linenum++;
    143 		lineptr = line;
    144 		if (*lineptr == '/')
    145 			continue;
    146 		lineptr += strspn(lineptr, " \n\t");
    147 		len = strcspn(lineptr, " \n\t");
    148 		lineptr[len] = 0;
    149 		if (!strlen(lineptr))
    150 			continue;
    151 		if (lineptr[strlen(lineptr) - 1] == '-') { /* it's a prefix */
    152 			if (prefixcount == MAXPREFIXES) {
    153 				fprintf(stderr, "Memory for prefixes exceeded in line %d\n",
    154 				    linenum);
    155 				continue;
    156 			}
    157 			lineptr[strlen(lineptr) - 1] = 0;
    158 			prefixtable[prefixcount].prefixname = dupstr(lineptr);
    159 			for (i = 0; i < prefixcount; i++)
    160 				if (!strcmp(prefixtable[i].prefixname, lineptr)) {
    161 					fprintf(stderr, "Redefinition of prefix '%s' on line %d ignored\n",
    162 					    lineptr, linenum);
    163 					continue;
    164 				}
    165 			lineptr += len + 1;
    166 			if (!strlen(lineptr)) {
    167 				readerror(linenum);
    168 				continue;
    169 			}
    170 			lineptr += strspn(lineptr, " \n\t");
    171 			len = strcspn(lineptr, "\n\t");
    172 			lineptr[len] = 0;
    173 			prefixtable[prefixcount++].prefixval = dupstr(lineptr);
    174 		}
    175 		else {		/* it's not a prefix */
    176 			if (unitcount == MAXUNITS) {
    177 				fprintf(stderr, "Memory for units exceeded in line %d\n",
    178 				    linenum);
    179 				continue;
    180 			}
    181 			unittable[unitcount].uname = dupstr(lineptr);
    182 			for (i = 0; i < unitcount; i++)
    183 				if (!strcmp(unittable[i].uname, lineptr)) {
    184 					fprintf(stderr, "Redefinition of unit '%s' on line %d ignored\n",
    185 					    lineptr, linenum);
    186 					continue;
    187 				}
    188 			lineptr += len + 1;
    189 			lineptr += strspn(lineptr, " \n\t");
    190 			if (!strlen(lineptr)) {
    191 				readerror(linenum);
    192 				continue;
    193 			}
    194 			len = strcspn(lineptr, "\n\t");
    195 			lineptr[len] = 0;
    196 			unittable[unitcount++].uval = dupstr(lineptr);
    197 		}
    198 	}
    199 	fclose(unitfile);
    200 }
    201 
    202 void
    203 initializeunit(struct unittype * theunit)
    204 {
    205 	theunit->factor = 1.0;
    206 	theunit->numerator[0] = theunit->denominator[0] = NULL;
    207 }
    208 
    209 
    210 int
    211 addsubunit(char *product[], char *toadd)
    212 {
    213 	char **ptr;
    214 
    215 	for (ptr = product; *ptr && *ptr != NULLUNIT; ptr++);
    216 	if (ptr >= product + MAXSUBUNITS) {
    217 		fprintf(stderr, "Memory overflow in unit reduction\n");
    218 		return 1;
    219 	}
    220 	if (!*ptr)
    221 		*(ptr + 1) = 0;
    222 	*ptr = dupstr(toadd);
    223 	return 0;
    224 }
    225 
    226 
    227 void
    228 showunit(struct unittype * theunit)
    229 {
    230 	char **ptr;
    231 	int printedslash;
    232 	int counter = 1;
    233 
    234 	printf("\t%.8g", theunit->factor);
    235 	for (ptr = theunit->numerator; *ptr; ptr++) {
    236 		if (ptr > theunit->numerator && **ptr &&
    237 		    !strcmp(*ptr, *(ptr - 1)))
    238 			counter++;
    239 		else {
    240 			if (counter > 1)
    241 				printf("%s%d", powerstring, counter);
    242 			if (**ptr)
    243 				printf(" %s", *ptr);
    244 			counter = 1;
    245 		}
    246 	}
    247 	if (counter > 1)
    248 		printf("%s%d", powerstring, counter);
    249 	counter = 1;
    250 	printedslash = 0;
    251 	for (ptr = theunit->denominator; *ptr; ptr++) {
    252 		if (ptr > theunit->denominator && **ptr &&
    253 		    !strcmp(*ptr, *(ptr - 1)))
    254 			counter++;
    255 		else {
    256 			if (counter > 1)
    257 				printf("%s%d", powerstring, counter);
    258 			if (**ptr) {
    259 				if (!printedslash)
    260 					printf(" /");
    261 				printedslash = 1;
    262 				printf(" %s", *ptr);
    263 			}
    264 			counter = 1;
    265 		}
    266 	}
    267 	if (counter > 1)
    268 		printf("%s%d", powerstring, counter);
    269 	printf("\n");
    270 }
    271 
    272 
    273 void
    274 zeroerror()
    275 {
    276 	fprintf(stderr, "Unit reduces to zero\n");
    277 }
    278 
    279 /*
    280    Adds the specified string to the unit.
    281    Flip is 0 for adding normally, 1 for adding reciprocal.
    282 
    283    Returns 0 for successful addition, nonzero on error.
    284 */
    285 
    286 int
    287 addunit(struct unittype * theunit, char *toadd, int flip)
    288 {
    289 	char *scratch, *savescr;
    290 	char *item;
    291 	char *divider, *subunit, *slash;
    292 	int doingtop;
    293 
    294 	savescr = scratch = dupstr(toadd);
    295 	for (slash = scratch + 1; *slash; slash++)
    296 		if (*slash == '-' &&
    297 		    (tolower(*(slash - 1)) != 'e' ||
    298 		    !strchr(".0123456789", *(slash + 1))))
    299 			*slash = ' ';
    300 	slash = strchr(scratch, '/');
    301 	if (slash)
    302 		*slash = 0;
    303 	doingtop = 1;
    304 	do {
    305 		item = strtok(scratch, " *\t\n/");
    306 		while (item) {
    307 			if (strchr("0123456789.", *item)) { /* item is a number */
    308 				double num;
    309 
    310 				divider = strchr(item, '|');
    311 				if (divider) {
    312 					*divider = 0;
    313 					num = atof(item);
    314 					if (!num) {
    315 						zeroerror();
    316 						return 1;
    317 					}
    318 					if (doingtop ^ flip)
    319 						theunit->factor *= num;
    320 					else
    321 						theunit->factor /= num;
    322 					num = atof(divider + 1);
    323 					if (!num) {
    324 						zeroerror();
    325 						return 1;
    326 					}
    327 					if (doingtop ^ flip)
    328 						theunit->factor /= num;
    329 					else
    330 						theunit->factor *= num;
    331 				}
    332 				else {
    333 					num = atof(item);
    334 					if (!num) {
    335 						zeroerror();
    336 						return 1;
    337 					}
    338 					if (doingtop ^ flip)
    339 						theunit->factor *= num;
    340 					else
    341 						theunit->factor /= num;
    342 
    343 				}
    344 			}
    345 			else {	/* item is not a number */
    346 				int repeat = 1;
    347 
    348 				if (strchr("23456789",
    349 				    item[strlen(item) - 1])) {
    350 					repeat = item[strlen(item) - 1] - '0';
    351 					item[strlen(item) - 1] = 0;
    352 				}
    353 				for (; repeat; repeat--)
    354 					if (addsubunit(doingtop ^ flip ? theunit->numerator : theunit->denominator, item))
    355 						return 1;
    356 			}
    357 			item = strtok(NULL, " *\t/\n");
    358 		}
    359 		doingtop--;
    360 		if (slash) {
    361 			scratch = slash + 1;
    362 		}
    363 		else
    364 			doingtop--;
    365 	} while (doingtop >= 0);
    366 	free(savescr);
    367 	return 0;
    368 }
    369 
    370 
    371 int
    372 compare(const void *item1, const void *item2)
    373 {
    374 	return strcmp(*(char **) item1, *(char **) item2);
    375 }
    376 
    377 
    378 void
    379 sortunit(struct unittype * theunit)
    380 {
    381 	char **ptr;
    382 	int count;
    383 
    384 	for (count = 0, ptr = theunit->numerator; *ptr; ptr++, count++);
    385 	qsort(theunit->numerator, count, sizeof(char *), compare);
    386 	for (count = 0, ptr = theunit->denominator; *ptr; ptr++, count++);
    387 	qsort(theunit->denominator, count, sizeof(char *), compare);
    388 }
    389 
    390 
    391 void
    392 cancelunit(struct unittype * theunit)
    393 {
    394 	char **den, **num;
    395 	int comp;
    396 
    397 	den = theunit->denominator;
    398 	num = theunit->numerator;
    399 
    400 	while (*num && *den) {
    401 		comp = strcmp(*den, *num);
    402 		if (!comp) {
    403 /*      if (*den!=NULLUNIT) free(*den);
    404       if (*num!=NULLUNIT) free(*num);*/
    405 			*den++ = NULLUNIT;
    406 			*num++ = NULLUNIT;
    407 		}
    408 		else if (comp < 0)
    409 			den++;
    410 		else
    411 			num++;
    412 	}
    413 }
    414 
    415 
    416 
    417 
    418 /*
    419    Looks up the definition for the specified unit.
    420    Returns a pointer to the definition or a null pointer
    421    if the specified unit does not appear in the units table.
    422 */
    423 
    424 static char buffer[100];	/* buffer for lookupunit answers with
    425 				   prefixes */
    426 
    427 char *
    428 lookupunit(char *unit)
    429 {
    430 	int i;
    431 	char *copy;
    432 
    433 	for (i = 0; i < unitcount; i++) {
    434 		if (!strcmp(unittable[i].uname, unit))
    435 			return unittable[i].uval;
    436 	}
    437 
    438 	if (unit[strlen(unit) - 1] == '^') {
    439 		copy = dupstr(unit);
    440 		copy[strlen(copy) - 1] = 0;
    441 		for (i = 0; i < unitcount; i++) {
    442 			if (!strcmp(unittable[i].uname, copy)) {
    443 				strcpy(buffer, copy);
    444 				free(copy);
    445 				return buffer;
    446 			}
    447 		}
    448 		free(copy);
    449 	}
    450 	if (unit[strlen(unit) - 1] == 's') {
    451 		copy = dupstr(unit);
    452 		copy[strlen(copy) - 1] = 0;
    453 		for (i = 0; i < unitcount; i++) {
    454 			if (!strcmp(unittable[i].uname, copy)) {
    455 				strcpy(buffer, copy);
    456 				free(copy);
    457 				return buffer;
    458 			}
    459 		}
    460 		if (copy[strlen(copy) - 1] == 'e') {
    461 			copy[strlen(copy) - 1] = 0;
    462 			for (i = 0; i < unitcount; i++) {
    463 				if (!strcmp(unittable[i].uname, copy)) {
    464 					strcpy(buffer, copy);
    465 					free(copy);
    466 					return buffer;
    467 				}
    468 			}
    469 		}
    470 		free(copy);
    471 	}
    472 	for (i = 0; i < prefixcount; i++) {
    473 		if (!strncmp(prefixtable[i].prefixname, unit,
    474 			strlen(prefixtable[i].prefixname))) {
    475 			unit += strlen(prefixtable[i].prefixname);
    476 			if (!strlen(unit) || lookupunit(unit)) {
    477 				strcpy(buffer, prefixtable[i].prefixval);
    478 				strcat(buffer, " ");
    479 				strcat(buffer, unit);
    480 				return buffer;
    481 			}
    482 		}
    483 	}
    484 	return 0;
    485 }
    486 
    487 
    488 
    489 /*
    490    reduces a product of symbolic units to primitive units.
    491    The three low bits are used to return flags:
    492 
    493      bit 0 (1) set on if reductions were performed without error.
    494      bit 1 (2) set on if no reductions are performed.
    495      bit 2 (4) set on if an unknown unit is discovered.
    496 */
    497 
    498 
    499 #define ERROR 4
    500 
    501 int
    502 reduceproduct(struct unittype * theunit, int flip)
    503 {
    504 
    505 	char *toadd;
    506 	char **product;
    507 	int didsomething = 2;
    508 
    509 	if (flip)
    510 		product = theunit->denominator;
    511 	else
    512 		product = theunit->numerator;
    513 
    514 	for (; *product; product++) {
    515 
    516 		for (;;) {
    517 			if (!strlen(*product))
    518 				break;
    519 			toadd = lookupunit(*product);
    520 			if (!toadd) {
    521 				printf("unknown unit '%s'\n", *product);
    522 				return ERROR;
    523 			}
    524 			if (strchr(toadd, PRIMITIVECHAR))
    525 				break;
    526 			didsomething = 1;
    527 			if (*product != NULLUNIT) {
    528 				free(*product);
    529 				*product = NULLUNIT;
    530 			}
    531 			if (addunit(theunit, toadd, flip))
    532 				return ERROR;
    533 		}
    534 	}
    535 	return didsomething;
    536 }
    537 
    538 
    539 /*
    540    Reduces numerator and denominator of the specified unit.
    541    Returns 0 on success, or 1 on unknown unit error.
    542 */
    543 
    544 int
    545 reduceunit(struct unittype * theunit)
    546 {
    547 	int ret;
    548 
    549 	ret = 1;
    550 	while (ret & 1) {
    551 		ret = reduceproduct(theunit, 0) | reduceproduct(theunit, 1);
    552 		if (ret & 4)
    553 			return 1;
    554 	}
    555 	return 0;
    556 }
    557 
    558 
    559 int
    560 compareproducts(char **one, char **two)
    561 {
    562 	while (*one || *two) {
    563 		if (!*one && *two != NULLUNIT)
    564 			return 1;
    565 		if (!*two && *one != NULLUNIT)
    566 			return 1;
    567 		if (*one == NULLUNIT)
    568 			one++;
    569 		else if (*two == NULLUNIT)
    570 			two++;
    571 		else if (strcmp(*one, *two))
    572 			return 1;
    573 		else
    574 			one++, two++;
    575 	}
    576 	return 0;
    577 }
    578 
    579 
    580 /* Return zero if units are compatible, nonzero otherwise */
    581 
    582 int
    583 compareunits(struct unittype * first, struct unittype * second)
    584 {
    585 	return
    586 	compareproducts(first->numerator, second->numerator) ||
    587 	compareproducts(first->denominator, second->denominator);
    588 }
    589 
    590 
    591 int
    592 completereduce(struct unittype * unit)
    593 {
    594 	if (reduceunit(unit))
    595 		return 1;
    596 	sortunit(unit);
    597 	cancelunit(unit);
    598 	return 0;
    599 }
    600 
    601 
    602 void
    603 showanswer(struct unittype * have, struct unittype * want)
    604 {
    605 	if (compareunits(have, want)) {
    606 		printf("conformability error\n");
    607 		showunit(have);
    608 		showunit(want);
    609 	}
    610 	else
    611 		printf("\t* %.8g\n\t/ %.8g\n", have->factor / want->factor,
    612 		    want->factor / have->factor);
    613 }
    614 
    615 
    616 void
    617 usage()
    618 {
    619 	fprintf(stderr, "\nunits [-f unitsfile] [-q] [-v] [from-unit to-unit]\n");
    620 	fprintf(stderr, "\n    -f specify units file\n");
    621 	fprintf(stderr, "    -q supress prompting (quiet)\n");
    622 	fprintf(stderr, "    -v print version number\n");
    623 	exit(3);
    624 }
    625 
    626 
    627 void
    628 main(int argc, char **argv)
    629 {
    630 
    631 	struct unittype have, want;
    632 	char havestr[81], wantstr[81];
    633 	char optchar;
    634 	char *userfile = 0;
    635 	int quiet = 0;
    636 
    637 	extern char *optarg;
    638 	extern int optind;
    639 
    640 	while (EOF != (optchar = getopt(argc, argv, "vqf:"))) {
    641 		switch (optchar) {
    642 		case 'f':
    643 			userfile = optarg;
    644 			break;
    645 		case 'q':
    646 			quiet = 1;
    647 			break;
    648 		case 'v':
    649 			fprintf(stderr, "\n  units version %s  Copyright (c) 1993 by Adrian Mariano\n",
    650 			    VERSION);
    651 			fprintf(stderr, "                    This program may be freely distributed\n");
    652 			usage();
    653 		default:
    654 			usage();
    655 			break;
    656 		}
    657 	}
    658 
    659 	if (optind != argc - 2 && optind != argc)
    660 		usage();
    661 
    662 	readunits(userfile);
    663 
    664 	if (optind == argc - 2) {
    665 		strcpy(havestr, argv[optind]);
    666 		strcpy(wantstr, argv[optind + 1]);
    667 		initializeunit(&have);
    668 		addunit(&have, havestr, 0);
    669 		completereduce(&have);
    670 		initializeunit(&want);
    671 		addunit(&want, wantstr, 0);
    672 		completereduce(&want);
    673 		showanswer(&have, &want);
    674 	}
    675 	else {
    676 		if (!quiet)
    677 			printf("%d units, %d prefixes\n\n", unitcount,
    678 			    prefixcount);
    679 		for (;;) {
    680 			do {
    681 				initializeunit(&have);
    682 				if (!quiet)
    683 					printf("You have: ");
    684 				if (!fgets(havestr, 80, stdin)) {
    685 					if (!quiet);
    686 					putchar('\n');
    687 					exit(0);
    688 				}
    689 			} while (addunit(&have, havestr, 0) ||
    690 			    completereduce(&have));
    691 			do {
    692 				initializeunit(&want);
    693 				if (!quiet)
    694 					printf("You want: ");
    695 				if (!fgets(wantstr, 80, stdin)) {
    696 					if (!quiet)
    697 						putchar('\n');
    698 					exit(0);
    699 				}
    700 			} while (addunit(&want, wantstr, 0) ||
    701 			    completereduce(&want));
    702 			showanswer(&have, &want);
    703 		}
    704 	}
    705 }
    706