File Coverage

blib/lib/Math/Fleximal.pm
Criterion Covered Total %
statement 9 242 3.7
branch 0 52 0.0
condition 0 19 0.0
subroutine 3 33 9.0
pod 15 30 50.0
total 27 376 7.1


line stmt bran cond sub pod time code
1             package Math::Fleximal;
2             $VERSION = 0.06;
3 1     1   766 use Carp;
  1         2  
  1         95  
4 1     1   995 use integer;
  1         11  
  1         5  
5 1     1   28 use strict;
  1         2  
  1         3057  
6              
7             sub abs {
8 0     0 0   my $self = (shift)->dup();
9 0           $self->{sign} = 1;
10 0           return $self;
11             }
12              
13             # Only do with positive result! (Else normalize bombs.)
14             sub abs_sub_from {
15 0     0 0   my $values = (shift)->{values};
16 0           my $decs = (shift)->{values};
17            
18 0           foreach my $i (0..$#$decs) {
19 0           $values->[$i] -= $decs->[$i];
20             }
21             }
22              
23             sub abs_add_to {
24 0     0 0   my $values = (shift)->{values};
25 0           my $incs = (shift)->{values};
26              
27 0           foreach my $i (0..$#$incs) {
28 0           $values->[$i] += $incs->[$i];
29             }
30             }
31              
32             sub add {
33 0     0 1   my $self = shift;
34 0           my $sum = $self->dup();
35 0           foreach (@_) {
36 0           $sum = $sum->plus($_);
37             }
38 0           return $sum;
39             }
40              
41             sub array2hash {
42 0     0 0   my %pos;
43 0           $pos{$_[$_]} = $_ foreach 0..$#_;
44 0 0         return wantarray ? %pos : \%pos;
45             }
46              
47             sub base_10 {
48 0     0 1   my $self = shift;
49 0           my $proto = __PACKAGE__->new(0);
50 0           return $proto->dup($self)->to_str();
51             }
52              
53             sub change_flex {
54 0     0 1   my $self = shift;
55 0           my $new_flex = shift;
56 0           my $proto = __PACKAGE__->new($new_flex->[0], $new_flex);
57 0           $proto->dup($self);
58             }
59              
60             sub cmp {
61 0     0 1   my $self = shift;
62 0           my $other = $self->dup(shift);
63 0 0         if ($self->{sign} != $other->{sign}) {
64 0           return $self->{sign};
65             }
66             else {
67             return (
68 0           cmp_vec($self->{values}, $other->{values})
69             * $self->{sign}
70             );
71             }
72             }
73              
74             sub cmp_vec {
75 0     0 0   my $first = shift;
76 0           my $second = shift;
77            
78 0           my $cmp = @$first <=> @$second;
79 0           my $i = @$first;
80            
81 0   0       while ($i and not $cmp) {
82 0           $i--;
83 0           $cmp = $first->[$i] <=> $second->[$i];
84             }
85              
86 0           return $cmp;
87             }
88              
89             sub div {
90 0     0 1   my $self = shift;
91 0           my @remain;
92 0           foreach (@_) {
93 0           ($self, my $rem) = $self->divide($_);
94 0           push @remain, $rem;
95             }
96 0 0         wantarray ? ($self, @remain) : $self;
97             }
98              
99             sub divide {
100 0     0 0   my $self = shift;
101 0           my $denom = $self->dup(shift);
102              
103 0 0         unless (@{$denom->{values}}) {
  0            
104 0           croak("Cannot divide by zero!");
105             }
106              
107             # Base 2 is convenient...
108 0           my @doubles = $denom->abs();
109 0           my $remain = $self->abs();
110 0           while ($doubles[-1]->cmp($remain) < 0) {
111 0           push @doubles, $doubles[-1]->plus($doubles[-1]);
112             }
113            
114 0           my $ans = '';
115 0           while (@doubles) {
116 0           my $double = pop (@doubles);
117 0 0         if ($double->cmp($remain) <= 0) {
118 0           $ans .= "1";
119 0           $remain = $remain->minus($double);
120             }
121             else {
122 0           $ans .= "0";
123             }
124             }
125            
126             # Convert answer
127 0           $ans = __PACKAGE__->new($ans, [0, 1]);
128             # Handle differing sign without exact division...
129 0 0 0       if (
130             $remain->cmp(0) and
131             $self->{sign} == -1
132             ) {
133 0           $remain = $denom->abs->minus($remain);
134 0           $ans = $ans->plus(1);
135             }
136 0           $ans->{sign} = $self->{sign} * $denom->{sign};
137 0           return ($self->dup($ans), $remain)
138             }
139              
140             sub dup {
141 0     0 1   my $self = shift;
142 0           my $copy = bless +{ %$self }, ref($self);
143 0 0         my $val = @_ ? shift : $self;
144 0           return $copy->set_value($val);
145             }
146              
147             sub gcd {
148 0     0 1   my $self = shift;
149 0           my $zero = $self->zero();
150              
151 0           foreach (@_) {
152 0           my $other = $self->dup($_);
153 0           while ($other->cmp($zero)) {
154 0           ($self, $other) = ($other, $self->mod($other));
155             }
156             }
157              
158 0           return $self;
159             }
160              
161             sub make_mybase {
162 0     0 0   my $self = shift;
163 0           return map $self->dup($_), @_;
164             }
165              
166             sub minus {
167 0     0 0   my $self = shift;
168 0           my $other = $self->dup(shift);
169 0           $other->{sign} = - $other->{sign};
170 0           return $self->add($other);
171             }
172              
173             sub mod {
174 0     0 1   my @remain = div(@_);
175 0           shift @remain;
176 0 0         wantarray ? @remain : $remain[-1];
177             }
178              
179             sub mul {
180 0     0 1   my $prod = (shift)->dup();
181 0           foreach (@_) {
182 0           $prod = $prod->times($_);
183             }
184 0           return $prod;
185             }
186              
187             sub new {
188 0     0 1   my %default = (
189             '+' => '+',
190             '-' => '-',
191             'show_+' => 0,
192             strip => qr/[\s\.,_]/,
193             );
194              
195 0           my $self = bless {sign => 1, value => [], %default}, shift;
196 0           my $value = shift;
197 0   0       my $flex = $self->{flex} = shift || [0..9];
198 0   0       my $args = shift || {};
199            
200 0           $self->{base} = @$flex;
201 0           $self->{match_fleck} = ret_match_any(@$flex);
202 0           $self->{fleck_lookup} = array2hash(@$flex);
203            
204 0           foreach my $key (keys %$args) {
205 0 0         if (exists $default{$key}) {
206 0           $self->{$key} = $args->{$key};
207             }
208             else {
209 0           my $valid = join ", ", map "'$_'", sort keys %default;
210 0           croak("Unknown parameter '$valid'. Allowed: ($valid)");
211             }
212             }
213            
214 0           return $self->set_value($value);
215             }
216              
217             # values assumed to work out nonnegative
218             sub normalize {
219 0     0 0   my $self = shift;
220 0           my $base = $self->{base};
221 0           my $values = $self->{values};
222            
223             # We need to have a valid base rep
224 0           my $i = 0;
225 0           my $carry = 0;
226 0   0       while ($carry or $i < @$values) {
227 0 0         $carry += $values->[$i] if ($values->[$i]);
228 0           while ($carry < 0) {
229 0           $carry += $base;
230 0           $values->[$i + 1]--;
231             }
232 0           $values->[$i] = $carry % $base;
233            
234 0           $carry /= $base;
235 0           ++$i;
236             }
237            
238             # Deal with leading 0's and 0...
239 0   0       pop(@$values) while @$values and not $values->[-1];
240 0 0         $self->{sign} = 1 if not @$values;
241 0           return $self;
242             }
243              
244             sub one {
245 0     0 1   my $num = (shift)->dup();
246 0           $num->{sign} = 1;
247 0           $num->{values} = [1];
248 0           return $num;
249             }
250              
251             sub parse_rep {
252 0     0 0   my $self = shift;
253 0           my $str = shift;
254            
255 0           $str =~ s/$self->{strip}//g;
256 0           my $sign = 1;
257 0 0         if ($str =~ /^\Q$self->{"-"}\E/g) {
258 0           $sign = -1;
259             }
260             else {
261 0           $str =~ /^\Q$self->{"+"}\E/g;
262             }
263            
264 0           my @values;
265 0           my $match_fleck = $self->{match_fleck};
266 0           my $fleck_lookup = $self->{fleck_lookup};
267 0           my $last_pos = pos($str);
268            
269 0           while ($str =~ /\G($match_fleck)/g) {
270 0           push @values, $fleck_lookup->{$1};
271 0           $last_pos = pos($str);
272             }
273            
274             croak(
275 0 0         "Cannot find any digits in $str.\n" .
276 0           "Current flex: (@{$self->{flex}})\n"
277             ) unless @values;
278            
279 0 0         carp("'$str' truncated in parse")
280             unless $last_pos == length($str);
281            
282 0           return ($sign, [reverse @values]);
283             }
284              
285             sub plus {
286 0     0 0   my $self = shift;
287 0           my $other = $self->dup(shift);
288 0           my $sum;
289 0 0         if ($self->{sign} == $other->{sign}) {
    0          
290 0           $sum = $self->dup();
291 0           abs_add_to($sum, $other);
292             }
293             elsif (0 < cmp_vec($self->{values}, $other->{values})) {
294 0           $sum = $self->dup();
295 0           $sum->abs_sub_from($other);
296             }
297             else {
298 0           $sum = $other->dup();
299 0           $sum->abs_sub_from($self);
300             }
301 0           return $sum->normalize();
302             }
303              
304             sub pow {
305 0     0 0   my $cur_base = shift;
306             {
307 0           my $exp = $cur_base->dup(shift);
  0            
308 0 0         unless (1 == $exp->{sign}) {
309 0           $exp = $exp->to_str();
310 0           croak("Cannot handle negative exponent: '$exp'");
311             }
312              
313 0           my $res = $cur_base->one();
314             # Base 2 is easier
315 0           $exp = $exp->change_flex([0, 1]);
316 0           foreach my $term (@{$exp->{values}}) {
  0            
317 0 0         if ($term) {
318 0           $res = $res->times($cur_base);
319             }
320 0           $cur_base = $cur_base->times($cur_base);
321             }
322 0 0         if (@_) {
323 0           $cur_base = $res;
324 0           redo;
325             }
326             else {
327 0           return $res;
328             }
329             }
330             }
331              
332             sub ret_match_any {
333             # Hack to match longest token possible
334 0     0 0   my @toks = reverse sort @_;
335 0           my $str = join "|", map quotemeta($_), @_;
336 0           return qr/$str/;
337             }
338              
339             sub set_value {
340 0     0 1   my $self = shift;
341 0           my $value = shift;
342 0 0         if (ref($value)) {
343 0 0         if ($self->{base} == $value->{base}) {
344 0           $self->{values} = [ @{ $value->{values} } ];
  0            
345             }
346             else {
347 0           my $factor = $value->{base};
348 0           my $converted = $self->zero();
349 0           my $scale = $self->one();
350              
351 0           foreach (@{ $value->{values} }) {
  0            
352 0           $converted = $converted->plus($scale->times_const($_));
353 0           $scale = $scale->times_const($factor);
354             }
355 0           $self->{values} = $converted->{values};
356             }
357 0           $self->{sign} = $value->{sign};
358             }
359             else {
360 0           @$self{'sign', 'values'} = $self->parse_rep($value);
361 0           $self->normalize();
362             }
363 0           return $self;
364             }
365              
366             sub subtr {
367 0     0 1   my $result = (shift)->dup();
368 0           $result = $result->minus($_) foreach @_;
369 0           return $result;
370             }
371              
372             sub times {
373 0     0 0   my $self = shift;
374 0           my $other = $self->dup(shift);
375            
376 0           my $result = $self->zero();
377 0           my @leading_zeros = ();
378            
379             # Prevents possible sign bug on 0
380 0 0 0       unless (@{$self->{values}} and @{$other->{values}}) {
  0            
  0            
381 0           return $result;
382             }
383            
384 0           foreach (@{ $other->{values} }) {
  0            
385 0           my $tmp = $self->times_const($_);
386 0           unshift @{$tmp->{values}}, @leading_zeros;
  0            
387 0           $result = $result->plus($tmp);
388 0           push @leading_zeros, 0;
389             }
390            
391 0           $result->{sign} = $self->{sign} * $other->{sign};
392              
393 0           $result;
394             }
395              
396             sub times_const {
397 0     0 0   my $result = (shift)->dup();
398 0           my $const = shift;
399 0 0         if ($const < 0) {
400 0           $const *= -1;
401 0           $result->{sign} = - $result->{sign};
402             }
403 0           foreach my $term (@{$result->{values}}) {
  0            
404 0           $term *= $const;
405             }
406 0           $result->normalize();
407 0           return $result;
408             }
409            
410              
411             sub to_str {
412 0     0 1   my $self = shift;
413 0           my $flex = $self->{flex};
414 0           my @vals = @{$self->{values}};
  0            
415 0 0         push @vals, 0 unless @vals;
416 0 0         my $p = $self->{'show_+'} ? $self->{'+'} : "";
417 0 0         return join "",
418             (1 == $self->{sign} ? $p : $self->{'-'}),
419             map $flex->[$_], reverse @vals;
420             }
421              
422             sub zero {
423 0     0 1   my $num = (shift)->dup();
424 0           $num->{sign} = 1;
425 0           $num->{values} = [];
426 0           return $num;
427             }
428              
429             1;
430              
431             __END__