Index: usr.bin/units/units.1 =================================================================== --- usr.bin/units/units.1 +++ usr.bin/units/units.1 @@ -1,115 +1,127 @@ .\" $FreeBSD$ -.Dd August 12, 2017 +.Dd January 30, 2019 .Dt UNITS 1 .Os .Sh NAME .Nm units -.Nd conversion program +.Nd conversion calculator .Sh SYNOPSIS .Nm +.Op Fl ehqtUVv .Op Fl f Ar filename .Op Fl H Ar filename -.Op Fl qvUV -.Op Ar from-unit to-unit -.Sh OPTIONS +.Op Fl o Ar format +.Op Ar from to +.Sh DESCRIPTION +The +.Nm +program converts quantities expressed in various scales to +their equivalents in other scales. +It can only +handle multiplicative or affine scale changes. +.Pp +.Nm +can work interactively by prompting +the user for input +.Pq see Sx EXAMPLES +or non-interactively, providing a conversion for given arguments +.Ar from +and +.Ar to . +.Pp The following options are available: -.Bl -tag -width indent -.It Fl h \&No , Fl -help -Show an overview of options -.It Fl f Ar filename \&No , Fl -file Ar filename +.Bl -tag -width "from to" +.It Fl e , Fl -exponential +Same as +.Fl o +.Cm %6e +(see the description of the +.Fl o +flag). +.It Fl f Ar filename , Fl -file Ar filename Specify the name of the units data file to load. -.It Fl H Ar filename \&No , Fl -history Ar filename +This option may be specified multiple times. +.It Fl H Ar filename , Fl -history Ar filename Ignored, for compatibility with GNU units. -.It Fl e , Fl -exponential -Behave as if -o '%6e' was typed. -.It Fl q \&No , Fl -quiet +.It Fl h , Fl -help +Show an overview of options. +.It Fl o Ar format , Fl -output-format Ar format +Select the output format string by which numbers are printed. +Defaults to +.Dq Li %.8g . +.It Fl q , Fl -quiet Suppress prompting of the user for units and the display of statistics about the number of units loaded. -.It Fl U \&No , Fl -unitsfile -If the default unit file exists prints its location. -If not, print -.Qo -Units data file not found -.Qc -.It Fl t \&No , Fl -terse -Only print the result. This is used when calling +.It Fl t , Fl -terse +Only print the result. +This is used when calling .Nm from other programs for easy to parse results. -.It Fl v \&No , Fl -verbose +.It Fl U , Fl -unitsfile +Print the location of the default unit file if it exists. +Otherwise, print +.Dq Units data file not found . +.It Fl V , Fl -version +Print the version number +.Pq which is always just Dq Fx units , +usage, and then exit. +.It Fl v , Fl -verbose Print the units in the conversion output. Be more verbose in general. -.It Fl o Ar format \&No , Fl -output-format Ar format -Select the output format string by which numbers are printed. -.It Fl V \&No , Fl -version -Print the version number, usage, and then exit. -.It Ar from-unit to-unit +.It Ar from to Allow a single unit conversion to be done directly from the command line. The program will not print prompts. -It will print out the -result of the single specified conversion. +It will print out the result of the single specified conversion. +Both arguments, i.e., +.Ar from +and +.Ar to , +can be just a unit +.Pq e.g., Dq Cm cm , +a quantity +.Pq e.g., Dq Cm 42 , +or a quantity with a unit +.Pq e.g., Dq Cm 42 cm .El -.Sh DESCRIPTION -The -.Nm -program converts quantities expressed in various scales to -their equivalents in other scales. -The -.Nm -program can only -handle multiplicative or affine scale changes. -It works interactively by prompting -the user for input: -.Bd -literal - You have: meters - You want: feet - * 3.2808399 - / 0.3048 - - You have: cm^3 - You want: gallons - * 0.00026417205 - / 3785.4118 - - You have: meters/s - You want: furlongs/fortnight - * 6012.8848 - / 0.00016630952 - - You have: 1|2 inch - You want: cm - * 1.27 - / 0.78740157 - - You have: 85 degF - You want: degC - 29.444444 -.Ed -.Pp -Powers of units can be specified using the '^' character as shown in -the example, or by simple concatenation: 'cm3' is equivalent to 'cm^3'. -Multiplication of units can be specified by using spaces, a dash or -an asterisk. -Division of units is indicated by the slash ('/'). -Note that multiplication has a higher precedence than division, -so 'm/s/s' is the same as 'm/s^2' or 'm/s s'. +.Ss Mathematical operators +.Bl -dash -compact +.It +Powers of units can be specified using the +.Dq Ic ^ +character as shown in the example, or by simple concatenation: +.Dq Ic cm3 +is +equivalent to +.Dq Ic cm^3 . +See the +.Sx BUGS +section +for details on the limitations of exponent values. +.It +Multiplication of units can be specified by using spaces +.Pq Dq " " , +a dash +.Pq Dq - +or an asterisk +.Pq Dq * . +.It +Division of units is indicated by the slash +.Pq Dq Ic / . +.It Division of numbers -must be indicated using the vertical bar ('|'). -To convert half a -meter, you would write '1|2 meter'. -If you write '1/2 meter' then the -units program would interpret that as equivalent to '0.5/meter'. -If you enter incompatible unit types, the units program will -print a message indicating that the units are not conformable and -it will display the reduced form for each unit: -.Bd -literal - You have: ergs/hour - You want: fathoms kg^2 / day - conformability error - 2.7777778e-11 kg m^2 / sec^3 - 2.1166667e-05 kg^2 m / sec -.Ed +must be indicated using the vertical bar +.Pq Dq Ic \&| Ns . +.El .Pp +Note that multiplication has a higher precedence than division, +so +.Dq Ic m/s/s +is the same as +.Dq Ic m/s^2 +or +.Dq Ic m/s s . +.Ss Units The conversion information is read from a units data file. The default file includes definitions for most familiar units, abbreviations and @@ -128,48 +140,165 @@ .It "au astronomical unit" .El .Pp -The unit 'pound' is a unit of mass. +The unit +.Dq Ic pound +is a unit of mass. Compound names are run together -so 'pound force' is a unit of force. -The unit 'ounce' is also a unit -of mass. -The fluid ounce is 'floz'. +so +.Dq Ic pound force +is a unit of force. +The unit +.Dq Ic ounce +is also a unit of mass. +The fluid ounce is +.Dq Ic floz . British units that differ from -their US counterparts are prefixed with 'br', and currency is prefixed -with its country name: 'belgiumfranc', 'britainpound'. +their US counterparts are prefixed with +.Dq br , +and currency is prefixed with its country name: +.Dq Ic belgiumfranc , +.Dq Ic britainpound . When searching for a unit, if the specified string does not appear exactly as a unit name, then .Nm -will try to remove a trailing 's' or a -trailing 'es' and check again for a match. -.Pp +will try to remove a trailing +.Dq s +or a trailing +.Dq es +and check again for a match. +.Ss Units file format To find out what units are available read the standard units file. If you want to add your own units you can supply your own file. A unit is specified on a single line by giving its name and an equivalence. Be careful to define new units in terms of old ones so that a reduction leads to the -primitive units which are marked with '!' characters. +primitive units which are marked with +.Dq \&! +characters. The .Nm program will not detect infinite loops that could be caused by careless unit definitions. Comments in the unit definition file -begin with a '#' or '/' character at the beginning of a line. +begin with a +.Dq # +or +.Dq / +character at the beginning of a line. .Pp -Prefixes are defined in the same was as standard units, but with -a trailing dash at the end of the prefix name. +Prefixes are defined in the same way as standard units, but with +a trailing dash +.Pq Dq - +at the end of the prefix name. If a unit is not found -even after removing trailing 's' or 'es', then it will be checked -against the list of prefixes. +even after removing trailing +.Dq s +or +.Dq es , +then it will be checked against the list of prefixes. Prefixes will be removed until a legal base unit is identified. +.Sh ENVIRONMENT +.Bl -tag -width PATH +.It Ev PATH +The colon-separated list of root directories at which +.Nm +tries to find +.Pa /usr/share/misc/definitions.units . .Pp +For example if +.Ev PATH +is set to +.Dq Li /tmp:/:/usr/local , +no +.Fl f +flags are provided, and +.Pa /usr/share/misc/definitions.units +is missing then +.Nm +tries to open the following files as the default units file: +.Pa /tmp/usr/share/misc/definitions.units , +.Pa /usr/share/misc/definitions.units , +and +.Pa /usr/local/usr/share/misc/definitions.units . +.El +.Sh FILES +.Bl -tag -width /usr/share/misc/definitions.units -compact +.It Pa /usr/share/misc/definitions.units +The standard units file. +.El +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +.Bl -tag -width 0n +.It Sy Example 1 : No Interactive usage +.Pp +Here is an example of an interactive session where the user is prompted for +units: +.Bd -literal -offset 2n +.Li You have : Ic meters +.Li You want : Ic feet + * 3.2808399 + / 0.3048 + +.Li You have : Ic cm^3 +.Li You want : Ic gallons + * 0.00026417205 + / 3785.4118 + +.Li You have : Ic meters/s +.Li You want : Ic furlongs/fortnight + * 6012.8848 + / 0.00016630952 + +.Li You have : Ic 1|2 inch +.Li You want : Ic cm + * 1.27 + / 0.78740157 + +.Li You have : Ic 85 degF +.Li You want : Ic degC + 29.444444 +.Ed +.It Sy Example 2 : No Difference between Do Ic \&| Dc No and Do Ic / Dc No division +.Pp +The following command shows how to convert half a meter to centimeters. +.Bd -literal -offset 2n +.Li $ Ic units '1|2 meter' cm + * 50 + / 0.02 +.Ed +.Pp +.Nm +prints the expected result because the division operator for numbers +.Pq Dq Li \&| +was used. +.Pp +Using the division operator for units +.Pq Dq Li / +would result in an error: +.Bd -literal -offset 2n +.Li $ Ic units '1/2 meter' cm +conformability error + 0.5 / m + 0.01 m +.Ed +.Pp +It is because +.Nm +interprets +.Dq Ic 1/2 meter +as +.Dq Ic 0.5/meter , +which is not conformable to +.Dq Ic cm . +.It Sy Example 3 : No Simple units file Here is an example of a short units file that defines some basic -units. +units: .Pp -.Bl -column -offset indent -compact "minute" +.Bl -column -offset 2n -compact "minute" .It "m !a!" .It "sec !b!" .It "micro- 1e-6" @@ -179,21 +308,122 @@ .It "ft 12 inches" .It "mile 5280 ft" .El -.Sh FILES -.Bl -tag -width /usr/share/misc/definitions.units -compact -.It Pa /usr/share/misc/definitions.units -the standard units library +.It Sy Example 4 : No Viewing units and conversions of the default units file +The following shell one-liner allows the user to view the contents of the +default units file: +.Bd -literal -offset 2n +.Li $ Ic less \&"$(units -U)" +.Ed .El +.Sh DIAGNOSTICS +.Bl -diag +.It can't find units file '%s' +The default units file is not in its default location +.Pq see Sx FILES +and it is not present in any file tree starting with their roots at directories from +.Ev PATH +.Pq see Sx ENVIRONMENT . +.It cap_rights_limit\&() failed +See +.Xr capsicum 4 . +.It conformability error +It is not possible to reduce the given units to one common unit: +they are not conformable. +Instead of a conversion, +.Nm +will display the reduced form for each provided unit: +.Bd -literal -offset 2n +.Li You have : Ic ergs/hour +.Li You want : Ic fathoms kg^2 / day +conformability error + 2.7777778e-11 kg m^2 / sec^3 + 2.1166667e-05 kg^2 m / sec +.Ed +.It Could not initialize history +See +.Xr editline 3 . +.It dupstr +.Xr strdup 3 +failed. +.It memory for prefixes exceeded in line %d +Over 100 prefixes were defined. +.It memory for units exceeded in line %d +Over 1000 prefixes were defined. +.It memory overflow in unit reduction +The requested conversion involves too many units +.Pq see Sx BUGS . +.It redefinition of prefix '%s' on line %d ignored +.No "" +.It redefinition of unit '%s' on line %d ignored +.No "" +.It unexpected end of prefix on line %d +.No "" +.It unexpected end of unit on line %d +.No "" +.It Units data file not found +The default units file is missing. +.It unable to enter capability mode +See +.Xr capsicum 4 . +.It unable to open units file '%s' +One of the user-specified units files cannot be opened. +.It unit reduces to zero +.No "" +.It unknown unit '%s' +The provided unit cannot be found in the units file. +.It WARNING: conversion of non-proportional quantities. +.Nm +may fail to convert +.Ar from +to +.Ar to +because the units are not proportional. +The warning is printed when a quantity is a part of the +.Ar to +argument. +It can be illustrated on an example of conversion from Fahrenheit to Celsius: +.Bd -literal -offset 2n +.Li $ Ic units \&"degF" \&"degC" + (-> x*0.55555556g -17.777778g) + (<- y*1.8g 32g) +.Li $ Ic units \&"degF" \&"1 degC" +WARNING: conversion of non-proportional quantities. + (-> x*0.55555556g -17.777778g) + (<- y*1.8g 32g) +.Li $ Ic units \&"1 degF" \&"1 degC" +WARNING: conversion of non-proportional quantities. + -17.222222 +.Ed +.El +.Sh SEE ALSO +.Xr bc 1 +.Sh HISTORY +The +.Nm +first appeared in +.Nx +and was ported to +.Fx 2.2.0 . +.Pp +The manual page was significantly rewritten in +.Fx 13.0 +by +.An Mateusz Piotrowski Aq Mt 0mp@FreeBSD.org . .Sh AUTHORS .An Adrian Mariano Aq Mt adrian@cam.cornell.edu .Sh BUGS -The effect of including a '/' in a prefix is surprising. +The effect of including a +.Dq / +in a prefix is surprising. .Pp Exponents entered by the user can be only one digit. You can work around this by multiplying several terms. .Pp -The user must use | to indicate division of numbers and / to -indicate division of symbols. +The user must use +.Dq Ic \&| +to indicate division of numbers and +.Dq Ic / +to indicate division of symbols. This distinction should not be necessary. .Pp @@ -203,3 +433,30 @@ The program should use a hash table to store units so that it does not take so long to load the units list and check for duplication. +.Pp +It is not possible to convert a negative value. +.Pp +The +.Nm +program +does not handle reductions of long lists of units very well: +.Bd -literal -offset 2n +.Li $ Ic units \&"$(yes m | head -n 154)" \&"$(yes cm | head -n 154)" + * 1e+308 + / 1e-308 +.Li $ Ic units \&"$(yes m | head -n 333)" \&"$(yes cm | head -n 333)" + * inf + / 0 +.Li $ Ic units \&"$(yes m | head -n 500)" \&"$(yes cm | head -n 500)" +units: memory overflow in unit reduction +conformability error + 1 m^500 + 1 centi cm^499 +.Li $ Ic units \&"$(yes m | head -n 501)" \&"$(yes cm | head -n 501)" +units: memory overflow in unit reduction +units: memory overflow in unit reduction +units: memory overflow in unit reduction +conformability error + 1 m^500 + 1 centi cm^499 +.Ed Index: usr.bin/units/units.c =================================================================== --- usr.bin/units/units.c +++ usr.bin/units/units.c @@ -117,7 +117,7 @@ } -static void +static void readunits(const char *userfile) { FILE *unitfile; @@ -221,7 +221,7 @@ fclose(unitfile); } -static void +static void initializeunit(struct unittype * theunit) { theunit->numerator[0] = theunit->denominator[0] = NULL; @@ -231,7 +231,7 @@ } -static int +static int addsubunit(char *product[], char *toadd) { char **ptr; @@ -248,7 +248,7 @@ } -static void +static void showunit(struct unittype * theunit) { char **ptr; @@ -296,7 +296,7 @@ } -void +void zeroerror(void) { warnx("unit reduces to zero"); @@ -310,7 +310,7 @@ Returns 0 for successful addition, nonzero on error. */ -static int +static int addunit(struct unittype * theunit, const char *toadd, int flip, int quantity) { char *scratch, *savescr; @@ -320,7 +320,7 @@ if (!strlen(toadd)) return 1; - + savescr = scratch = dupstr(toadd); for (slash = scratch + 1; *slash; slash++) if (*slash == '-' && @@ -424,14 +424,14 @@ } -static int +static int compare(const void *item1, const void *item2) { return strcmp(*(const char * const *)item1, *(const char * const *)item2); } -static void +static void sortunit(struct unittype * theunit) { char **ptr; @@ -444,7 +444,7 @@ } -void +void cancelunit(struct unittype * theunit) { char **den, **num; @@ -552,7 +552,7 @@ #define ERROR 4 -static int +static int reduceproduct(struct unittype * theunit, int flip) { @@ -595,7 +595,7 @@ Returns 0 on success, or 1 on unknown unit error. */ -static int +static int reduceunit(struct unittype * theunit) { int ret; @@ -610,7 +610,7 @@ } -static int +static int compareproducts(char **one, char **two) { while (*one || *two) { @@ -635,7 +635,7 @@ /* Return zero if units are compatible, nonzero otherwise */ -static int +static int compareunits(struct unittype * first, struct unittype * second) { return @@ -644,7 +644,7 @@ } -static int +static int completereduce(struct unittype * unit) { if (reduceunit(unit)) @@ -654,7 +654,7 @@ return 0; } -static void +static void showanswer(struct unittype * have, struct unittype * want) { double ans; @@ -731,21 +731,21 @@ usage(void) { fprintf(stderr, - "usage: units [-f unitsfile] [-H historyfile] [-UVq] [from-unit to-unit]\n"); + "usage: units [-ehqtUVv] [-f filename] [-H filename] [-o format] [from to]\n"); exit(3); } static struct option longopts[] = { - {"help", no_argument, NULL, 'h'}, {"exponential", no_argument, NULL, 'e'}, {"file", required_argument, NULL, 'f'}, {"history", required_argument, NULL, 'H'}, + {"help", no_argument, NULL, 'h'}, {"output-format", required_argument, NULL, 'o'}, {"quiet", no_argument, NULL, 'q'}, {"terse", no_argument, NULL, 't'}, {"unitsfile", no_argument, NULL, 'U'}, - {"verbose", no_argument, NULL, 'v'}, {"version", no_argument, NULL, 'V'}, + {"verbose", no_argument, NULL, 'v'}, { 0, 0, 0, 0 } }; @@ -797,15 +797,15 @@ case 'v': verbose = true; break; - case 'V': - fprintf(stderr, "FreeBSD units\n"); - /* FALLTHROUGH */ case 'U': if (access(UNITSFILE, F_OK) == 0) printf("%s\n", UNITSFILE); else printf("Units data file not found"); exit(0); + case 'V': + fprintf(stderr, "FreeBSD units\n"); + /* FALLTHROUGH */ case 'h': /* FALLTHROUGH */