{
$Math::VarRate::VERSION
=
'0.100000'
;
}
sub
new {
my
(
$class
,
$arg
) =
@_
;
my
$changes
=
$arg
->{rate_changes} || {};
my
$self
=
bless
{
rate_changes
=>
$changes
,
starting_value
=>
$arg
->{starting_value} || 0,
} =>
$class
;
$self
->_sanity_check_rate_changes;
$self
->_precompute_offsets;
return
$self
;
}
sub
_sanity_check_rate_changes {
my
(
$self
) =
@_
;
my
$rc
=
$self
->{rate_changes};
my
%check
= (
rates
=> [
values
%$rc
],
offsets
=> [
keys
%$rc
],
);
while
(
my
(
$k
,
$v
) =
each
%check
) {
Carp::confess(
"non-numeric $k are not allowed"
)
if
grep
{ ! Scalar::Util::looks_like_number(
"$_"
) }
@$v
;
Carp::confess(
"negative $k are not allowed"
)
if
grep
{
$_
< 0 }
@$v
;
}
}
sub
starting_value {
$_
[0]->{starting_value} || 0 }
sub
offset_for {
my
(
$self
,
$value
) =
@_
;
Carp::croak(
"illegal value: non-numeric"
)
unless
Scalar::Util::looks_like_number(
"$value"
);
Carp::croak(
"illegal value: negative"
)
unless
$value
>= 0;
$value
+= 0;
return
0
if
$value
==
$self
->starting_value;
my
$ko
=
$self
->{known_offsets};
my
(
$offset
) =
sort
{
$b
<=>
$a
}
grep
{
$ko
->{
$_
} <
$value
}
keys
%$ko
;
return
unless
defined
$offset
;
my
$rate
=
$self
->{rate_changes}{
$offset
};
return
undef
if
$rate
== 0;
my
$to_go
=
$value
-
$ko
->{
$offset
};
my
$dur
=
$to_go
/
$rate
;
return
$offset
+
$dur
;
}
sub
value_at {
my
(
$self
,
$offset
) =
@_
;
Carp::croak(
"illegal offset: non-numeric"
)
unless
Scalar::Util::looks_like_number(
"$offset"
);
Carp::croak(
"illegal offset: negative"
)
unless
$offset
>= 0;
$offset
+= 0;
my
$known_offsets
=
$self
->{known_offsets};
return
$known_offsets
->{
$offset
}
if
exists
$known_offsets
->{
$offset
};
my
(
$max
) =
sort
{
$b
<=>
$a
}
grep
{
$_
<
$offset
}
keys
%$known_offsets
;
return
$self
->starting_value
unless
defined
$max
;
my
$start
=
$known_offsets
->{
$max
};
my
$rate
=
$self
->{rate_changes}{
$max
};
my
$dur
=
$offset
-
$max
;
return
$start
+
$rate
*
$dur
;
}
sub
_precompute_offsets {
my
(
$self
) =
@_
;
my
$value
=
$self
->starting_value;
my
$v_at_o
= {};
my
%changes
= %{
$self
->{rate_changes} };
my
$prev
= 0;
my
$rate
= 0;
for
my
$offset
(
sort
{
$a
<=>
$b
}
keys
%changes
) {
my
$duration
=
$offset
-
$prev
;
$value
+=
$duration
*
$rate
;
$v_at_o
->{
$offset
} =
$value
;
$rate
=
$changes
{
$offset
};
$prev
=
$offset
;
}
$self
->{known_offsets} =
$v_at_o
;
}
1;