File Coverage

blib/lib/Physics/Unit/Scalar.pm
Criterion Covered Total %
statement 221 282 78.3
branch 68 134 50.7
condition 36 81 44.4
subroutine 42 49 85.7
pod 13 18 72.2
total 380 564 67.3


line stmt bran cond sub pod time code
1             package Physics::Unit::Scalar;
2              
3 2     2   147380 use strict;
  2         4  
  2         83  
4 2     2   19 use warnings;
  2         11  
  2         130  
5 2     2   15 use Carp;
  2         4  
  2         227  
6 2     2   13 use base qw(Exporter);
  2         10  
  2         382  
7 2     2   13 use vars qw( $VERSION @EXPORT_OK %EXPORT_TAGS $debug);
  2         3  
  2         248  
8              
9             $VERSION = '0.60';
10             $VERSION = eval $VERSION;
11              
12             @EXPORT_OK = qw( ScalarFactory GetScalar );
13             %EXPORT_TAGS = ('ALL' => \@EXPORT_OK);
14              
15 2     2   1283 use Physics::Unit ':ALL';
  2         8  
  2         2540  
16              
17              
18             InitSubtypes();
19              
20             sub new {
21 27     27 1 557253 my $proto = shift;
22 27 50       114 print "Scalar::new: proto is $proto.\n" if $debug;
23 27         42 my $class;
24 27         51 my $self = {};
25              
26 27 100       70 if (ref $proto) {
27             # Copy constructor
28 9         21 $class = ref $proto;
29 9         60 $self->{$_} = $$proto{$_} for keys %$proto;
30             }
31             else {
32             # Construct from a definition string
33             # Get the definition string, and remove whitespace
34 18         32 my $def = shift;
35 18 50       72 print "def is '$def'.\n" if $debug;
36 18 100       46 if (defined $def) {
37 17         163 $def =~ s/^\s*(.*?)\s*$/$1/;
38             }
39              
40 18         38 $class = $proto;
41              
42             # Convert the argument into a unit object
43 18 100       49 if ($class eq 'Physics::Unit::Scalar') {
44             # Build a generic Physics::Unit::Scalar object
45              
46 3         7 return ScalarFactory($def);
47              
48             #my $u = Physics::Unit->new($def);
49             #$self->{value} = $u->factor;
50             #$u->factor(1);
51             #$self->{MyUnit} = $self->{default_unit} = $u;
52             }
53             else {
54             # The user specified the type of Scalar explicitly
55             my $mu = $self->{MyUnit} = $self->{default_unit} =
56 15         72 GetMyUnit($class);
57              
58             # If no definition string was given, then set the value to
59             # one.
60              
61 15 100 66     447 if (!defined $def || $def eq '') {
    100          
62 1         2 $self->{value} = 1;
63             }
64              
65             # If the definition consists of just a number, then we'll use
66             # the default unit
67              
68             elsif ($def =~ /^$Physics::Unit::number_re$/io) {
69 2         8 $self->{value} = $def + 0; # convert to number
70             }
71              
72             else {
73 12         50 my $u = GetUnit($def);
74              
75 12 100       50 croak 'Unit definition string is of incorrect type'
76             if 'Physics::Unit::' . $u->type ne $class;
77              
78 11         51 $self->{value} = $u->convert($mu);
79             }
80             }
81             }
82              
83 23         119 bless $self, $class;
84             }
85              
86             sub ScalarFactory {
87 33     33 1 184 my $self = {
88             value => 1,
89             MyUnit => Physics::Unit->new(shift),
90             };
91              
92             # Call the mystery ScalarResolve() function.
93 33         88 return ScalarResolve($self);
94             }
95              
96             sub default_unit {
97 1     1 1 3 my $proto = shift;
98 1 50       6 if (ref $proto) {
99 1         5 return $proto->{default_unit};
100             }
101             else {
102 0         0 return GetDefaultUnit($proto);
103             }
104             }
105              
106             sub ToString {
107 11     11 1 2319 my $self = shift;
108 11 100       53 return $self->value .' '. $self->MyUnit->ToString unless @_;
109 1         6 my $u = GetUnit(shift);
110 1         4 my $v = $self->value * $self->MyUnit->convert($u);
111 1         4 return $v .' '. $u->ToString;
112             }
113              
114             sub convert {
115 1     1 1 897 my $self = shift;
116              
117 1         5 my $u = GetUnit(shift);
118              
119 1 50 33     17 croak 'convert called with invalid parameters'
120             if !ref $self || !ref $u;
121              
122 1         5 return $self->value * $self->MyUnit->convert($u);
123             }
124              
125             sub value {
126 39     39 1 966 my $self = shift;
127 39 50       92 $self->{value} = shift if @_;
128 39         376 return $self->{value};
129             }
130              
131             sub add {
132 1     1 1 937 my $self = shift;
133              
134 1         4 my $other = GetScalar(shift);
135              
136 1 50 33     8 croak 'Invalid arguments to Physics::Unit::Scalar::add'
137             if !ref $self || !ref $other;
138 1 50       6 carp "Scalar types don't match in add()"
139             if ref $self ne ref $other;
140              
141 1         3 $self->{value} += $other->{value};
142              
143 1         5 return $self;
144             }
145              
146             sub neg {
147 0     0 1 0 my $self = shift;
148 0 0       0 croak 'Invalid arguments to Physics::Unit::Scalar::neg'
149             if !ref $self;
150              
151 0         0 $self->{value} = - $self->{value};
152             }
153              
154             sub subtract {
155 0     0 1 0 my $self = shift;
156              
157 0         0 my $other = GetScalar(shift);
158              
159 0 0 0     0 croak 'Invalid arguments to Physics::Unit::Scalar::subtract'
160             if !(ref $self) || !(ref $other);
161 0 0       0 carp "Scalar types don't match in subtract()"
162             if ref $self ne ref $other;
163              
164 0         0 $self->{value} -= $other->{value};
165              
166 0         0 return $self;
167             }
168              
169             sub times {
170 8     8 1 32 my $self = shift;
171 8         19 my $other = GetScalar(shift);
172              
173 8 50 33     56 croak 'Invalid arguments to Physics::Unit::Scalar::times'
174             if !ref $self || !ref $other;
175              
176 8         185 my $value = $self->{value} * $other->{value};
177              
178 8         28 my $mu = $self->{MyUnit}->copy;
179              
180 8         30 $mu->times($other->{MyUnit});
181              
182 8         45 my $newscalar = {
183             value => $value,
184             MyUnit => $mu,
185             };
186              
187 8         22 return ScalarResolve($newscalar);
188             }
189              
190             sub recip {
191 5     5 1 12 my $self = shift;
192 5 50       13 croak 'Invalid argument to Physics::Unit::Scalar::recip'
193             unless ref $self;
194              
195             croak 'Attempt to take reciprocal of a zero Scalar'
196 5 50       14 unless $self->{value};
197              
198 5         19 my $mu = $self->{MyUnit}->copy;
199              
200             my $newscalar = {
201             value => 1 / $self->{value},
202 5         23 MyUnit => $mu->recip,
203             };
204              
205 5         37 return ScalarResolve($newscalar);
206             }
207              
208             sub divide {
209 2     2 1 8 my $self = shift;
210              
211 2         6 my $other = GetScalar(shift);
212              
213 2 50 33     15 croak 'Invalid arguments to Physics::Unit::Scalar::times'
214             if !ref $self || !ref $other;
215              
216 2         10 my $arg = $other->recip;
217              
218 2         13 return $self->times($arg);
219             }
220              
221             sub GetScalar {
222 54     54 1 2306 my $n = shift;
223 54 100       134 if (ref $n) {
224 24         56 return $n;
225             }
226             else {
227 30         75 return ScalarFactory($n);
228             }
229             }
230              
231             sub InitSubtypes {
232 2     2 0 13 for my $type (ListTypes()) {
233 60 50       125 print "Creating class $type\n" if $debug;
234              
235 60         118 my $prototype = GetTypeUnit($type);
236 60   66     157 my $type_unit_name = $prototype->name || $prototype->def;
237             {
238 2     2   27 no strict 'refs';
  2         4  
  2         89  
  60         92  
239 2     2   11 no warnings 'once';
  2         3  
  2         443  
240 60         90 my $package = 'Physics::Unit::' . $type;
241 60         82 @{$package . '::ISA'} = qw(Physics::Unit::Scalar);
  60         1108  
242 60         165 ${$package . '::DefaultUnit'} = ${$package . '::MyUnit'} =
  60         270  
  60         277  
243             GetUnit( $type_unit_name );
244             }
245             }
246             }
247              
248             sub MyUnit {
249 32     32 0 52 my $proto = shift;
250 32 50       72 if (ref ($proto)) {
251 32         102 return $proto->{MyUnit};
252             }
253             else {
254 0         0 return GetMyUnit($proto);
255             }
256             }
257              
258             sub GetMyUnit {
259 55     55 0 107 my $class = shift;
260 2     2   12 no strict 'refs';
  2         4  
  2         119  
261 55         89 return ${$class . '::MyUnit'};
  55         363  
262             }
263              
264             sub GetDefaultUnit {
265 0     0 0 0 my $class = shift;
266 2     2   8 no strict 'refs';
  2         2  
  2         3392  
267 0         0 return ${$class . '::DefaultUnit'};
  0         0  
268             }
269              
270             sub ScalarResolve {
271 46     46 0 72 my $self = shift;
272              
273 46         81 my $mu = $self->{MyUnit};
274 46         140 my $type = $mu->type;
275              
276 46 100       157 if ($type) {
277 40 50       116 $type = 'dimensionless' if $type eq 'prefix';
278 40         146 $type = 'Physics::Unit::' . $type;
279              
280 40         98 my $newunit = GetMyUnit($type);
281 40         136 $self->{value} *= $mu->convert($newunit);
282 40         79 $self->{MyUnit} = $newunit;
283 40         92 $self->{default_unit} = $newunit;
284             }
285             else {
286 6         14 $type = "Physics::Unit::Scalar";
287              
288 6         27 $self->{value} *= $mu->factor;
289 6         18 $mu->factor(1);
290 6         27 $self->{default_unit} = $mu;
291             }
292              
293 46         353 bless $self, $type;
294             }
295              
296             ######### Overloading ######################
297             # Doesn't use the existing methods but
298             # defines new ones, so as not to interfere
299             # with any existing functionality.
300             ############################################
301              
302             our $format_string; # can be set to provide a
303             # parameter for sprintf() in the overloaded
304             # ToString function.
305              
306             sub _overload_ToString {
307 20     20   36 my $self = shift;
308              
309 20 50       44 return sprintf($format_string, $self->value) .' '. $self->MyUnit->ToString
310             if defined $format_string;
311              
312 20         52 return $self->value .' '. $self->MyUnit->ToString;
313             }
314              
315             # regarding string comparators, I think there is an
316             # argument that the numerical comparators (<=>) should work
317             # solely on the numeric component, and the string
318             # comparators (cmp) could work on the dimensions and their
319             # degree. For example, "GetScalar('1 m^2') lt GetScalar('1 m^3')"
320             # would be true. This would allow the user some flexibility
321             # in sorting. I've not done it because it would probably
322             # require a lot of time discussing and implementing to
323             # get right.
324             sub _overload_eq {
325 9     9   5765 my $self = shift;
326 9         32 my $other = GetScalar(shift);
327              
328 9 50 33     66 croak 'Invalid arguments to Physics::Unit::Scalar::_overload_eq'
329             if !ref $self || !ref $other;
330              
331 9         39 return $self->_overload_ToString() eq $other->_overload_ToString();
332             }
333              
334             sub _overload_ne {
335 1     1   471 my $self = shift;
336 1         4 my $other = GetScalar(shift);
337              
338 1 50 33     10 croak 'Invalid arguments to Physics::Unit::Scalar::_overload_ne'
339             if !ref $self || !ref $other;
340              
341 1         5 return $self->_overload_ToString() ne $other->_overload_ToString();
342             }
343              
344             # There appears to be a bug in ScalarResolve()
345             # if $mu->type comes back with an arrayref,
346             # e.g. in the case of ambiguous type for a derived
347             # unit such as ['Energy', 'Torque'], then it crashes,
348             # as it doesn't seem to be able to handle that.
349             # _ScalarResolve() tries to fix this by checking
350             # global variable @type_context to see if the user
351             # has set a preferred type to use (a hacky solution,
352             # admittedly); or if not, it just takes the first
353             # entry in the type arrayref so as not to crash.
354             our @type_context = ();
355              
356             # See if the user has set a preferred unit type for
357             # the calculations they are performing.
358             sub _DisambiguateType {
359 0     0   0 my $ar = shift;
360              
361 0 0       0 if ( scalar(@type_context) ) {
362 0         0 foreach my $type (@{$ar}) {
  0         0  
363 0         0 foreach my $preferred (@type_context) {
364 0 0       0 if ( $type eq $preferred ) {
365 0         0 return $type;
366             }
367             }
368             }
369             }
370              
371 0         0 return $ar->[0];
372             }
373              
374             sub _ScalarResolve {
375 0     0   0 my $self = shift;
376              
377 0         0 my $mu = $self->{MyUnit};
378 0         0 my $type = $mu->type;
379              
380 0 0       0 if ($type) {
381             # the following line is the only change to the original
382 0 0       0 $type = _DisambiguateType($type) if ref($type) eq 'ARRAY';
383 0 0       0 $type = 'dimensionless' if $type eq 'prefix';
384 0         0 $type = 'Physics::Unit::' . $type;
385              
386 0         0 my $newunit = GetMyUnit($type);
387 0         0 $self->{value} *= $mu->convert($newunit);
388 0         0 $self->{MyUnit} = $newunit;
389 0         0 $self->{default_unit} = $newunit;
390             }
391             else {
392 0         0 $type = "Physics::Unit::Scalar";
393              
394 0         0 $self->{value} *= $mu->factor;
395 0         0 $mu->factor(1);
396 0         0 $self->{default_unit} = $mu;
397             }
398              
399 0         0 bless $self, $type;
400             }
401              
402             sub _overload_add {
403 2     2   1763 my $self = shift;
404 2         9 my $other = GetScalar(shift);
405              
406 2 50 33     15 croak 'Invalid arguments to Physics::Unit::Scalar::_overload_add'
407             if !ref $self || !ref $other;
408              
409             # overloading should return a new object
410 2         8 my $n = $self->new();
411              
412             # be a bit strict here about what can be added to what else
413 2 50 66     19 if ( (ref($self) eq ref($other)) ||
      66        
414             (ref($self) eq 'Physics::Unit::Dimensionless') ||
415             (ref($other) eq 'Physics::Unit::Dimensionless') ) {
416              
417 2         63 $n->{value} += $other->{value};
418             }
419             else {
420 0         0 carp 'Cannot add a ' . ref($self) . ' to a ' . ref($other);
421             }
422              
423 2         16 return $n;
424             }
425              
426             sub _overload_subtract {
427 2     2   7 my $self = shift;
428 2         9 my $other = GetScalar(shift);
429 2         5 my $swapped = shift;
430              
431 2 50 33     12 croak 'Invalid arguments to Physics::Unit::Scalar::_overload_subtract'
432             if !ref $self || !ref $other;
433              
434 2         6 my $n = $self->new();
435              
436 2 50 66     20 if ( (ref($self) eq ref($other)) ||
      66        
437             (ref($self) eq 'Physics::Unit::Dimensionless') ||
438             (ref($other) eq 'Physics::Unit::Dimensionless') ) {
439              
440 2 100 66     11 if ( defined($swapped) and ($swapped == 1) ) {
441 1         5 $n->{value} = $other->{value} - $n->{value};
442             }
443             else {
444 1         4 $n->{value} -= $other->{value};
445             }
446             }
447             else {
448              
449 0 0 0     0 if ( defined($swapped) and ($swapped == 1) ) {
450 0         0 carp 'Cannot subtract a ' . ref($self) . ' from a ' . ref($other);
451             }
452             else {
453 0         0 carp 'Cannot subtract a ' . ref($other) . ' from a ' . ref($self);
454             }
455             }
456              
457 2         9 return $n;
458             }
459              
460             sub _overload_times {
461 0     0   0 my $self = shift;
462 0         0 my $other = GetScalar(shift);
463              
464 0 0 0     0 croak 'Invalid arguments to Physics::Unit::Scalar::_overload_times'
465             if !ref $self || !ref $other;
466              
467 0         0 my $value = $self->{value} * $other->{value};
468              
469 0         0 my $mu = $self->{MyUnit}->copy;
470              
471 0         0 $mu->times($other->{MyUnit});
472              
473 0         0 my $newscalar = {
474             value => $value,
475             MyUnit => $mu,
476             };
477              
478 0         0 return _ScalarResolve($newscalar);
479             }
480              
481             sub _overload_divide {
482 3     3   250 my $self = shift;
483 3         7 my $other = GetScalar(shift);
484 3         7 my $swapped = shift;
485              
486 3 50 33     15 croak 'Invalid arguments to Physics::Unit::Scalar::_overload_divide'
487             if !ref $self || !ref $other;
488              
489 3 50 66     15 if ( defined($swapped) and ($swapped == 1) ) {
490 0         0 my $arg = $self->recip;
491 0         0 return $other->times($arg);
492             }
493             else {
494 3         19 my $arg = $other->recip;
495 3         13 return $self->times($arg);
496             }
497             }
498              
499             sub _overload_power {
500 2     2   6 my $self = shift;
501 2         7 my $other = GetScalar(shift);
502              
503 2 50 33     15 croak 'Invalid arguments to Physics::Unit::Scalar::_overload_power'
504             if !ref $self || !ref $other;
505            
506 2 50       9 croak "Physics::Unit::Scalar::_overload_power: can only raise to dimensionless powers (got '$other')"
507             if ref($other) ne 'Physics::Unit::Dimensionless';
508              
509 2         13 my $p = $other->value();
510              
511 2 50       6 croak "Physics::Unit::Scalar::_overload_power: can only raise to integer powers currently (got '$p')"
512             unless $p == int($p);
513              
514 2         8 my $n = $self->new();
515              
516             # be explicit about different scenarios
517 2 50       16 if ( $p < -1 ) {
    50          
    50          
    50          
518 0         0 $p = abs($p)-1;
519 0         0 $n = $n->times($self) while $p--;
520 0         0 return $n->recip;
521             }
522             elsif ( $p == -1 ) {
523 0         0 return $n->recip;
524             }
525             elsif ( $p == 0 ) {
526 0         0 return GetScalar(1);
527             }
528             elsif ( $p == 1 ) {
529 0         0 return $n;
530             }
531             else {
532 2         3 $p--;
533 2         15 $n = $n->times($self) while $p--;
534 2         13 return $n;
535             }
536              
537             }
538              
539             sub _overload_sin {
540 1     1   1407 my $self = shift;
541              
542 1 50       6 croak 'Invalid arguments to Physics::Unit::Scalar::_overload_sin'
543             if !ref $self;
544              
545 1 50       6 carp "Warning: Arguments to sin() would be without dimension, traditionally. (got '$self')"
546             unless ref($self) eq 'Physics::Unit::Dimensionless';
547              
548 1         39 return sin($self->{value});
549             }
550              
551             sub _overload_cos {
552 1     1   1007 my $self = shift;
553              
554 1 50       6 croak 'Invalid arguments to Physics::Unit::Scalar::_overload_cos'
555             if !ref $self;
556              
557 1 50       4 carp "Warning: Arguments to cos() would be without dimension, traditionally. (got '$self')"
558             unless ref($self) eq 'Physics::Unit::Dimensionless';
559              
560 1         26 return cos($self->{value});
561             }
562              
563             sub _overload_atan2 {
564 3     3   531 my $self = shift;
565 3         7 my $other = GetScalar(shift);
566 3         6 my $swapped = shift;
567              
568 3 50 33     12 croak 'Invalid arguments to Physics::Unit::Scalar::_overload_atan2'
569             if !ref $self || !ref $other;
570              
571 3         3 my $n;
572              
573 3 50 66     19 if ( (ref($self) eq ref($other)) ||
      66        
574             (ref($self) eq 'Physics::Unit::Dimensionless') ||
575             (ref($other) eq 'Physics::Unit::Dimensionless') ) {
576              
577 3 100       28 my $atan2v = $swapped ? atan2($other->{value}, $self->{value}) : atan2($self->{value}, $other->{value});
578 3 50       20 $n = GetScalar("$atan2v radians") if defined $atan2v;
579             }
580             else {
581 0         0 croak 'Cannot perform atan2 of ' . ref($self) . ' with ' . ref($other);
582             }
583              
584 3         9 return $n;
585             }
586              
587             sub _overload_exp {
588 1     1   2 my $self = shift;
589              
590 1 50       3 croak 'Invalid arguments to Physics::Unit::Scalar::_overload_exp'
591             if !ref $self;
592              
593 1 50       3 carp "Warning: Arguments to exp() would be without dimension, traditionally. (got '$self')"
594             unless ref($self) eq 'Physics::Unit::Dimensionless';
595              
596 1         13 return exp($self->{value});
597             }
598              
599             sub _overload_log {
600 1     1   2 my $self = shift;
601              
602 1 50       4 croak 'Invalid arguments to Physics::Unit::Scalar::_overload_log'
603             if !ref $self;
604              
605 1 50       2 carp "Warning: Arguments to log() would be without dimension, traditionally. (got '$self')"
606             unless ref($self) eq 'Physics::Unit::Dimensionless';
607              
608 1         12 return log($self->{value});
609             }
610              
611             sub _overload_int {
612 1     1   885 my $self = shift;
613              
614 1 50       4 croak 'Invalid arguments to Physics::Unit::Scalar::_overload_int'
615             if !ref $self;
616              
617 1         3 my $n = $self->new();
618              
619 1         3 $n->{value} = int($n->{value});
620              
621 1         2 return $n;
622             }
623              
624             sub _overload_abs {
625 1     1   1460 my $self = shift;
626              
627 1 50       5 croak 'Invalid arguments to Physics::Unit::Scalar::_overload_abs'
628             if !ref $self;
629              
630 1         7 my $n = $self->new();
631              
632 1         4 $n->{value} = abs($n->{value});
633              
634 1         4 return $n;
635             }
636              
637             # by overloading <=>, we will get the other comparison operators too
638             sub _overload_spaceship {
639 12     12   1823 my $self = shift;
640 12         28 my $other = GetScalar(shift);
641 12         25 my $swapped = shift;
642              
643 12 50 33     55 croak 'Invalid arguments to Physics::Unit::Scalar::_overload_spaceship'
644             if !ref $self || !ref $other;
645            
646 12 50 66     52 if ( (ref($self) eq ref($other)) ||
      66        
647             (ref($self) eq 'Physics::Unit::Dimensionless') ||
648             (ref($other) eq 'Physics::Unit::Dimensionless') ) {
649              
650 12 50       106 return $swapped ? $other->{value} <=> $self->{value} : $self->{value} <=> $other->{value};
651             }
652             else {
653             # perhaps being a bit strict here
654 0           croak 'Cannot compare a ' . ref($self) . ' to a ' . ref($other);
655             }
656             }
657              
658             use overload
659             "+" => \&_overload_add,
660             "-" => \&_overload_subtract,
661             "*" => \&_overload_times,
662             "/" => \&_overload_divide,
663             "**" => \&_overload_power,
664             "sin" => \&_overload_sin,
665             "cos" => \&_overload_cos,
666             "atan2" => \&_overload_atan2,
667             "exp" => \&_overload_exp,
668             "log" => \&_overload_log,
669             "int" => \&_overload_int,
670             "abs" => \&_overload_abs,
671             "<=>" => \&_overload_spaceship,
672             "eq" => \&_overload_eq,
673             "ne" => \&_overload_ne,
674             '""' => \&_overload_ToString,
675 4     4   311 "0+" => sub { $_[0]->value() },
676 0     0   0 "bool" => sub { $_[0]->value() },
677 2     2   1432 ;
  2         3118  
  2         30  
678              
679             1;
680              
681             __END__