1 /* $NetBSD: units.c,v 1.25 2014/01/07 02:07:09 joerg Exp $ */
4 * units.c Copyright (c) 1993 by Adrian Mariano (adrian@cam.cornell.edu)
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. The name of the author may not be used to endorse or promote products
12 * derived from this software without specific prior written permission.
13 * Disclaimer: This software is provided by the author "as is". The author
14 * shall not be liable for any damages caused in any way by this software.
16 * I would appreciate (though I do not require) receiving a copy of any
17 * improvements you might make to this program.
28 #include "pathnames.h"
33 #define UNITSFILE _PATH_UNITSLIB
37 #define MAXPREFIXES 50
39 #define MAXSUBUNITS 500
41 #define PRIMITIVECHAR '!'
43 static int precision
= 8; /* for printf with "%.*g" format */
45 static const char *errprefix
= NULL
; /* if not NULL, then prepend this
46 * to error messages and send them to
47 * stdout instead of stderr.
50 static const char *powerstring
= "^";
55 } unittable
[MAXUNITS
];
58 const char *numerator
[MAXSUBUNITS
];
59 const char *denominator
[MAXSUBUNITS
];
64 const char *prefixname
;
65 const char *prefixval
;
66 } prefixtable
[MAXPREFIXES
];
69 static const char *NULLUNIT
= "";
72 static int prefixcount
;
75 static int addsubunit(const char *[], const char *);
76 static int addunit(struct unittype
*, const char *, int);
77 static void cancelunit(struct unittype
*);
78 static int compare(const void *, const void *);
79 static int compareproducts(const char **, const char **);
80 static int compareunits(struct unittype
*, struct unittype
*);
81 static int compareunitsreciprocal(struct unittype
*, struct unittype
*);
82 static int completereduce(struct unittype
*);
83 static void initializeunit(struct unittype
*);
84 static void readerror(int);
85 static void readunits(const char *);
86 static int reduceproduct(struct unittype
*, int);
87 static int reduceunit(struct unittype
*);
88 static void showanswer(struct unittype
*, struct unittype
*);
89 static void showunit(struct unittype
*);
90 static void sortunit(struct unittype
*);
91 __dead
static void usage(void);
92 static void zeroerror(void);
93 static char *dupstr(const char *);
94 static const char *lookupunit(const char *);
97 dupstr(const char *str
)
103 err(3, "Memory allocation error");
108 static __printflike(1, 2) void
109 mywarnx(const char *fmt
, ...)
115 /* warn to stdout, with errprefix prepended */
116 printf("%s", errprefix
);
127 readerror(int linenum
)
129 mywarnx("Error in units file '%s' line %d", UNITSFILE
, linenum
);
134 readunits(const char *userfile
)
137 char line
[80], *lineptr
;
138 int len
, linenum
, i
, isdup
;
144 unitfile
= fopen(userfile
, "rt");
146 err(1, "Unable to open units file '%s'", userfile
);
149 unitfile
= fopen(UNITSFILE
, "rt");
155 env
= getenv("PATH");
157 if (strchr(env
, ';'))
158 strlcpy(separator
, ";",
161 strlcpy(separator
, ":",
163 direc
= strtok(env
, separator
);
165 strlcpy(filename
, "", sizeof(filename
));
166 strlcat(filename
, direc
,
168 strlcat(filename
, "/",
170 strlcat(filename
, UNITSFILE
,
172 unitfile
= fopen(filename
, "rt");
175 direc
= strtok(NULL
, separator
);
179 errx(1, "Can't find units file '%s'",
183 while (!feof(unitfile
)) {
184 if (!fgets(line
, 79, unitfile
))
190 lineptr
+= strspn(lineptr
, " \n\t");
191 len
= strcspn(lineptr
, " \n\t");
193 if (!strlen(lineptr
))
195 if (lineptr
[strlen(lineptr
) - 1] == '-') { /* it's a prefix */
196 if (prefixcount
== MAXPREFIXES
) {
198 "Memory for prefixes exceeded in line %d",
202 lineptr
[strlen(lineptr
) - 1] = 0;
203 for (isdup
= 0, i
= 0; i
< prefixcount
; i
++) {
204 if (!strcmp(prefixtable
[i
].prefixname
,
212 "Redefinition of prefix '%s' on line %d ignored",
216 prefixtable
[prefixcount
].prefixname
= dupstr(lineptr
);
218 if (!strlen(lineptr
)) {
222 lineptr
+= strspn(lineptr
, " \n\t");
223 len
= strcspn(lineptr
, "\n\t");
225 prefixtable
[prefixcount
++].prefixval
= dupstr(lineptr
);
227 else { /* it's not a prefix */
228 if (unitcount
== MAXUNITS
) {
229 mywarnx("Memory for units exceeded in line %d",
233 for (isdup
= 0, i
= 0; i
< unitcount
; i
++) {
234 if (!strcmp(unittable
[i
].uname
, lineptr
)) {
241 "Redefinition of unit '%s' on line %d ignored",
245 unittable
[unitcount
].uname
= dupstr(lineptr
);
247 lineptr
+= strspn(lineptr
, " \n\t");
248 if (!strlen(lineptr
)) {
252 len
= strcspn(lineptr
, "\n\t");
254 unittable
[unitcount
++].uval
= dupstr(lineptr
);
261 initializeunit(struct unittype
* theunit
)
263 theunit
->factor
= 1.0;
264 theunit
->numerator
[0] = theunit
->denominator
[0] = NULL
;
268 addsubunit(const char *product
[], const char *toadd
)
272 for (ptr
= product
; *ptr
&& *ptr
!= NULLUNIT
; ptr
++);
273 if (ptr
>= product
+ MAXSUBUNITS
) {
274 mywarnx("Memory overflow in unit reduction");
279 *ptr
= dupstr(toadd
);
284 showunit(struct unittype
* theunit
)
290 printf("\t%.*g", precision
, theunit
->factor
);
291 for (ptr
= theunit
->numerator
; *ptr
; ptr
++) {
292 if (ptr
> theunit
->numerator
&& **ptr
&&
293 !strcmp(*ptr
, *(ptr
- 1)))
297 printf("%s%d", powerstring
, counter
);
304 printf("%s%d", powerstring
, counter
);
307 for (ptr
= theunit
->denominator
; *ptr
; ptr
++) {
308 if (ptr
> theunit
->denominator
&& **ptr
&&
309 !strcmp(*ptr
, *(ptr
- 1)))
313 printf("%s%d", powerstring
, counter
);
324 printf("%s%d", powerstring
, counter
);
331 mywarnx("Unit reduces to zero");
335 Adds the specified string to the unit.
336 Flip is 0 for adding normally, 1 for adding reciprocal.
338 Returns 0 for successful addition, nonzero on error.
342 addunit(struct unittype
* theunit
, const char *toadd
, int flip
)
344 char *scratch
, *savescr
;
346 char *divider
, *slash
;
349 savescr
= scratch
= dupstr(toadd
);
350 for (slash
= scratch
+ 1; *slash
; slash
++)
352 (tolower((unsigned char)*(slash
- 1)) != 'e' ||
353 !strchr(".0123456789", *(slash
+ 1))))
355 slash
= strchr(scratch
, '/');
360 item
= strtok(scratch
, " *\t\n/");
362 if (strchr("0123456789.", *item
)) {
363 /* item starts with a number */
367 divider
= strchr(item
, '|');
370 num
= strtod(item
, &endptr
);
375 if (endptr
!= divider
) {
376 /* "6foo|2" is an error */
377 mywarnx("Junk before '|'");
381 theunit
->factor
*= num
;
383 theunit
->factor
/= num
;
384 num
= strtod(divider
+ 1, &endptr
);
390 theunit
->factor
/= num
;
392 theunit
->factor
*= num
;
394 /* "6|2foo" is like "6|2 foo" */
400 num
= strtod(item
, &endptr
);
406 theunit
->factor
*= num
;
408 theunit
->factor
/= num
;
410 /* "3foo" is like "3 foo" */
416 else { /* item is not a number */
419 if (strchr("23456789",
420 item
[strlen(item
) - 1])) {
421 repeat
= item
[strlen(item
) - 1] - '0';
422 item
[strlen(item
) - 1] = 0;
424 for (; repeat
; repeat
--)
425 if (addsubunit(doingtop
^ flip
? theunit
->numerator
: theunit
->denominator
, item
))
428 item
= strtok(NULL
, " *\t/\n");
436 } while (doingtop
>= 0);
442 compare(const void *item1
, const void *item2
)
444 return strcmp(*(const char * const *) item1
,
445 *(const char * const *) item2
);
449 sortunit(struct unittype
* theunit
)
454 for (count
= 0, ptr
= theunit
->numerator
; *ptr
; ptr
++, count
++);
455 qsort(theunit
->numerator
, count
, sizeof(char *), compare
);
456 for (count
= 0, ptr
= theunit
->denominator
; *ptr
; ptr
++, count
++);
457 qsort(theunit
->denominator
, count
, sizeof(char *), compare
);
461 cancelunit(struct unittype
* theunit
)
463 const char **den
, **num
;
466 den
= theunit
->denominator
;
467 num
= theunit
->numerator
;
469 while (*num
&& *den
) {
470 comp
= strcmp(*den
, *num
);
472 /* if (*den!=NULLUNIT) free(*den);
473 if (*num!=NULLUNIT) free(*num);*/
488 Looks up the definition for the specified unit.
489 Returns a pointer to the definition or a null pointer
490 if the specified unit does not appear in the units table.
493 static char buffer
[100]; /* buffer for lookupunit answers with
497 lookupunit(const char *unit
)
502 for (i
= 0; i
< unitcount
; i
++) {
503 if (!strcmp(unittable
[i
].uname
, unit
))
504 return unittable
[i
].uval
;
507 if (unit
[strlen(unit
) - 1] == '^') {
509 copy
[strlen(copy
) - 1] = 0;
510 for (i
= 0; i
< unitcount
; i
++) {
511 if (!strcmp(unittable
[i
].uname
, copy
)) {
512 strlcpy(buffer
, copy
, sizeof(buffer
));
519 if (unit
[strlen(unit
) - 1] == 's') {
521 copy
[strlen(copy
) - 1] = 0;
522 for (i
= 0; i
< unitcount
; i
++) {
523 if (!strcmp(unittable
[i
].uname
, copy
)) {
524 strlcpy(buffer
, copy
, sizeof(buffer
));
529 if (copy
[strlen(copy
) - 1] == 'e') {
530 copy
[strlen(copy
) - 1] = 0;
531 for (i
= 0; i
< unitcount
; i
++) {
532 if (!strcmp(unittable
[i
].uname
, copy
)) {
533 strlcpy(buffer
, copy
, sizeof(buffer
));
541 for (i
= 0; i
< prefixcount
; i
++) {
542 if (!strncmp(prefixtable
[i
].prefixname
, unit
,
543 strlen(prefixtable
[i
].prefixname
))) {
544 unit
+= strlen(prefixtable
[i
].prefixname
);
545 if (!strlen(unit
) || lookupunit(unit
)) {
546 strlcpy(buffer
, prefixtable
[i
].prefixval
,
548 strlcat(buffer
, " ", sizeof(buffer
));
549 strlcat(buffer
, unit
, sizeof(buffer
));
560 reduces a product of symbolic units to primitive units.
561 The three low bits are used to return flags:
563 bit 0 (1) set on if reductions were performed without error.
564 bit 1 (2) set on if no reductions are performed.
565 bit 2 (4) set on if an unknown unit is discovered.
572 reduceproduct(struct unittype
* theunit
, int flip
)
576 const char **product
;
577 int didsomething
= 2;
580 product
= theunit
->denominator
;
582 product
= theunit
->numerator
;
584 for (; *product
; product
++) {
587 if (!strlen(*product
))
589 toadd
= lookupunit(*product
);
591 mywarnx("Unknown unit '%s'", *product
);
594 if (strchr(toadd
, PRIMITIVECHAR
))
597 if (*product
!= NULLUNIT
) {
598 free(__UNCONST(*product
));
601 if (addunit(theunit
, toadd
, flip
))
610 Reduces numerator and denominator of the specified unit.
611 Returns 0 on success, or 1 on unknown unit error.
615 reduceunit(struct unittype
* theunit
)
621 ret
= reduceproduct(theunit
, 0) | reduceproduct(theunit
, 1);
629 compareproducts(const char **one
, const char **two
)
631 while (*one
|| *two
) {
632 if (!*one
&& *two
!= NULLUNIT
)
634 if (!*two
&& *one
!= NULLUNIT
)
636 if (*one
== NULLUNIT
)
638 else if (*two
== NULLUNIT
)
640 else if (*one
&& *two
&& strcmp(*one
, *two
))
649 /* Return zero if units are compatible, nonzero otherwise */
652 compareunits(struct unittype
* first
, struct unittype
* second
)
655 compareproducts(first
->numerator
, second
->numerator
) ||
656 compareproducts(first
->denominator
, second
->denominator
);
660 compareunitsreciprocal(struct unittype
* first
, struct unittype
* second
)
663 compareproducts(first
->numerator
, second
->denominator
) ||
664 compareproducts(first
->denominator
, second
->numerator
);
669 completereduce(struct unittype
* unit
)
671 if (reduceunit(unit
))
680 showanswer(struct unittype
* have
, struct unittype
* want
)
682 if (compareunits(have
, want
)) {
683 if (compareunitsreciprocal(have
, want
)) {
684 printf("conformability error\n");
688 printf("\treciprocal conversion\n");
689 printf("\t* %.*g\n\t/ %.*g\n",
690 precision
, 1 / (have
->factor
* want
->factor
),
691 precision
, want
->factor
* have
->factor
);
695 printf("\t* %.*g\n\t/ %.*g\n",
696 precision
, have
->factor
/ want
->factor
,
697 precision
, want
->factor
/ have
->factor
);
701 listunits(int expand
)
703 struct unittype theunit
;
711 * send error and warning messages to stdout,
712 * and make them look like comments.
717 printf("/ expand=%d precision=%d unitcount=%d prefixcount=%d\n",
718 expand
, precision
, unitcount
, prefixcount
);
721 /* 1. Dump all primitive units, e.g. "m !a!", "kg !b!", ... */
722 printf("/ Primitive units\n");
723 for (i
= 0; i
< unitcount
; i
++) {
724 thename
= unittable
[i
].uname
;
725 thedefn
= unittable
[i
].uval
;
726 if (thedefn
[0] == PRIMITIVECHAR
) {
727 printf("%s\t%s\n", thename
, thedefn
);
731 /* 2. Dump all prefixes, e.g. "yotta- 1e24", "zetta- 1e21", ... */
732 printf("/ Prefixes\n");
733 for (i
= 0; i
< prefixcount
; i
++) {
734 printexpansion
= expand
;
735 thename
= prefixtable
[i
].prefixname
;
736 thedefn
= prefixtable
[i
].prefixval
;
739 * prefix names are sometimes identical to unit
740 * names, so we have to expand thedefn instead of
743 initializeunit(&theunit
);
744 if (addunit(&theunit
, thedefn
, 0) != 0
745 || completereduce(&theunit
) != 0) {
748 mywarnx("Error in prefix '%s-'", thename
);
751 if (printexpansion
) {
752 printf("%s-", thename
);
755 printf("%s-\t%s\n", thename
, thedefn
);
758 /* 3. Dump all other units. */
759 printf("/ Other units\n");
760 for (i
= 0; i
< unitcount
; i
++) {
761 printexpansion
= expand
;
762 thename
= unittable
[i
].uname
;
763 thedefn
= unittable
[i
].uval
;
764 if (thedefn
[0] == PRIMITIVECHAR
)
768 * expand thename, not thedefn, so that
769 * we can catch errors in the name itself.
770 * e.g. a name that contains a hyphen
771 * will be interpreted as multiplication.
773 initializeunit(&theunit
);
774 if (addunit(&theunit
, thename
, 0) != 0
775 || completereduce(&theunit
) != 0) {
778 mywarnx("Error in unit '%s'", thename
);
781 if (printexpansion
) {
782 printf("%s", thename
);
785 printf("%s\t%s\n", thename
, thedefn
);
789 mywarnx("Definitions with errors: %d", errors
);
790 return (errors
? 1 : 0);
797 "\nunits [-Llqv] [-f filename] [[count] from-unit to-unit]\n");
798 fprintf(stderr
, "\n -f specify units file\n");
799 fprintf(stderr
, " -L list units in standardized base units\n");
800 fprintf(stderr
, " -l list units\n");
801 fprintf(stderr
, " -q suppress prompting (quiet)\n");
802 fprintf(stderr
, " -v print version number\n");
807 main(int argc
, char **argv
)
810 struct unittype have
, want
;
811 char havestr
[81], wantstr
[81];
813 const char *userfile
= 0;
814 int list
= 0, listexpand
= 0;
817 while ((optchar
= getopt(argc
, argv
, "lLvqf:")) != -1) {
834 fprintf(stderr
, "\n units version %s Copyright (c) 1993 by Adrian Mariano\n",
836 fprintf(stderr
, " This program may be freely distributed\n");
847 if ((argc
!= 3 && argc
!= 2 && argc
!= 0)
848 || (list
&& argc
!= 0))
852 errprefix
= "/ "; /* set this before reading the file */
857 return listunits(listexpand
);
860 strlcpy(havestr
, argv
[0], sizeof(havestr
));
861 strlcat(havestr
, " ", sizeof(havestr
));
862 strlcat(havestr
, argv
[1], sizeof(havestr
));
869 strlcpy(havestr
, argv
[0], sizeof(havestr
));
870 strlcpy(wantstr
, argv
[1], sizeof(wantstr
));
871 initializeunit(&have
);
872 addunit(&have
, havestr
, 0);
873 completereduce(&have
);
874 initializeunit(&want
);
875 addunit(&want
, wantstr
, 0);
876 completereduce(&want
);
877 showanswer(&have
, &want
);
881 printf("%d units, %d prefixes\n\n", unitcount
,
885 initializeunit(&have
);
887 printf("You have: ");
888 if (!fgets(havestr
, 80, stdin
)) {
893 } while (addunit(&have
, havestr
, 0) ||
894 completereduce(&have
));
896 initializeunit(&want
);
898 printf("You want: ");
899 if (!fgets(wantstr
, 80, stdin
)) {
904 } while (addunit(&want
, wantstr
, 0) ||
905 completereduce(&want
));
906 showanswer(&have
, &want
);