File Coverage

blib/lib/Math/VarRate.pm
Criterion Covered Total %
statement 71 71 100.0
branch 24 24 100.0
condition 4 4 100.0
subroutine 10 10 100.0
pod 4 4 100.0
total 113 113 100.0


line stmt bran cond sub pod time code
1 3     3   69913 use strict;
  3         7  
  3         113  
2 3     3   18 use warnings;
  3         5  
  3         176  
3             package Math::VarRate;
4             {
5             $Math::VarRate::VERSION = '0.100000';
6             }
7             # ABSTRACT: deal with linear, variable rates of increase
8              
9 3     3   17 use Carp ();
  3         5  
  3         42  
10 3     3   24 use Scalar::Util ();
  3         4  
  3         2784  
11              
12              
13             sub new {
14 15     15 1 3156 my ($class, $arg) = @_;
15              
16 15   100     55 my $changes = $arg->{rate_changes} || {};
17 15   100     112 my $self = bless {
18             rate_changes => $changes,
19             starting_value => $arg->{starting_value} || 0,
20             } => $class;
21              
22 15         183 $self->_sanity_check_rate_changes;
23 11         37 $self->_precompute_offsets;
24              
25 11         63 return $self;
26             }
27              
28             sub _sanity_check_rate_changes {
29 15     15   24 my ($self) = @_;
30 15         41 my $rc = $self->{rate_changes};
31              
32 15         100 my %check = (
33             rates => [ values %$rc ],
34             offsets => [ keys %$rc ],
35             );
36              
37 15         71 while (my ($k, $v) = each %check) {
38 44         473 Carp::confess("non-numeric $k are not allowed")
39 28 100       49 if grep { ! Scalar::Util::looks_like_number("$_") } @$v;
40 26 100       56 Carp::confess("negative $k are not allowed") if grep { $_ < 0 } @$v;
  42         516  
41             }
42             }
43              
44              
45 40 100   40 1 3353 sub starting_value { $_[0]->{starting_value} || 0 }
46              
47              
48             sub offset_for {
49 22     22 1 540 my ($self, $value) = @_;
50              
51 22 100       217 Carp::croak("illegal value: non-numeric")
52             unless Scalar::Util::looks_like_number("$value");
53              
54 21 100       170 Carp::croak("illegal value: negative") unless $value >= 0;
55              
56 20         36 $value += 0;
57              
58 20 100       44 return 0 if $value == $self->starting_value;
59              
60 16         26 my $ko = $self->{known_offsets};
61 16         44 my ($offset) = sort { $b <=> $a } grep { $ko->{ $_ } < $value } keys %$ko;
  16         644  
  35         104  
62              
63 16 100       46 return unless defined $offset;
64              
65 14         29 my $rate = $self->{rate_changes}{ $offset };
66              
67             # If we stopped for good, we can never reach the target. -- rjbs, 2009-05-11
68 14 100       38 return undef if $rate == 0;
69              
70 12         21 my $to_go = $value - $ko->{ $offset };
71 12         22 my $dur = $to_go / $rate;
72              
73 12         73 return $offset + $dur;
74             }
75              
76              
77             sub value_at {
78 32     32 1 10109 my ($self, $offset) = @_;
79              
80 32 100       245 Carp::croak("illegal offset: non-numeric")
81             unless Scalar::Util::looks_like_number("$offset");
82              
83 31 100       277 Carp::croak("illegal offset: negative") unless $offset >= 0;
84              
85 30         36 $offset += 0;
86              
87 30         49 my $known_offsets = $self->{known_offsets};
88              
89 30 100       101 return $known_offsets->{ $offset } if exists $known_offsets->{ $offset };
90              
91 22         60 my ($max) = sort { $b <=> $a } grep { $_ < $offset } keys %$known_offsets;
  12         25  
  44         116  
92              
93 22 100       62 return $self->starting_value unless defined $max;
94              
95 16         28 my $start = $known_offsets->{ $max };
96 16         26 my $rate = $self->{rate_changes}{ $max };
97 16         22 my $dur = $offset - $max;
98              
99 16         78 return $start + $rate * $dur;
100             }
101              
102             sub _precompute_offsets {
103 11     11   17 my ($self) = @_;
104              
105 11         26 my $value = $self->starting_value;
106 11         18 my $v_at_o = {};
107 11         15 my %changes = %{ $self->{rate_changes} };
  11         39  
108 11         18 my $prev = 0;
109 11         18 my $rate = 0;
110              
111 11         47 for my $offset (sort { $a <=> $b } keys %changes) {
  13         29  
112 19         24 my $duration = $offset - $prev;
113              
114 19         28 $value += $duration * $rate;
115 19         29 $v_at_o->{ $offset } = $value;
116              
117 19         25 $rate = $changes{ $offset };
118 19         36 $prev = $offset;
119             }
120              
121 11         38 $self->{known_offsets} = $v_at_o;
122             }
123              
124             1;
125              
126             __END__