2 * units.c Copyright (c) 1993 by Adrian Mariano (adrian@cam.cornell.edu)
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
14 * I would appreciate (though I do not require) receiving a copy of any
15 * improvements you might make to this program.
17 * $FreeBSD: src/usr.bin/units/units.c,v 1.11 2008/08/16 16:27:41 dwmalone Exp $
18 * $DragonFly: src/usr.bin/units/units.c,v 1.4 2006/08/13 19:19:44 swildner Exp $
28 #include "pathnames.h"
33 #define UNITSFILE _PATH_UNITSLIB
37 #define MAXPREFIXES 100
39 #define MAXSUBUNITS 500
41 #define PRIMITIVECHAR '!'
43 const char *powerstring
= "^";
48 } unittable
[MAXUNITS
];
51 char *numerator
[MAXSUBUNITS
];
52 char *denominator
[MAXSUBUNITS
];
61 } prefixtable
[MAXPREFIXES
];
76 static int addsubunit(char *[], char *);
77 static int addunit(struct unittype
*, char *, int, int);
78 static void cancelunit(struct unittype
*);
79 static int compare(const void *, const void *);
80 static int compareproducts(char **, char **);
81 static int compareunits(struct unittype
*, struct unittype
*);
82 static int completereduce(struct unittype
*);
83 static char *dupstr(const char *);
84 static void initializeunit(struct unittype
*);
85 static char *lookupunit(const char *);
86 static void readunits(const char *);
87 static int reduceproduct(struct unittype
*, int);
88 static int reduceunit(struct unittype
*);
89 static void showanswer(struct unittype
*, struct unittype
*);
90 static void showunit(struct unittype
*);
91 static void sortunit(struct unittype
*);
92 static void usage(void);
93 static void zeroerror(void);
97 dupstr(const char *str
)
101 ret
= malloc(strlen(str
) + 1);
103 errx(3, "memory allocation error");
110 readunits(const char *userfile
)
113 char line
[512], *lineptr
;
120 unitfile
= fopen(userfile
, "rt");
122 errx(1, "unable to open units file '%s'", userfile
);
125 unitfile
= fopen(UNITSFILE
, "rt");
130 env
= getenv("PATH");
132 direc
= strtok(env
, SEPARATOR
);
134 snprintf(filename
, sizeof(filename
),
135 "%s/%s", direc
, UNITSFILE
);
136 unitfile
= fopen(filename
, "rt");
139 direc
= strtok(NULL
, SEPARATOR
);
143 errx(1, "can't find units file '%s'", UNITSFILE
);
146 while (!feof(unitfile
)) {
147 if (!fgets(line
, sizeof(line
), unitfile
))
153 lineptr
+= strspn(lineptr
, " \n\t");
154 len
= strcspn(lineptr
, " \n\t");
156 if (!strlen(lineptr
))
158 if (lineptr
[strlen(lineptr
) - 1] == '-') { /* it's a prefix */
159 if (prefixcount
== MAXPREFIXES
) {
160 warnx("memory for prefixes exceeded in line %d", linenum
);
163 lineptr
[strlen(lineptr
) - 1] = 0;
164 prefixtable
[prefixcount
].prefixname
= dupstr(lineptr
);
165 for (i
= 0; i
< prefixcount
; i
++)
166 if (!strcmp(prefixtable
[i
].prefixname
, lineptr
)) {
167 warnx("redefinition of prefix '%s' on line %d ignored",
172 lineptr
+= strspn(lineptr
, " \n\t");
173 len
= strcspn(lineptr
, "\n\t");
175 warnx("unexpected end of prefix on line %d",
180 prefixtable
[prefixcount
++].prefixval
= dupstr(lineptr
);
182 else { /* it's not a prefix */
183 if (unitcount
== MAXUNITS
) {
184 warnx("memory for units exceeded in line %d", linenum
);
187 unittable
[unitcount
].uname
= dupstr(lineptr
);
188 for (i
= 0; i
< unitcount
; i
++)
189 if (!strcmp(unittable
[i
].uname
, lineptr
)) {
190 warnx("redefinition of unit '%s' on line %d ignored",
195 lineptr
+= strspn(lineptr
, " \n\t");
196 if (!strlen(lineptr
)) {
197 warnx("unexpected end of unit on line %d",
201 len
= strcspn(lineptr
, "\n\t");
203 unittable
[unitcount
++].uval
= dupstr(lineptr
);
210 initializeunit(struct unittype
* theunit
)
212 theunit
->numerator
[0] = theunit
->denominator
[0] = NULL
;
213 theunit
->factor
= 1.0;
214 theunit
->offset
= 0.0;
215 theunit
->quantity
= 0;
220 addsubunit(char *product
[], char *toadd
)
224 for (ptr
= product
; *ptr
&& *ptr
!= NULLUNIT
; ptr
++);
225 if (ptr
>= product
+ MAXSUBUNITS
) {
226 warnx("memory overflow in unit reduction");
231 *ptr
= dupstr(toadd
);
237 showunit(struct unittype
* theunit
)
243 printf("\t%.8g", theunit
->factor
);
245 printf("&%.8g", theunit
->offset
);
246 for (ptr
= theunit
->numerator
; *ptr
; ptr
++) {
247 if (ptr
> theunit
->numerator
&& **ptr
&&
248 !strcmp(*ptr
, *(ptr
- 1)))
252 printf("%s%d", powerstring
, counter
);
259 printf("%s%d", powerstring
, counter
);
262 for (ptr
= theunit
->denominator
; *ptr
; ptr
++) {
263 if (ptr
> theunit
->denominator
&& **ptr
&&
264 !strcmp(*ptr
, *(ptr
- 1)))
268 printf("%s%d", powerstring
, counter
);
279 printf("%s%d", powerstring
, counter
);
287 warnx("unit reduces to zero");
291 Adds the specified string to the unit.
292 Flip is 0 for adding normally, 1 for adding reciprocal.
293 Quantity is 1 if this is a quantity to be converted rather than a pure unit.
295 Returns 0 for successful addition, nonzero on error.
299 addunit(struct unittype
* theunit
, char *toadd
, int flip
, int quantity
)
301 char *scratch
, *savescr
;
303 char *divider
, *slash
, *offset
;
309 savescr
= scratch
= dupstr(toadd
);
310 for (slash
= scratch
+ 1; *slash
; slash
++)
312 (tolower(*(slash
- 1)) != 'e' ||
313 !strchr(".0123456789", *(slash
+ 1))))
315 slash
= strchr(scratch
, '/');
320 item
= strtok(scratch
, " *\t\n/");
322 if (strchr("0123456789.", *item
)) { /* item is a number */
323 double num
, offsetnum
;
326 theunit
->quantity
= 1;
328 offset
= strchr(item
, '&');
331 offsetnum
= atof(offset
+1);
335 divider
= strchr(item
, '|');
343 if (doingtop
^ flip
) {
344 theunit
->factor
*= num
;
345 theunit
->offset
*= num
;
347 theunit
->factor
/= num
;
348 theunit
->offset
/= num
;
350 num
= atof(divider
+ 1);
355 if (doingtop
^ flip
) {
356 theunit
->factor
/= num
;
357 theunit
->offset
/= num
;
359 theunit
->factor
*= num
;
360 theunit
->offset
*= num
;
369 if (doingtop
^ flip
) {
370 theunit
->factor
*= num
;
371 theunit
->offset
*= num
;
373 theunit
->factor
/= num
;
374 theunit
->offset
/= num
;
378 theunit
->offset
+= offsetnum
;
380 else { /* item is not a number */
383 if (strchr("23456789",
384 item
[strlen(item
) - 1])) {
385 repeat
= item
[strlen(item
) - 1] - '0';
386 item
[strlen(item
) - 1] = 0;
388 for (; repeat
; repeat
--)
389 if (addsubunit(doingtop
^ flip
? theunit
->numerator
: theunit
->denominator
, item
))
392 item
= strtok(NULL
, " *\t/\n");
400 } while (doingtop
>= 0);
407 compare(const void *item1
, const void *item2
)
409 return strcmp(*(const char * const *)item1
, *(const char * const *)item2
);
414 sortunit(struct unittype
* theunit
)
419 for (count
= 0, ptr
= theunit
->numerator
; *ptr
; ptr
++, count
++);
420 qsort(theunit
->numerator
, count
, sizeof(char *), compare
);
421 for (count
= 0, ptr
= theunit
->denominator
; *ptr
; ptr
++, count
++);
422 qsort(theunit
->denominator
, count
, sizeof(char *), compare
);
427 cancelunit(struct unittype
* theunit
)
432 den
= theunit
->denominator
;
433 num
= theunit
->numerator
;
435 while (*num
&& *den
) {
436 comp
= strcmp(*den
, *num
);
438 /* if (*den!=NULLUNIT) free(*den);
439 if (*num!=NULLUNIT) free(*num);*/
454 Looks up the definition for the specified unit.
455 Returns a pointer to the definition or a null pointer
456 if the specified unit does not appear in the units table.
459 static char buffer
[100]; /* buffer for lookupunit answers with
463 lookupunit(const char *unit
)
468 for (i
= 0; i
< unitcount
; i
++) {
469 if (!strcmp(unittable
[i
].uname
, unit
))
470 return unittable
[i
].uval
;
473 if (unit
[strlen(unit
) - 1] == '^') {
475 copy
[strlen(copy
) - 1] = 0;
476 for (i
= 0; i
< unitcount
; i
++) {
477 if (!strcmp(unittable
[i
].uname
, copy
)) {
478 strlcpy(buffer
, copy
, sizeof(buffer
));
485 if (unit
[strlen(unit
) - 1] == 's') {
487 copy
[strlen(copy
) - 1] = 0;
488 for (i
= 0; i
< unitcount
; i
++) {
489 if (!strcmp(unittable
[i
].uname
, copy
)) {
490 strlcpy(buffer
, copy
, sizeof(buffer
));
495 if (copy
[strlen(copy
) - 1] == 'e') {
496 copy
[strlen(copy
) - 1] = 0;
497 for (i
= 0; i
< unitcount
; i
++) {
498 if (!strcmp(unittable
[i
].uname
, copy
)) {
499 strlcpy(buffer
, copy
, sizeof(buffer
));
507 for (i
= 0; i
< prefixcount
; i
++) {
508 size_t len
= strlen(prefixtable
[i
].prefixname
);
509 if (!strncmp(prefixtable
[i
].prefixname
, unit
, len
)) {
510 if (!strlen(unit
+ len
) || lookupunit(unit
+ len
)) {
511 snprintf(buffer
, sizeof(buffer
), "%s %s",
512 prefixtable
[i
].prefixval
, unit
+ len
);
523 reduces a product of symbolic units to primitive units.
524 The three low bits are used to return flags:
526 bit 0 (1) set on if reductions were performed without error.
527 bit 1 (2) set on if no reductions are performed.
528 bit 2 (4) set on if an unknown unit is discovered.
535 reduceproduct(struct unittype
* theunit
, int flip
)
540 int didsomething
= 2;
543 product
= theunit
->denominator
;
545 product
= theunit
->numerator
;
547 for (; *product
; product
++) {
550 if (!strlen(*product
))
552 toadd
= lookupunit(*product
);
554 printf("unknown unit '%s'\n", *product
);
557 if (strchr(toadd
, PRIMITIVECHAR
))
560 if (*product
!= NULLUNIT
) {
564 if (addunit(theunit
, toadd
, flip
, 0))
573 Reduces numerator and denominator of the specified unit.
574 Returns 0 on success, or 1 on unknown unit error.
578 reduceunit(struct unittype
* theunit
)
584 ret
= reduceproduct(theunit
, 0) | reduceproduct(theunit
, 1);
593 compareproducts(char **one
, char **two
)
595 while (*one
|| *two
) {
596 if (!*one
&& *two
!= NULLUNIT
)
598 if (!*two
&& *one
!= NULLUNIT
)
600 if (*one
== NULLUNIT
)
602 else if (*two
== NULLUNIT
)
604 else if (strcmp(*one
, *two
))
613 /* Return zero if units are compatible, nonzero otherwise */
616 compareunits(struct unittype
* first
, struct unittype
* second
)
619 compareproducts(first
->numerator
, second
->numerator
) ||
620 compareproducts(first
->denominator
, second
->denominator
);
625 completereduce(struct unittype
* unit
)
627 if (reduceunit(unit
))
636 showanswer(struct unittype
* have
, struct unittype
* want
)
638 if (compareunits(have
, want
)) {
639 printf("conformability error\n");
643 else if (have
->offset
!= want
->offset
) {
645 printf("WARNING: conversion of non-proportional quantities.\n");
649 (have
->factor
+ have
->offset
-want
->offset
)/want
->factor
);
651 printf(" (-> x*%.8g %+.8g)\n\t (<- y*%.8g %+.8g)\n",
652 have
->factor
/ want
->factor
,
653 (have
->offset
-want
->offset
)/want
->factor
,
654 want
->factor
/ have
->factor
,
655 (want
->offset
- have
->offset
)/have
->factor
);
658 printf("\t* %.8g\n\t/ %.8g\n", have
->factor
/ want
->factor
,
659 want
->factor
/ have
->factor
);
667 "usage: units [-f unitsfile] [-q] [-v] [from-unit to-unit]\n");
673 main(int argc
, char **argv
)
676 struct unittype have
, want
;
677 char havestr
[81], wantstr
[81];
682 while ((optchar
= getopt(argc
, argv
, "vqf:")) != -1) {
691 fprintf(stderr
, "\n units version %s Copyright (c) 1993 by Adrian Mariano\n",
693 fprintf(stderr
, " This program may be freely distributed\n");
701 if (optind
!= argc
- 2 && optind
!= argc
)
706 if (optind
== argc
- 2) {
707 strlcpy(havestr
, argv
[optind
], sizeof(havestr
));
708 strlcpy(wantstr
, argv
[optind
+ 1], sizeof(wantstr
));
709 initializeunit(&have
);
710 addunit(&have
, havestr
, 0, 1);
711 completereduce(&have
);
712 initializeunit(&want
);
713 addunit(&want
, wantstr
, 0, 1);
714 completereduce(&want
);
715 showanswer(&have
, &want
);
719 printf("%d units, %d prefixes\n", unitcount
,
723 initializeunit(&have
);
725 printf("You have: ");
726 if (!fgets(havestr
, sizeof(havestr
), stdin
)) {
731 } while (addunit(&have
, havestr
, 0, 1) ||
732 completereduce(&have
));
734 initializeunit(&want
);
736 printf("You want: ");
737 if (!fgets(wantstr
, sizeof(wantstr
), stdin
)) {
742 } while (addunit(&want
, wantstr
, 0, 1) ||
743 completereduce(&want
));
744 showanswer(&have
, &want
);