1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
|
## Copyright (C) 2005 Carl Osterwisch <osterwischc@asme.org>
## Copyright (C) 2013 Carnë Draug <carandraug@octave.org>
##
## 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 3 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, see <http://www.gnu.org/licenses/>.
## -*- texinfo -*-
## @deftypefn {Function File} {} units (@var{fromUnit}, @var{toUnit})
## @deftypefnx {Function File} {} units (@var{fromUnit}, @var{toUnit}, @var{x})
## Return the conversion factor from @var{fromUnit} to @var{toUnit} measurements.
##
## This is an octave interface to the @strong{GNU Units} program which comes
## with an annotated, extendable database defining over two thousand
## measurement units. See @code{man units} or
## @url{http://www.gnu.org/software/units} for more information.
## If the optional argument @var{x} is supplied, return that argument
## multiplied by the conversion factor. For example, to
## convert three values from miles per hour into meters per second:
##
## @example
## units ("mile/hr", "m/sec", [30, 55, 75])
## ans =
##
## 13.411 24.587 33.528
## @end example
## @end deftypefn
function y = units (fromUnit, toUnit, x = 1)
if (nargin < 2 || nargin > 3)
print_usage ();
elseif (! ischar (fromUnit))
error ("units: FromUNIT must be a string");
elseif (! ischar (toUnit))
error ("units: ToUNIT must be a string");
elseif (! isnumeric (x))
error ("units: X must be numeric");
endif
persistent available = check_units ();
persistent compact = has_compact_option ();
persistent template = template_cmd (compact);
## We have to insert the template on the string this way, because it may have
## a %%.16g which we may want to keep for later use in non-linear conversion
cmd = sprintf ([template ' "%s" "%s"'], fromUnit, toUnit);
[status, rawoutput] = system (cmd);
if (status)
error ("units: %s", rawoutput);
endif
if (! compact)
## No compact or one-line option, we need to find the conversion factor
## from the text ourselves
ini_factor = index (rawoutput, "*");
end_factor = index (rawoutput, "\n") - 1;
if (isempty (ini_factor) || ini_factor > end_factor)
error ("units: unable to parse output from units:\n%s", rawoutput);
endif
rawoutput = rawoutput(ini_factor+1:end_factor);
endif
c_factor = str2double (rawoutput);
if (any (isnan (c_factor(:))))
if (index (rawoutput, "=") || index (rawoutput, "+") ||
index (rawoutput, "-") || index (rawoutput, "*") ||
index (rawoutput, "/"))
## If there's a mathematical operator in the output, it may be a formula
## for a non-linear conversion such as "tempC(x) = x K + stdtemp"
## We don't check for the equal only because some versions of units data
## file (not version of the units application, see bug #38270) have a
## very different syntax.
if (nargin < 3)
## for a non-linear unit conversion, we need a value to convert
error ("units: argument X is required for non-linear unit conversion");
endif
y = zeros (size (x));
template_non_linear = function_template (template, fromUnit, toUnit);
for ind = 1:numel(y)
cmd = sprintf (template_non_linear, x(ind));
[status, rawoutput] = system (cmd);
if (status)
error ("units: %s", rawoutput);
endif
y(ind) = str2double (rawoutput);
if (isnan (y(ind)))
error ("units unable to parse non-linear conversion `%s'", rawoutput);
endif
endfor
else
error ("units: unable to parse output `%s' from units.", rawoutput);
endif
else
y = x * c_factor;
endif
endfunction
function fpath = check_units ()
## See bug #38270 about why we're checking this way.
fpath = file_in_path (getenv ("PATH"), sprintf ("units%s", octave_config_info ("EXEEXT")));
if (isempty (fpath))
error ("units: %s\nVerify that GNU units is installed in the current path.",
rawoutput);
endif
endfunction
function compact = has_compact_option ()
compact = true;
## We must give some units to convert because the only thing that would
## make it not do any work (--version) actually exits with exit value 3
[status, rawoutput] = system ('units --compact --one-line "in" "cm"');
if (status)
compact = false;
endif
endfunction
function template = template_cmd (compact)
## do we have the format option?
format = true;
[status, rawoutput] = system ('units --output-format "%.16g" "in" "cm"');
if (status)
format = false;
endif
template = "units ";
if (format)
template = [template '--output-format "%%.16g" '];
endif
if (compact)
template = [template '--compact --one-line '];
endif
endfunction
## Test the correct way to do non-linear conversion.
function template = function_template (template, from, to)
## First try the most common syntax distributed with the most recent
## units.dat file using parentheses "from(x)" as if it was a function.
if (! system (sprintf ([template '"%s(1)" "%s"'], from, to), true))
template = sprintf ('%s "%s(%%.16g)" "%s"', template, from, to);
## If it doesn't work, try the oldest syntax of the style "x from"
elseif (! system (sprintf ([template '"100 %s" "%s"'], from, to), true))
template = sprintf ('%s "%%.16g %s" "%s"', template, from, to);
## If it doesn't work, give up
else
error ("units: unable to identify correct syntax for non-linear conversion");
endif
endfunction
%!demo
%! a.value = 100; a.unit = 'lb';
%! b.value = 50; b.unit = 'oz';
%! c.unit = 'kg';
%! c.value = units(a.unit, c.unit, a.value) + units(b.unit, c.unit, b.value)
# test normal usage
%!assert (units ("in", "mm"), 25.4)
# multiple values
%!assert (units ("in", "mm", [5 7; 8 9]), 25.4 * [5 7; 8 9])
# a non-linear conversion
%!assert (units ("tempC", "tempF", 100), 212)
%!error <non-linear unit conversion> units ("tempC", "tempF")
|