File Coverage

blib/lib/Physics/Unit.pm
Criterion Covered Total %
statement 318 348 91.3
branch 146 192 76.0
condition 27 45 60.0
subroutine 42 47 89.3
pod 29 42 69.0
total 562 674 83.3


line stmt bran cond sub pod time code
1             package Physics::Unit;
2              
3 3     3   132352 use strict;
  3         6  
  3         136  
4 3     3   15 use warnings;
  3         5  
  3         162  
5 3     3   17 use Carp;
  3         8  
  3         206  
6 3     3   27 use base qw(Exporter);
  3         8  
  3         448  
7 3         18143 use vars qw(
8             $VERSION
9             @EXPORT_OK
10             %EXPORT_TAGS
11             $debug
12             $number_re
13 3     3   18 );
  3         6  
14              
15             $VERSION = '0.60';
16             $VERSION = eval $VERSION;
17              
18             @EXPORT_OK = qw(
19             $number_re
20             GetTypeUnit
21             GetUnit
22             InitBaseUnit
23             InitPrefix
24             InitTypes
25             InitUnit
26             ListTypes
27             ListUnits
28             NumBases
29             DeleteNames
30             );
31              
32             %EXPORT_TAGS = ('ALL' => \@EXPORT_OK);
33              
34             # This is the regular expression used to parse out a number. It
35             # is here so that other modules can use it for convenience.
36              
37             $number_re = '([-+]?((\d+\.?\d*)|(\.\d+))([eE][-+]?((\d+\.?\d*)|(\.\d+)))?)';
38              
39             # The value of this hash is a string representing the token returned
40             # when this word is seen
41              
42             my %reserved_word = (
43             per => 'divide',
44             square => 'square',
45             sq => 'square',
46             cubic => 'cubic',
47             squared => 'squared',
48             cubed => 'cubed',
49             );
50              
51             # Pre-defined units
52             my %unit_by_name;
53              
54             # Values are references to units representing the prefix
55             my %prefix;
56              
57             # Known quantity types. The values of this hash are references to
58             # unit objects that exemplify these types
59             my %prototype;
60              
61             # The number of base units
62             my $NumBases = 0;
63              
64             # The names of the base units
65             my @BaseName;
66              
67             InitBaseUnit (
68             'Distance' => ['meter', 'm', 'meters', 'metre', 'metres'],
69             'Mass' => ['gram', 'gm', 'grams'],
70             'Time' => ['second', 's', 'sec', 'secs', 'seconds'],
71             'Temperature' => ['kelvin', 'k', 'kelvins',
72             'degree-kelvin', 'degrees-kelvin', 'degree-kelvins'],
73             'Current' => ['ampere', 'amp', 'amps', 'amperes'],
74             'Substance' => ['mole', 'mol', 'moles'],
75             'Luminosity' => ['candela', 'cd', 'candelas', 'candle', 'candles'],
76             'Money' => ['us-dollar', 'dollar', 'dollars', 'us-dollars', '$'],
77             'Data' => ['bit', 'bits'],
78             );
79              
80             InitPrefix (
81             'deka', 1e1,
82             'deca', 1e1,
83             'hecto', 1e2,
84             'kilo', 1e3,
85             'mega', 1e6,
86             'giga', 1e9,
87             'tera', 1e12,
88             'peta', 1e15,
89             'exa', 1e18,
90             'zetta', 1e21,
91             'yotta', 1e24,
92             'deci', 1e-1,
93             'centi', 1e-2,
94             'milli', 1e-3,
95             'micro', 1e-6,
96             'nano', 1e-9,
97             'pico', 1e-12,
98             'femto', 1e-15,
99             'atto', 1e-18,
100             'zepto', 1e-21,
101             'yocto', 1e-24,
102              
103             # binary prefixes
104             'kibi', 2**10,
105             'mebi', 2**20,
106             'gibi', 2**30,
107             'tebi', 2**40,
108             'pebi', 2**50,
109             'exbi', 2**60,
110              
111             # others
112             'semi', 0.5,
113             'demi', 0.5,
114             );
115              
116              
117             InitUnit (
118             # Dimensionless
119             ['pi',], '3.1415926535897932385',
120             ['e',], '2.7182818284590452354',
121              
122             ['unity', 'one', 'ones',], '1',
123             ['two', 'twos',], '2',
124             ['three', 'threes',], '3',
125             ['four', 'fours',], '4',
126             ['five', 'fives',], '5',
127             ['six', 'sixes',], '6',
128             ['seven', 'sevens',], '7',
129             ['eight', 'eights',], '8',
130             ['nine', 'nines'], '9',
131             ['ten', 'tens',], '10',
132             ['eleven', 'elevens',], '11',
133             ['twelve', 'twelves',], '12',
134             ['thirteen', 'thirteens',], '13',
135             ['fourteen', 'fourteens',], '14',
136             ['fifteen', 'fifteens',], '15',
137             ['sixteen', 'sixteens',], '16',
138             ['seventeen', 'seventeens',], '17',
139             ['eighteen', 'eighteens',], '18',
140             ['nineteen', 'nineteens',], '19',
141             ['twenty', 'twenties',], '20',
142             ['thirty', 'thirties',], '30',
143             ['forty', 'forties',], '40',
144             ['fifty', 'fifties',], '50',
145             ['sixty', 'sixties',], '60',
146             ['seventy', 'seventies',], '70',
147             ['eighty', 'eighties',], '80',
148             ['ninety', 'nineties',], '90',
149             ['hundred', 'hundreds'], '100',
150             ['thousand', 'thousands'], '1000',
151             ['million', 'millions',], '1e6',
152             ['billion', 'billions',], '1e9',
153             ['trillion', 'trillions',], '1e12',
154             ['quadrillion', 'quadrillions',], '1e15',
155             ['quintillion', 'quintillions',], '1e18',
156              
157             ['half', 'halves',], '0.5',
158             ['third', 'thirds',], '1/3',
159             ['fourth', 'fourths',], '0.25',
160             ['tenth',], '0.1',
161             ['hundredth',], '0.01',
162             ['thousandth',], '0.001',
163             ['millionth',], '1e-6',
164             ['billionth',], '1e-9',
165             ['trillionth',], '1e-12',
166             ['quadrillionth',], '1e-15',
167             ['quintillionth',], '1e-18',
168              
169             ['percent', '%',], '0.01',
170             ['dozen', 'doz',], '12',
171             ['gross',], '144',
172              
173             # Angular
174             ['radian', 'radians',], '1',
175             ['steradian', 'sr', 'steradians',], '1',
176             ['degree', 'deg', 'degrees',], 'pi radians / 180',
177             ['arcminute', 'arcmin', 'arcminutes',], 'deg / 60',
178             ['arcsecond', 'arcsec', 'arcseconds',], 'arcmin / 60',
179              
180             # Distance
181             ['foot', 'ft', 'feet',], '.3048 m', # exact
182             ['inch', 'in', 'inches',], 'ft/12', # exact
183             ['mil', 'mils',], 'in/1000', # exact
184             ['yard', 'yards',], '3 ft', # exact
185             ['fathom', 'fathoms',], '2 yards', # exact
186             ['rod', 'rods',], '5.5 yards', # exact
187             ['pole', 'poles',], '1 rod', # exact
188             ['perch', 'perches',], '1 rod', # exact
189             ['furlong', 'furlongs',], '40 rods', # exact
190             ['mile', 'mi', 'miles',], '5280 ft', # exact
191              
192             ['micron', 'microns', 'um',], '1e-6 m', # exact
193             ['angstrom', 'a', 'angstroms',], '1e-10 m', # exact
194             ['cm',], 'centimeter', # exact
195             ['km',], 'kilometer', # exact
196             ['nm',], 'nanometer', # exact
197             ['mm',], 'millimeter', # exact
198              
199             ['pica',], 'in/6', # exact, but see below
200             ['point',], 'pica/12', # exact
201              
202             ['nautical-mile', 'nmi', 'nauticalmiles',
203             'nauticalmile', 'nautical-miles',], '1852 m', # exact
204             ['astronomical-unit', 'au', 'earth-to-sun'], '149597870700 m',
205             ['light-year', 'ly', 'light-years',
206             'lightyear', 'lightyears'], '9.46e15 m',
207             ['parsec', 'parsecs',], '3.083e16 m',
208              
209             # from https://nssdc.gsfc.nasa.gov/planetary/factsheet/planet_table_ratio.html
210             ['moon-to-sun'], '1 au', # ???
211             ['mercury-to-sun'], '0.387 au',
212             ['venus-to-sun'], '0.723 au',
213             ['mars-to-sun'], '1.52 au',
214             ['jupiter-to-sun'], '5.2 au',
215             ['saturn-to-sun'], '9.57 au',
216             ['uranus-to-sun'], '19.17 au',
217             ['neptune-to-sun'], '30.18 au',
218             ['pluto-to-sun'], '39.48 au',
219              
220              
221             # equatorial radius of the reference geoid:
222             ['re'], '6378388 m', # exact
223             # polar radius of the reference geoid:
224             ['rp'], '6356912 m', # exact
225              
226             # Acceleration
227             ['g0', 'earth-gravity'], '9.80665 m/s^2', # exact
228              
229             # from https://nssdc.gsfc.nasa.gov/planetary/factsheet/planet_table_ratio.html
230             ['moon-gravity'], '0.166 earth-gravity',
231             ['mercury-gravity'], '0.378 earth-gravity',
232             ['venus-gravity'], '0.907 earth-gravity',
233             ['mars-gravity'], '0.377 earth-gravity',
234             ['jupiter-gravity'], '2.36 earth-gravity',
235             ['saturn-gravity'], '0.916 earth-gravity',
236             ['uranus-gravity'], '0.889 earth-gravity',
237             ['neptune-gravity'], '1.12 earth-gravity',
238             ['pluto-gravity'], '0.071 earth-gravity',
239              
240             # Mass
241             ['kg',], 'kilogram', # exact
242             ['metric-ton', 'metric-tons', 'tonne',
243             'tonnes'], '1000 kg', # exact
244              
245             ['grain', 'grains'], '.0648 gm',
246              
247             ['pound-mass', 'lbm', 'lbms',
248             'pounds-mass'], '0.45359237 kg', # exact
249             ['ounce', 'oz', 'ounces'], 'lbm/16', # exact
250             ['stone', 'stones'], '14 lbm', # exact
251             ['hundredweight', 'hundredweights'], '100 lbms', # exact
252             ['ton', 'tons', 'short-ton', 'short-tons'], '2000 lbms', # exact
253             ['long-ton', 'long-tons'], '2240 lbms', # exact
254              
255             ['slug', 'slugs'], 'lbm g0 s^2/ft', # exact
256             ['mg',], 'milligram', # exact
257             ['ug',], 'microgram', # exact
258              
259             ['dram', 'drams'], 'ounce / 16', # exact
260              
261             ['troy-pound', 'troy-pounds'], '0.373 kg',
262             ['troy-ounce', 'troy-ounces',
263             'ounce-troy', 'ounces-troy'], '31.103 gm',
264             ['pennyweight', 'pennyweights'], '1.555 gm',
265             ['scruple', 'scruples'], '1.296 gm',
266              
267             ['hg',], 'hectogram', # exact
268             ['dag',], 'decagram', # exact
269             ['dg',], 'decigram', # exact
270             ['cg',], 'centigram', # exact
271             ['carat', 'carats', 'karat', 'karats',], '200 milligrams', # exact
272             ['j-point',], '2 carats', # exact
273              
274             ['atomic-mass-unit', 'amu', 'u',
275             'atomic-mass-units'], '1.6605402e-27 kg',
276              
277              
278             # Time
279             ['minute', 'min', 'mins', 'minutes'], '60 s',
280             ['hour', 'hr', 'hrs', 'hours'], '60 min',
281             ['day', 'days', 'earth-day', 'earth-days'], '24 hr',
282             ['week', 'wk', 'weeks'], '7 days',
283             ['fortnight', 'fortnights'], '2 week',
284             ['year', 'yr', 'yrs', 'years'], '365.25 days',
285             ['month', 'mon', 'mons', 'months'], 'year / 12', # an average month
286             ['score', 'scores'], '20 yr',
287             ['century', 'centuries'], '100 yr',
288             ['millenium', 'millenia',], '1000 yr',
289              
290             ['ms', 'msec', 'msecs'], 'millisecond',
291             ['us', 'usec', 'usecs'], 'microsecond',
292             ['ns', 'nsec', 'nsecs'], 'nanosecond',
293             ['ps', 'psec', 'psecs'], 'picosecond',
294              
295             # from https://nssdc.gsfc.nasa.gov/planetary/factsheet/planet_table_ratio.html
296             ['moon-day', 'moon-days',], '29.5 earth-days',
297             ['mercury-day', 'mercury-days'], '175.9 earth-days',
298             ['venus-day', 'venus-days',], '116.8 earth-days',
299             ['mars-day', 'mars-days',], '1.03 earth-days',
300             ['jupiter-day', 'jupiter-days',], '0.414 earth-days',
301             ['saturn-day', 'saturn-days',], '0.444 earth-days',
302             ['uranus-day', 'uranus-days',], '0.718 earth-days',
303             ['neptune-day', 'neptune-days',], '0.671 earth-days',
304             ['pluto-day', 'pluto-days',], '6.39 earth-days',
305              
306             # Data
307             ['byte', 'bytes'], '8 bits',
308              
309             # Frequency
310             ['hertz', 'hz'], '1/sec',
311             ['becquerel', 'bq'], '1 hz',
312             ['revolution', 'revolutions',], '1',
313             ['rpm',], 'revolutions per minute',
314             ['cycle', 'cycles',], '1',
315              
316             # Current
317             ['abampere', 'abamp', 'abamps', 'abamperes'], '10 amps',
318             ['statampere', 'statamp', 'statamps', 'statamperes'], '3.336e-10 amps',
319              
320             ['ma',], 'milliamp',
321             ['ua',], 'microamp',
322              
323             # Electric_potential
324             ['volt', 'v', 'volts'], 'kg m^2 / amp s^3',
325             ['mv',], 'millivolt',
326             ['uv',], 'microvolt',
327             ['abvolt', 'abvolts'], '1e-8 volt',
328             ['statvolt', 'statvolts'], '299.8 volt',
329              
330             # Resistance
331             ['ohm', 'ohms'], 'kg m^2 / amp^2 s^3',
332             ['abohm', 'abohms'], 'nano ohm',
333             ['statohm', 'statohms'], '8.987e11 ohm',
334             ['kilohm', 'kilohms',], 'kilo ohm',
335             ['megohm', 'megohms'], 'mega ohm',
336              
337             # Conductance
338             ['siemens',], 'amp^2 s^3 / kg m^2',
339             ['mho', 'mhos'], '1/ohm',
340              
341             # Capacitance
342             ['farad', 'f', 'farads'], 'amp^2 s^4 / kg m^2',
343             ['abfarad', 'abfarads'], 'giga farad',
344             ['statfarad', 'statfarads'], '1.113e-12 farad',
345              
346             ['uf',], 'microfarad',
347             ['pf',], 'picofarads',
348              
349             # Inductance
350             ['henry', 'henrys'], 'kg m^2 / amp^2 s^2',
351             ['abhenry', 'abhenrys'], 'nano henry',
352             ['stathenry', 'stathenrys'], '8.987e11 henry',
353              
354             ['uh',], 'microhenry',
355             ['mh',], 'millihenry',
356              
357             # Magnetic_flux
358             ['weber', 'wb', 'webers'], 'kg m^2 / amp s^2',
359             ['maxwell', 'maxwells'], '1e-8 weber',
360              
361             # Magnetic_field
362             ['tesla', 'teslas'], 'kg / amp sec^2',
363             ['gauss',], '1e-4 tesla',
364              
365             # Temperature
366             ['degree-rankine', 'degrees-rankine'], '5/9 * kelvin', # exact
367              
368             # Force
369             ['pound', 'lb', 'lbs', 'pounds',
370             'pound-force', 'lbf',
371             'pounds-force', 'pound-weight'], 'slug foot / s^2', # exact
372             ['ounce-force', 'ozf'], 'pound-force / 16', # exact
373             ['newton', 'nt', 'newtons'], 'kg m / s^2', # exact
374             ['dyne', 'dynes'], 'gm cm / s^2', # exact
375             ['gram-weight', 'gram-force'], 'gm g0', # exact
376             ['kgf',], 'kilo gram-force', # exact
377              
378             # Area
379             ['are', 'ares'], '100 square meters',
380             ['hectare', 'hectares',], '100 ares',
381             ['acre', 'acres'], '43560 square feet',
382             ['barn', 'barns'], '1e-28 square meters',
383              
384             # Volume
385             ['liter', 'l', 'liters'], 'm^3/1000', # exact
386             ['cl',], 'centiliter', # exact
387             ['dl',], 'deciliter', # exact
388             ['cc', 'ml',], 'cubic centimeter', # exact
389              
390             ['gallon', 'gal', 'gallons'], '3.785411784 liter',
391             ['quart', 'qt', 'quarts'], 'gallon/4',
392             ['peck', 'pecks'], '8 quarts',
393             ['bushel', 'bushels'], '4 pecks',
394             ['fifth', 'fifths'], 'gallon/5',
395             ['pint', 'pt', 'pints'], 'quart/2',
396             ['cup', 'cups'], 'pint/2',
397             ['fluid-ounce', 'floz', 'fluidounce',
398             'fluidounces', 'fluid-ounces'], 'cup/8',
399             ['gill', 'gills'], '4 fluid-ounces',
400             ['fluidram', 'fluidrams'], '3.5516 cc',
401             ['minim', 'minims'], '0.059194 cc',
402             ['tablespoon', 'tbsp', 'tablespoons'], 'fluid-ounce / 2',
403             ['teaspoon', 'tsp', 'teaspoons'], 'tablespoon / 3',
404              
405             # Power
406             ['watt', 'w', 'watts'], 'kg m^2 / s^3',
407             ['horsepower', 'hp'], '550 foot pound-force / s',
408              
409             # Energy
410             ['joule', 'j', 'joules'], 'kg m^2 / s^2', # exact
411             ['electron-volt', 'ev', 'electronvolt',
412             'electronvolts', 'electron-volts'], '1.60217733e-19 joule',
413              
414             ['mev',], 'mega electron-volt',
415             ['gev',], 'giga electron-volt',
416             ['tev',], 'tera electron-volt',
417              
418             ['calorie', 'cal', 'calories'], '4.184 joules', # exact
419             ['kcal',], 'kilocalorie', # exact
420             ['british-thermal-unit', 'btu', 'btus',
421             'britishthermalunit', 'britishthermalunits',
422             'british-thermal-units'], '1055.056 joule',
423             ['erg', 'ergs'], '1.0e-7 joule', # exact
424             ['kwh',], 'kilowatt hour', # exact
425              
426             # Torque
427             ['foot-pound', 'ftlb', 'ft-lb',
428             'footpound', 'footpounds', 'foot-pounds'], 'foot pound-force',
429              
430             # Charge
431             ['coulomb', 'coul', 'coulombs'], 'ampere second',
432             ['abcoulomb', 'abcoul', 'abcoulombs'], '10 coulomb',
433             ['statcoulomb', 'statcoul', 'statcoulombs'], '3.336e-10 coulomb',
434             ['elementary-charge', 'eq'], '1.6021892e-19 coulomb',
435              
436             # Pressure
437             ['pascal', 'pa'], 'newton / m^2',
438             ['bar', 'bars'], '1e5 pascal',
439             ['torr',], '(101325 / 760) pascal',
440             ['psi',], 'pounds per inch^2',
441             ['atmosphere', 'atm'], '101325 pascal', # exact
442              
443             # Speed
444             ['mph',], 'mi/hr',
445             ['kph',], 'km/hr',
446             ['kps',], 'km/s',
447             ['fps',], 'ft/s',
448             ['knot', 'knots'], 'nm/hr',
449             ['mps',], 'meter/s',
450             ['speed-of-light', 'c'], '2.99792458e8 m/sec',
451              
452             # Dose of radiation
453             ['gray', 'gy'], 'joule / kg',
454             ['sievert', 'sv'], 'joule / kg',
455             ['rad',], 'gray / 100',
456             ['rem',], 'sievert / 100',
457              
458             # Other
459             ['gravitational-constant', 'g'], '6.6720e-11 m^3 / kg s^2',
460             # Planck constant:
461             ['h'], '6.626196e-34 J/s',
462             # Avogadro constant:
463             ['na'], '6.022169/mol',
464             );
465              
466              
467             InitTypes (
468             'Dimensionless' => 'unity',
469             'Frequency' => 'hertz',
470             'Electric_potential' => 'volt',
471             'Resistance' => 'ohm',
472             'Conductance' => 'siemens',
473             'Capacitance' => 'farad',
474             'Inductance' => 'henry',
475             'Magnetic_flux' => 'weber',
476             'Magnetic_field' => 'tesla',
477             'Momentum' => 'kg m/s',
478             'Force' => 'newton',
479             'Area' => 'are',
480             'Volume' => 'liter',
481             'Power' => 'watt',
482             'Energy' => 'joule',
483             'Torque' => 'kg m^2/s^2',
484             'Charge' => 'coulomb',
485             'Pressure' => 'pascal',
486             'Speed' => 'mps',
487             'Dose' => 'gray', # of radiation
488             'Acceleration' => 'm/s^2',
489             );
490              
491              
492             GetUnit('joule')->type('Energy');
493             GetUnit('ev')->type('Energy');
494             GetUnit('mev')->type('Energy');
495             GetUnit('gev')->type('Energy');
496             GetUnit('tev')->type('Energy');
497             GetUnit('cal')->type('Energy');
498             GetUnit('kcal')->type('Energy');
499             GetUnit('btu')->type('Energy');
500             GetUnit('erg')->type('Energy');
501             GetUnit('kwh')->type('Energy');
502             GetUnit('ftlb')->type('Torque');
503              
504              
505             sub InitBaseUnit {
506 4     4 1 19 while (@_) {
507 28         51 my ($t, $names) = (shift, shift);
508 28 50 33     110 croak 'Invalid arguments to InitBaseUnit'
509             if ref $t || (ref $names ne "ARRAY");
510              
511 28 50       46 print "Initializing Base Unit $$names[0]\n" if $debug;
512              
513 28         45 my $unit = NewOne();
514 28         91 $unit->AddNames(@$names);
515 28         56 $unit->{def} = $unit->name(); # def same as name
516              
517             # The dimension vector for this Unit has zeros in every place
518             # except the last
519 28         47 $unit->{dim}->[$NumBases] = 1;
520 28         39 $BaseName[$NumBases] = $unit->abbr();
521 28         50 $NumBases++;
522              
523 28         43 $unit->NewType($t);
524             }
525             }
526              
527             sub InitPrefix {
528 4     4 1 24 while (@_) {
529 89         167 my ($name, $factor) = (shift, shift);
530 89 50 33     386 croak 'Invalid arguments to InitPrefix'
      33        
      33        
531             if !$name || !$factor || ref $name || ref $factor;
532              
533 89 50       128 print "Initializing Prefix $name\n" if $debug;
534              
535 89         132 my $u = NewOne();
536 89         160 $u->AddNames($name);
537 89         107 $u->{factor} = $factor;
538 89         107 $u->{type} = 'prefix';
539 89         125 $prefix{$name} = $u;
540              
541 89         158 $u->{def} = $factor;
542             }
543             }
544              
545             sub InitUnit {
546 7     7 1 27 while (@_) {
547 739         1527 my ($names, $def) = (shift, shift);
548              
549 739 50 33     2674 if (ref $names ne "ARRAY" || !$def) {
550 0         0 print "InitUnit, second argument is '$def'\n";
551 0         0 croak 'Invalid arguments to InitUnit';
552             }
553              
554 739 50       1268 print "Initializing Unit $$names[0]\n" if $debug;
555 739         1164 my $u = CreateUnit($def);
556 739         1632 $u->AddNames(@$names);
557             }
558             }
559              
560             sub InitTypes {
561 4     4 1 25 while (@_) {
562 64         105 my ($t, $u) = (shift, shift);
563 64 50 33     229 croak 'Invalid arguments to InitTypes'
      33        
564             if !$t || ref $t || !$u;
565              
566 64         85 my $unit = GetUnit($u);
567 64         102 $unit->NewType($t);
568             }
569             }
570              
571             sub GetUnit {
572 2239     2239 1 244881 my $u = shift;
573 2239 50       4046 croak 'GetUnit: expected an argument' unless $u;
574 2239 100       4488 return $u if ref $u;
575              
576 1190 100       2716 if ($unit_by_name{$u}) {
577             #print "GetUnit, $u yields ", $unit_by_name{$u}->name, "\n";
578 1154         2928 return $unit_by_name{$u};
579             }
580              
581             # Try it as an expression
582 36         100 return CreateUnit($u);
583             }
584              
585             sub ListUnits {
586 5     5 1 3211 return sort keys %unit_by_name;
587             }
588              
589             sub ListTypes {
590 2     2 1 50 return sort keys %prototype;
591             }
592              
593             sub NumBases {
594 0     0 1 0 return $NumBases;
595             }
596              
597             sub GetTypeUnit {
598 60     60 1 83 my $t = shift;
599 60         133 return $prototype{$t};
600             }
601              
602             # DeleteNames - argument can be either an array ref, a list of name strings, or
603             # a unit object
604             sub DeleteNames {
605 4     4 1 48 my $arg0 = $_[0];
606 4   100     20 my $argIsUnit = ref $arg0 && ref $arg0 ne 'ARRAY';
607             # Get the list of names to delete
608             my $names =
609             !ref $arg0
610             ? \@_ # list of names
611             : ref $arg0 eq 'ARRAY'
612             ? $arg0 # array ref
613 4 100       18 : $arg0->{names}; # unit object
    100          
614              
615 4         9 my $u;
616 4 100       10 if ($argIsUnit) { $u = $arg0; }
  1         3  
617 4         10 for my $n (@$names) {
618 8 50       22 if (LookName($n) != 2) {
619 0         0 croak "'$n' is not a unit name.";
620             }
621 8 50       21 print "deleting '$n'\n" if $debug;
622 8         15 delete $prefix{$n};
623 8 100       20 if (!$argIsUnit) { $u = $unit_by_name{$n}; }
  5         9  
624 8         16 delete $unit_by_name{$n};
625             # Delete the array element matching $n from @{$u->{names}}
626 8 100       15 if (!$argIsUnit) {
627 5         11 $u->{names} = [ grep { $_ ne $n } @{$u->{names}} ];
  15         44  
  5         14  
628             }
629             }
630 4 100       18 if ($argIsUnit) { $u->{names} = []; }
  1         4  
631             }
632              
633              
634             sub new {
635 48     48 1 2527 my $proto = shift;
636 48         93 my $class;
637              
638             my $self;
639 48 100       111 if (ref $proto) { # object method
640 2         7 $self = $proto->copy;
641             }
642             else { # class method
643 46         74 my $r = shift;
644 46         130 $self = CreateUnit($r);
645             }
646              
647 48         192 $self->AddNames(@_);
648 48         202 return $self;
649             }
650              
651             sub type {
652 100     100 1 2069 my $self = shift;
653              
654             # See if the user is setting the type
655 100         128 my $t;
656 100 100       226 if ($t = shift) {
657             # XXX Maybe we should check that $t is a valid type name, and
658             # XXX that it's type really does match.
659 34         58 return $self->{type} = $t;
660             }
661              
662             # If the type is known already, return it
663 66 100       195 return $self->{type} if $self->{type};
664              
665             # See if it is a prefix
666 62         170 my $name = $self->name();
667              
668             return $self->{type} = 'prefix'
669 62 50 66     179 if defined $name && defined $prefix{$name};
670              
671             # Collect all matching types
672 62         95 my @t;
673 62         460 for (keys %prototype) {
674             push @t, $_
675 1867 100       3433 unless CompareDim($self, $prototype{$_});
676             }
677              
678             # Return value depends on whether we got zero, one, or multiple types
679 62 100       337 return undef unless @t;
680 55 100       611 return $self->{type} = $t[0] if @t == 1;
681 1         6 return \@t;
682             }
683              
684             sub name {
685 189     189 1 313 my $self = shift;
686 189         323 my $n = $self->{names};
687 189         781 return $$n[0];
688             }
689              
690             sub abbr {
691 28     28 1 35 my $self = shift;
692 28         51 my $n = ${$self->{names}}[0];
  28         54  
693 28 50       56 return undef unless defined $n;
694              
695 28         62 for ($self->names()) {
696 117 100       191 $n = $_ if length $_ < length $n;
697             }
698 28         61 return $n;
699             }
700              
701             sub names {
702 31     31 1 1529 my $self = shift;
703 31         38 return @{$self->{names}};
  31         117  
704             }
705              
706             sub def {
707 7     7 1 13 my $self = shift;
708 7         32 return $self->{def};
709             }
710              
711             sub expanded {
712 11     11 1 38 my $self = shift;
713 11         25 my $s = $self->{factor};
714 11 100       38 $s = '' if $s == 1;
715              
716 11         19 my $i = 0;
717 11         20 for my $d (@{$self->{dim}}) {
  11         35  
718 118 100       231 if ($d) {
719             #print "Dimension index $i is $d\n";
720 22 100       56 if ($s) { $s .= " "; }
  20         79  
721 22         63 $s .= $BaseName[$i];
722 22 100       63 $s .= "^$d" unless $d == 1;
723             }
724 118         179 $i++;
725             }
726              
727 11 50       32 $s = 1 if $s eq '';
728 11         99 return $s;
729             }
730              
731             sub ToString {
732 31     31 1 48 my $self = shift;
733 31   66     71 return $self->name || $self->def || $self->expanded;
734             }
735              
736             sub factor {
737 15     15 1 24 my $self = shift;
738 15 100       42 if (@_) {
739 6         16 $self->CheckChange;
740 6         12 $self->{factor} = shift;
741             }
742 15         42 return $self->{factor};
743             }
744              
745             sub convert {
746 56     56 1 143 my ($self, $other) = @_;
747 56         132 my $u = GetUnit($other);
748 56 50       124 carp "Can't convert ". $self->name() .' to '. $u->name()
749             if CompareDim($self, $u);
750 56         312 return $self->{factor} / $u->{factor};
751             }
752              
753             sub times {
754 788     788 1 1057 my $self = shift;
755 788         1701 $self->CheckChange;
756 788         1426 my $u = GetUnit(shift);
757 788         1703 $self->{factor} *= $u->{factor};
758              
759 788         1516 for (0 .. $NumBases) {
760 7916 100       13114 my $u_val = defined $u->{dim}[$_] ? $u->{dim}[$_] : 0;
761 7916 100       11686 if (defined $self->{dim}[$_]) {
762 7898         11518 $self->{dim}[$_] += $u_val;
763             }
764             else {
765 18         73 $self->{dim}[$_] = $u_val;
766             }
767             }
768              
769 788         1235 $self->{type} = '';
770 788         2724 return $self;
771             }
772              
773             sub recip {
774 206     206 1 310 my $self = shift;
775 206         1940 $self->CheckChange;
776 206         474 $self->{factor} = 1 / $self->{factor};
777              
778 206         427 for (0 .. $NumBases) {
779 2074 100       3008 if (defined $self->{dim}[$_]) {
780 2066         3150 $self->{dim}->[$_] = -$self->{dim}->[$_];
781             }
782             else {
783 8         28 $self->{dim}[$_] = 0;
784             }
785             }
786              
787 206         449 return $self;
788             }
789              
790             sub divide {
791 201     201 1 409 my ($self, $other) = @_;
792 201         358 my $u = GetUnit($other)->copy;
793 201         511 $self->times($u->recip);
794             }
795              
796             sub power {
797 129     129 1 197 my $self = shift;
798 129         287 $self->CheckChange;
799 129         164 my $p = shift;
800 129 50       309 die 'Exponentiation to integer values only, please'
801             unless $p == int $p;
802 129         244 $self->{factor} **= $p;
803              
804 129         260 for (0 .. $NumBases) {
805 1295 100       2120 $self->{dim}[$_] = 0 unless defined $self->{dim}[$_];
806 1295         1768 $self->{dim}[$_] *= $p;
807             }
808              
809 129         200 $self->{type} = '';
810 129         198 return $self;
811             }
812              
813             sub add {
814 0     0 1 0 my $self = shift;
815 0         0 $self->CheckChange;
816              
817 0         0 my $other = shift;
818 0         0 my $u = GetUnit($other);
819              
820 0 0       0 croak "Can't add ". $u->type .' to a '. $self->type
821             if CompareDim($self, $u);
822 0         0 $self->{factor} += $u->{factor};
823 0         0 return $self;
824             }
825              
826             sub neg {
827 0     0 1 0 my $self = shift;
828 0         0 $self->CheckChange;
829 0         0 $self->{factor} = -$self->{factor};
830 0         0 return $self;
831             }
832              
833             sub subtract {
834 0     0 1 0 my ($self, $other) = @_;
835 0         0 my $u = GetUnit($other)->copy;
836 0         0 $self->add( $u->neg );
837             }
838              
839             sub copy {
840 1206     1206 1 1670 my $self = shift;
841             my $n = {
842             'factor' => $self->{factor},
843 1206         7436 'dim' => [@{$self->{dim}}],
844             'type' => $self->{type},
845             'names' => [],
846             'def' => $self->{def},
847 1206         1933 };
848              
849 1206         2353 bless $n, 'Physics::Unit';
850 1206         1807 return $n;
851             }
852              
853             sub equal {
854 6     6 1 18 my $obj1 = shift;
855              
856             # If it was called as a class method, throw away the first
857             # argument (the class name)
858 6 100       18 $obj1 = shift unless ref $obj1;
859 6         73 $obj1 = GetUnit($obj1);
860 6         32 my $obj2 = GetUnit(shift);
861              
862 6 50       18 return 0 if CompareDim($obj1, $obj2);
863 6 50       21 return 0 unless $obj1->{factor} == $obj2->{factor};
864 6         36 return 1;
865             }
866              
867             sub NewOne {
868 724     724 0 3759 my $u = {
869             'factor' => 1,
870             'dim' => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
871             'type' => undef,
872             'names' => [],
873             'def' => undef,
874             };
875 724         1598 bless $u, 'Physics::Unit';
876             }
877              
878             sub AddNames {
879 904     904 0 1121 my $self = shift;
880 904         1068 my $n;
881 904         1861 while ($n = shift) {
882 1717 50       2910 croak "Can't use a reference as a name!" if ref $n;
883 1717 50       2501 carp "Name $n is already defined" if LookName($n);
884 1717         2135 push @{$self->{names}}, "\L$n";
  1717         4019  
885 1717         7934 $unit_by_name{$n} = $self;
886             }
887             }
888              
889             sub NewType {
890 92     92 0 158 my ($self, $t) = @_;
891             # my $oldtype = $self->type;
892             # croak "NewType: the type $t is already defined as $oldtype"
893             # if $oldtype ne 'unknown';
894 92         138 $self->{type} = $t;
895 92         307 $prototype{$t} = $self;
896             }
897              
898             sub CreateUnit {
899 821     821 0 1127 my $def = shift;
900             # argument was a Unit object
901 821 100       1370 return $def->new() if ref $def;
902             # argument was either a simple name or an expression - doesn't matter
903 820         1359 $def = lc $def;
904              
905 820         1300 my $u = expr($def);
906 820         1641 $u->{def} = $def;
907 820         1284 return $u;
908             }
909              
910             sub CompareDim {
911 1929     1929 0 3206 my ($u1, $u2) = @_;
912 1929         2872 my $d1 = $u1->{dim};
913 1929         2912 my $d2 = $u2->{dim};
914              
915 1929         3148 for (0 .. $NumBases) {
916 4111 100       7390 $$d1[$_] = 0 unless defined $$d1[$_];
917 4111 100       7132 $$d2[$_] = 0 unless defined $$d2[$_];
918 4111         5869 my $c = ($$d1[$_] <=> $$d2[$_]);
919 4111 100       9666 return $c if $c;
920             }
921 118         377 return 0;
922             }
923              
924             sub LookName {
925 2743     2743 0 4596 my $name = shift;
926 2743 50       5243 return 3 if defined $prototype{$name};
927 2743 100       5350 return 2 if defined $unit_by_name{$name};
928 1838 100       3038 return 1 if defined $reserved_word{$name};
929 1812         3138 return 0;
930             }
931              
932             sub DebugString {
933 0     0 0 0 my $self = shift;
934 0         0 my $s = $self->{factor};
935 0         0 $s .= '['. join (', ', @{$self->{dim}}) .']';
  0         0  
936 0         0 return $s;
937             }
938              
939             sub CheckChange {
940 1129     1129 0 1427 my $self = shift;
941 1129 50       2152 carp "You're not allowed to change named units!" if $self->{names}[0];
942 1129         1966 $self->{names} = [];
943 1129         2128 $self->{type} = $self->{def} = undef;
944             }
945              
946             # global variables used for parsing.
947             my $def; # string being parsed
948             my $tok; # the token type
949             my $numval; # the value when the token is a number
950             my $tokname; # when it is a name
951             my $indent; # used to indent debug messages
952              
953             # parser
954             sub expr {
955 823 100   823 0 1425 if (@_) {
956 820         1112 $def = shift;
957 820         1342 get_token();
958             }
959              
960 823 50       1764 print ' ' x $indent, "inside expr\n" if $debug;
961 823         1073 $indent++;
962 823         1398 my $u = term();
963              
964 823         1069 for (;;) {
965 1025 100       2038 if ($tok eq 'times') {
    100          
966 4         15 get_token();
967 4         13 $u->times(term());
968             }
969             elsif ($tok eq 'divide') {
970 198         394 get_token();
971 198         345 $u->divide(term());
972             }
973             else {
974 823 50       1372 print ' ' x $indent, "expr: returning ", $u->DebugString, "\n"
975             if $debug;
976 823         932 $indent--;
977 823         1353 return $u;
978             }
979             }
980             }
981              
982             sub term {
983 1025 50   1025 0 1694 print ' ' x $indent, "inside term\n" if $debug;
984 1025         1261 $indent++;
985 1025         1667 my $u = Factor();
986              
987 1025         1379 for (;;) {
988 1507 50       2457 print ' ' x $indent, "inside term loop\n" if $debug;
989 1507 100 66     7889 if ($tok eq 'number' ||
      100        
      100        
      66        
990             $tok eq 'name' ||
991             $tok eq 'prefix' ||
992             $tok eq 'square' ||
993             $tok eq 'cubic')
994             {
995 482         901 $u->times(Factor());
996             }
997             else {
998 1025 50       1786 print ' ' x $indent, "term: returning ", $u->DebugString, "\n"
999             if $debug;
1000 1025         1301 $indent--;
1001 1025         1818 return $u;
1002             }
1003             }
1004             }
1005              
1006             sub Factor {
1007 1507 50   1507 0 2600 print ' ' x $indent, "inside factor\n" if $debug;
1008 1507         1800 $indent++;
1009              
1010 1507         2292 my $u = prim();
1011              
1012 1507         1951 for (;;) {
1013 1621 50       2691 print ' ' x $indent, "inside factor loop\n" if $debug;
1014 1621 100       2576 if ($tok eq 'exponent') {
1015 114         217 get_token();
1016 114 50       237 die 'Exponent must be an integer'
1017             unless $tok eq 'number';
1018 114         331 $u->power($numval);
1019 114         175 get_token();
1020             }
1021             else {
1022 1507 50       2527 print ' ' x $indent, "factor: returning ",
1023             $u->DebugString, "\n" if $debug;
1024 1507         1788 $indent--;
1025 1507         2729 return $u;
1026             }
1027             }
1028             }
1029              
1030             sub prim {
1031 1614 50   1614 0 2700 print ' ' x $indent, "inside prim\n" if $debug;
1032 1614         2790 $indent++;
1033              
1034 1614         1951 my $u;
1035              
1036 1614 100       3397 if ($tok eq 'number') {
    100          
    100          
    100          
    50          
    100          
    50          
1037 607 50       974 print ' ' x $indent, "got number $numval\n" if $debug;
1038             # Create a new Unit object to represent this number
1039 607         1046 $u = NewOne();
1040 607         1008 $u->{factor} = $numval;
1041 607         955 get_token();
1042             }
1043             elsif ($tok eq 'prefix') {
1044 93 50       170 print ' ' x $indent, "got a prefix: ", "$tokname\n" if $debug;
1045 93         178 $u = GetUnit($tokname)->copy();
1046 93         202 get_token();
1047 93         211 $u->times(prim());
1048             }
1049             elsif ($tok eq 'name') {
1050 897 50       1440 print ' ' x $indent, "got a name: ", "$tokname\n" if $debug;
1051 897         1603 $u = GetUnit($tokname)->copy();
1052 897         1545 get_token();
1053             }
1054             elsif ($tok eq 'lparen') {
1055 3 50       10 print ' ' x $indent, "got a left paren\n" if $debug;
1056 3         25 get_token();
1057 3         21 $u = expr();
1058 3 50       27 die 'Missing right parenthesis'
1059             unless $tok eq 'rparen';
1060 3         18 get_token();
1061             }
1062             elsif ($tok eq 'end') {
1063 0 0       0 print ' ' x $indent, "got end\n" if $debug;
1064 0         0 $u = NewOne();
1065             }
1066             elsif ($tok eq 'square') {
1067 11         27 get_token();
1068 11         41 $u = prim()->power(2);
1069             }
1070             elsif ($tok eq 'cubic') {
1071 3         10 get_token();
1072 3         13 $u = prim()->power(3);
1073             }
1074             else {
1075 0         0 die 'Primary expected';
1076             }
1077              
1078 1614 50       2960 print ' ' x $indent, "prim: returning ", $u->DebugString, "\n"
1079             if $debug;
1080 1614         2112 $indent--;
1081              
1082             # Before returning, see if the *next* token is 'squared' or 'cubed'
1083 1614         2009 for(;;) {
1084 1615 100       3249 if ($tok eq 'squared') {
    50          
1085 1         6 get_token();
1086 1         4 $u->power(2);
1087             }
1088             elsif ($tok eq 'cubed') {
1089 0         0 get_token();
1090 0         0 $u->power(3);
1091             }
1092             else {
1093 1614         2209 last;
1094             }
1095             }
1096              
1097 1614         2517 return $u;
1098             }
1099              
1100             sub get_token {
1101 2868 50   2868 0 4937 print ' ' x $indent, "get_token, looking at '$def'\n" if $debug;
1102              
1103             # First remove whitespace at the begining
1104 2868         6674 $def =~ s/^\s+//;
1105              
1106 2868 100       5194 if ($def eq '') {
1107 820         1158 $tok = 'end';
1108 820         1141 return;
1109             }
1110              
1111 2048 100 100     16227 if ($def =~ s/^\(//) {
    100          
    100          
    100          
    100          
    100          
    50          
1112 3         7 $tok = 'lparen';
1113             }
1114             elsif ($def =~ s/^\)//) {
1115 3         6 $tok = 'rparen';
1116             }
1117             elsif ($def =~ s/^\*\*// || $def =~ s/^\^//) {
1118 114         188 $tok = 'exponent';
1119             }
1120             elsif ($def =~ s/^\*//) {
1121 4         10 $tok = 'times';
1122             }
1123             elsif ($def =~ s/^\///) {
1124 187         311 $tok = 'divide';
1125             }
1126             elsif ($def =~ s/^$number_re//io) {
1127 721         2518 $numval = $1 + 0; # convert to a number
1128 721         1252 $tok = 'number';
1129             }
1130             elsif ($def =~ /^([^\ \n\r\t\f\(\)\/\^\*]+)/) {
1131 1016         2307 my $t = $1;
1132 1016         1689 my $l = LookName($t);
1133              
1134 1016 100       2431 if ($l == 1) {
    100          
1135 26         64 $tok = $reserved_word{$t};
1136 26         45 $tokname = $t;
1137 26         55 $def = substr $def, length($t);
1138 26         62 return;
1139             }
1140             elsif ($l == 2) {
1141 897         1227 $tok = 'name';
1142 897         1179 $tokname = $t;
1143 897         1727 $def = substr $def, length($t);
1144 897         1455 return;
1145             }
1146              
1147             # Couldn't find the name on the first try, look for prefix
1148 93         887 for my $p (keys %prefix) {
1149 1516 100       9928 if ($t =~ /^$p/i) {
1150 93         197 $tok = 'prefix';
1151 93         143 $tokname = $p;
1152 93         192 $def = substr $def, length($p);
1153 93         229 return;
1154             }
1155             }
1156 0           die "Unknown unit: $t\n";
1157             }
1158             else {
1159 0           die "Illegal token in $def";
1160             }
1161             }
1162              
1163             1;
1164             __END__