File Coverage

blib/lib/Physics/Unit.pm
Criterion Covered Total %
statement 315 345 91.3
branch 146 192 76.0
condition 27 45 60.0
subroutine 41 46 89.1
pod 29 42 69.0
total 558 670 83.2


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