File Coverage

blib/lib/App/Math/Tutor/Numbers.pm
Criterion Covered Total %
statement 236 298 79.1
branch 67 140 47.8
condition 11 31 35.4
subroutine 63 78 80.7
pod 0 4 0.0
total 377 551 68.4


line stmt bran cond sub pod time code
1             package App::Math::Tutor::Numbers;
2              
3 2     2   1166 use warnings;
  2         4  
  2         59  
4 2     2   45 use strict;
  2         14  
  2         62  
5              
6             =head1 NAME
7              
8             App::Math::Tutor::Numbers - Numbers provider for math exercises
9              
10             =cut
11              
12 2     2   515 use App::Math::Tutor::Util ();
  2         5  
  2         88  
13              
14             our $VERSION = '0.004';
15              
16             {
17             package #
18             VulFrac;
19              
20 2     2   1108 use Moo;
  2         25990  
  2         20  
21             use overload
22             '""' => "_stringify",
23 998     998   4272 'bool' => sub { !!$_[0]->num },
24 2         50 '0+' => "_numify",
25 2     2   3785 '<=>' => "_num_compare";
  2         1013  
26              
27 2     2   197 use Scalar::Util qw/blessed dualvar/;
  2         4  
  2         157  
28 2     2   4781 use Math::Complex;
  2         42184  
  2         2457  
29              
30             has num => (
31             is => "ro",
32             required => 1
33             );
34              
35             has denum => (
36             is => "ro",
37             required => 1
38             );
39             has sign => (
40             is => "ro",
41             required => 1
42             );
43              
44             around BUILDARGS => sub {
45             my $orig = shift;
46             my $self = shift;
47             my $params = $self->$orig(@_) or return;
48             defined $params->{sign} or $params->{sign} = 1;
49             $params->{num} < 0
50             and $params->{num} = blessed $params->{num} ? $params->{num}->_abs : abs( $params->{num} )
51             and $params->{sign} *= -1;
52             $params->{denum} < 0
53             and $params->{denum} =
54             blessed $params->{denum} ? $params->{denum}->_abs : abs( $params->{denum} )
55             and $params->{sign} *= -1;
56             $params->{sign} = $params->{sign} < 0 ? dualvar( -1, "-" ) : dualvar( 1, "" );
57             $params;
58             };
59              
60             sub _stringify
61             {
62 801 50   801   2052 $_[0]->num or return $_[0]->num;
63 801 50       2481 $_[0]->denum == 1 and return $_[0]->num;
64 801 100 66     8406 $_[1]
65             and $_[0]->num > $_[0]->denum
66             and return
67             sprintf( '\normalsize{%d} \frac{%d}{%d}',
68             int( $_[0]->num / $_[0]->denum ),
69             $_[0]->num % $_[0]->denum,
70             $_[0]->denum );
71              
72 761         1181 my ( $lb, $rb ) = ( "", "" );
73 761 50 33     1983 $_[0]->sign < 0
      66        
74             and ( blessed $_[0]->num or blessed $_[0]->denum )
75             and ( $lb, $rb ) = ( "\\left(", "\\right)" );
76 761         5225 return sprintf( "%s\\frac{%s}{%s}", $_[0]->sign, $_[0]->num, $_[0]->denum );
77             }
78              
79             sub _numify
80             {
81 909     909   2391 my ( $s, $n, $d ) = ( $_[0]->sign, $_[0]->num, $_[0]->denum );
82 909 100       2001 blessed $n and $n = $n->_numify;
83 909 50       1703 blessed $d and $d = $d->_numify;
84 909         4946 return $s * $n / $d;
85             }
86              
87             sub _num_compare
88             {
89 417     417   621 my ( $self, $other, $swapped ) = @_;
90 417 100       1060 $swapped and return $other <=> $self->_numify;
91              
92 53 100       173 blessed $other or return $self->_numify <=> $other;
93 45         110 return $self->_numify <=> $other->_numify;
94             }
95              
96             sub _euklid
97             {
98 290     290   354 my ( $a, $b ) = @_;
99 290         359 my $h;
100 290         535 while ( $b != 0 ) { $h = $a % $b; $a = $b; $b = $h; }
  1210         2843  
  1210         1089  
  1210         2197  
101 290         521 return $a;
102             }
103              
104             sub _gcd
105             {
106 196     196   1086 my ( $a, $b ) = ( $_[0]->num, $_[0]->denum );
107 196 100       473 my $gcd = $a > $b ? _euklid( $a, $b ) : _euklid( $b, $a );
108 196         948 return $gcd;
109             }
110              
111             sub _reciprocal
112             {
113 15     15   382 return ref( $_[0] )->new(
114             num => $_[0]->denum,
115             denum => $_[0]->num,
116             sign => $_[0]->sign
117             );
118             }
119              
120             sub _neg
121             {
122 0     0   0 my $s = $_[0]->sign;
123 0         0 $s *= -1;
124 0 0       0 $s = $s < 0 ? dualvar( -1, "-" ) : dualvar( 1, "" );
125 0         0 return ref( $_[0] )->new(
126             num => $_[0]->num,
127             denum => $_[0]->denum,
128             sign => $s
129             );
130             }
131              
132             sub _abs
133             {
134 2     2   48 return ref( $_[0] )->new(
135             num => $_[0]->num,
136             denum => $_[0]->denum,
137             sign => dualvar( 1, "" )
138             );
139             }
140              
141             sub _reduce
142             {
143 94     94   699 my ( $a, $b ) = ( $_[0]->num, $_[0]->denum );
144 94 100       219 my $gcd = $a > $b ? _euklid( $a, $b ) : _euklid( $b, $a );
145             return
146 94         2207 VulFrac->new(
147             num => $_[0]->num / $gcd,
148             denum => $_[0]->denum / $gcd,
149             sign => $_[0]->sign
150             );
151             }
152              
153             sub _build_from_decimal
154             {
155 0     0   0 my ( $c, $n ) = @_;
156 0         0 my $d = 1;
157 0         0 while ( $n != int($n) )
158             {
159 0         0 $n *= 10;
160 0         0 $d *= 10;
161             }
162             return
163 0         0 $c->new(
164             num => $n,
165             denum => $d
166             )->_reduce;
167             }
168             }
169              
170             {
171             package #
172             NatNum;
173              
174 2     2   23 use Moo;
  2         4  
  2         19  
175             use overload
176             '""' => "_stringify",
177             '0+' => "_numify",
178 52     52   164 'bool' => sub { $_[0]->value != 0 },
179 2     2   758 '<=>' => "_num_compare";
  2         5  
  2         25  
180              
181 2     2   190 use Scalar::Util qw/blessed/;
  2         2  
  2         100  
182 2     2   9 use Math::Complex;
  2         5  
  2         674  
183              
184             has value => (
185             is => "ro",
186             required => 1
187             );
188              
189 124     124   389 sub _stringify { "" . $_[0]->value }
190 259     259   1465 sub _numify { $_[0]->value }
191              
192             sub _num_compare
193             {
194 45     45   75 my ( $self, $other, $swapped ) = @_;
195 45 50       77 $swapped and return $other <=> $self->_numify;
196              
197 45 50       150 blessed $other or return $self->_numify <=> $other;
198 45         91 return $self->_numify <=> $other->_numify;
199             }
200              
201 0     0 0 0 sub sign { return $_[0]->value <=> 0 }
202 2     2   40 sub _abs { return NatNum->new( value => abs( $_[0]->value ) ) }
203             }
204              
205             {
206             package #
207             PolyTerm;
208              
209 2     2   9 use Moo;
  2         3  
  2         54  
210             use overload
211             '""' => "_stringify",
212 2     2   491 'bool' => sub { $_[0]->factor != 0 };
  2     0   3  
  2         12  
  0         0  
213              
214 2     2   112 use Scalar::Util qw/blessed/;
  2         3  
  2         87  
215 2     2   10 use Math::Complex;
  2         3  
  2         896  
216              
217             has factor => (
218             is => "ro",
219             default => sub { 1 },
220             );
221             has exponent => (
222             is => "ro",
223             required => 1
224             );
225              
226             sub _stringify
227             {
228 0     0   0 my ($self) = @_;
229 0         0 my ( $fact, $exp ) = ( $self->factor, $self->exponent );
230 0 0       0 $fact or return "0";
231 0 0       0 0 == $exp and return "$fact";
232 0 0 0     0 1 == $exp and 1 != $fact and return "{$fact}x";
233 0 0       0 1 == $exp and return "x";
234 0 0       0 1 == $fact and return "x^{$exp}";
235 0         0 return sprintf( "{%s}x^{%s}", $fact, $exp );
236             }
237              
238             sub _abs
239             {
240 0     0   0 my ( $fact, $exp ) = ( $_[0]->factor, $_[0]->exponent );
241 0 0       0 $fact = blessed $fact ? $fact->_abs() : abs($fact);
242             return
243 0         0 PolyTerm->new( factor => $fact,
244             exponent => $exp );
245             }
246              
247 0     0 0 0 sub sign { return $_[0]->factor <=> 0 }
248             }
249              
250             {
251             package #
252             PolyNum;
253              
254 2     2   9 use Moo;
  2         3  
  2         8  
255             use overload
256 2         18 '""' => "_stringify",
257             '0+' => "_numify",
258             'bool' => "_filled", # XXX prodcat(values->as_bool)
259 2     2   492 '<=>' => "_num_compare";
  2         3  
260              
261 2     2   151 use Scalar::Util qw/blessed/;
  2         3  
  2         111  
262 2     2   9 use Math::Complex;
  2         8  
  2         1227  
263             App::Math::Tutor::Util->import(qw(sumcat_terms));
264              
265             has values => (
266             is => "ro",
267             required => 1
268             );
269             has operator => (
270             is => 'ro',
271             required => 1,
272             );
273              
274 166     166   2617 sub _stringify { sumcat_terms( $_[0]->operator, @{ $_[0]->values } ); }
  166         731  
275              
276             sub _numify
277             {
278 66     66   149 my ( $op, @terms ) = ( $_[0]->operator, @{ $_[0]->values } );
  66         276  
279 66         90 my $rc = 0;
280              
281 66         235 foreach my $i ( 0 .. $#terms )
282             {
283 132 100       261 if ( $i == 0 )
284             {
285 66 100       279 $rc = blessed $terms[$i] ? $terms[$i]->_numify : $terms[$i];
286 66         95 next;
287             }
288              
289 66 50       196 $op eq "+" and $rc += blessed $terms[$i] ? $terms[$i]->_numify : $terms[$i];
    100          
290 66 100       288 $op eq "-" and $rc -= blessed $terms[$i] ? $terms[$i]->_numify : $terms[$i];
    100          
291             }
292              
293 66         1092 return $rc;
294             }
295              
296             sub _num_compare
297             {
298 36     36   131 my ( $self, $other, $swapped ) = @_;
299 36 100       116 $swapped and return $other <=> $self->_numify;
300              
301 34 50       147 blessed $other or return $self->_numify <=> $other;
302 0         0 return $self->_numify <=> $other->_numify;
303             }
304              
305             sub _filled
306             {
307 90   50 90   209 $_ and return 1 foreach ( @{ $_[0]->values } );
  90         453  
308 0         0 return;
309             }
310              
311 0     0 0 0 sub sign { $_[0]->values->[0]->sign }
312              
313             sub _abs
314             {
315 2     2   3 my ( $first, @ov ) = @{ $_[0]->values };
  2         7  
316 2 50       15 return ref( $_[0] )->new( operator => $_[0]->operator,
317             values => [ blessed $first ? $first->_abs : abs($first), @ov ]
318             );
319             }
320             }
321              
322             {
323             package #
324             ProdNum;
325              
326 2     2   10 use Moo;
  2         8  
  2         8  
327             use overload
328 2         8 '""' => "_stringify",
329             '0+' => "_numify",
330             'bool' => "_filled", # XXX prodcat(values->as_bool)
331 2     2   482 '<=>' => "_num_compare";
  2         4  
332              
333 2     2   156 use Scalar::Util qw/blessed/;
  2         2  
  2         83  
334 2     2   9 use Math::Complex;
  2         3  
  2         1362  
335             App::Math::Tutor::Util->import(qw(prodcat_terms));
336              
337             has values => (
338             is => "ro",
339             required => 1
340             );
341             has operator => (
342             is => 'ro',
343             required => 1,
344             );
345              
346 269     269   1158 sub _stringify { prodcat_terms( $_[0]->operator, @{ $_[0]->values } ); }
  269         888  
347              
348             sub _numify
349             {
350 450     450   1054 my ( $op, @terms ) = ( $_[0]->operator, @{ $_[0]->values } );
  450         1340  
351 450         541 my $rc = 0;
352              
353 450         910 foreach my $i ( 0 .. $#terms )
354             {
355 900 50       1990 my $t = blessed $terms[$i] ? $terms[$i]->_numify : $terms[$i];
356 900 100       2042 if ( $i == 0 )
357             {
358 450         547 $rc = $t;
359 450         700 next;
360             }
361              
362 450 50       1014 $op eq "*" and $rc *= $t;
363 450 50       1997 $op eq "/" and $rc /= $t;
364             }
365              
366 450         4389 return $rc;
367             }
368              
369             sub _num_compare
370             {
371 270     270   392 my ( $self, $other, $swapped ) = @_;
372 270 50       557 $swapped and return $other <=> $self->_numify;
373              
374 270 50       851 blessed $other or return $self->_numify <=> $other;
375 0         0 return $self->_numify <=> $other->_numify;
376             }
377              
378             sub _filled
379             {
380 300   50 300   322 $_ or return 0 foreach ( @{ $_[0]->values } );
  300         1410  
381 300         1081 return 1;
382             }
383              
384             sub sign
385             {
386 0 0   0 0 0 @{ $_[0]->values } or return 0;
  0         0  
387 0         0 my $sign = 1;
388 0         0 foreach my $term ( @{ $_[0]->values } )
  0         0  
389             {
390 0 0       0 my $s = blessed $term ? $term->sign : $term <=> 0;
391 0         0 $sign *= $s;
392             }
393 0         0 return $sign;
394             }
395              
396             sub _abs
397             {
398 0     0   0 my @v;
399 0         0 foreach my $term ( @{ $_[0]->values } )
  0         0  
400             {
401 0 0       0 my $x = blessed $term ? $term->_abs : abs($term);
402 0         0 push @v, $x;
403             }
404 0         0 return ref( $_[0] )->new( operator => $_[0]->operator,
405             values => [@v] );
406             }
407             }
408              
409             {
410             package #
411             Power;
412              
413 2     2   10 use Moo;
  2         3  
  2         7  
414             use overload
415             '""' => "_stringify",
416             '0+' => "_numify",
417 4     4   19 'bool' => sub { !!$_[0]->basis }, # 0 ** 7 == 0
418 2     2   569 '<=>' => "_num_compare";
  2         1536  
  2         28  
419              
420 2     2   187 use Scalar::Util qw/blessed dualvar/;
  2         3  
  2         109  
421 2     2   9 use Math::Complex;
  2         3  
  2         2102  
422              
423             has basis => (
424             is => "ro",
425             required => 1
426             );
427              
428             has exponent => (
429             is => "ro",
430             required => 1
431             );
432              
433             has mode => (
434             is => "rw",
435             default => sub { 0 },
436             );
437              
438             has factor => (
439             is => "ro",
440             default => sub { 1 },
441             );
442              
443             has sign => (
444             is => "lazy",
445             );
446              
447             sub _stringify
448             {
449 2     2   18 my ( $b, $e, $f, $m ) = ( $_[0]->basis, $_[0]->exponent, $_[0]->factor, $_[0]->mode );
450 2 50       3 $b or return "0";
451 2 50       7 defined $f or $f = 1;
452 2 50       5 $f or return;
453 2 50       4 $e == 1 and return $b;
454 2 50       8 blessed $e or $e = VulFrac->_build_from_decimal($e);
455 2         3 my $bn = 1;
456 2         3 eval { $bn = ( 1 <=> $b ); };
  2         5  
457 2         4 my $x;
458 2 50 33     9 $m
    0 33        
    50          
    50          
459             and ( $e <=> int($e) ) != 0
460             and 0 != $bn
461             and $x = sprintf(
462             "\\sqrt%s{%s}",
463             $e->denum != 2 ? sprintf( "[%s]", $e->denum ) : "",
464             $e->num != 1
465             ? sprintf( "{%s}^{%s}",
466             blessed $b ? "\\left($b\{}\\right)" : $b, $e->num )
467             : $b
468             );
469 2 0 33     13 defined $x
    50          
470             or $x = sprintf( "{%s}^{%s}", blessed $b ? "\\left($b\{}\\right)" : $b, $e )
471             if 0 != $bn;
472 2 50       6 defined $x or $x = "";
473 2 0 0     15 1 != $f
    50          
474             and $x = sprintf( "%s%s", $f, ( $x and $x !~ m/^\\/ ) ? "\\left($x\{}\\right)" : "$x" );
475 2 50       5 $x or $x = "$b";
476 2         10 return $x;
477             }
478              
479             sub _numify
480             {
481 0     0   0 my ( $b, $e, $f ) = ( $_[0]->basis, $_[0]->exponent, $_[0]->factor );
482 0 0       0 defined $f or $f = 1;
483 0 0       0 blessed $e or $e = VulFrac->_build_from_decimal($e);
484 0         0 my ( $en, $ed ) = ( $e->num, $e->denum );
485 0 0       0 blessed $en and $en = $en->_numify;
486 0 0       0 blessed $ed and $ed = $ed->_numify;
487 0 0       0 blessed $b and $b = $b->_numify;
488 0 0       0 blessed $f and $f = $f->_numify;
489 0         0 return $f * root( $b**$en, $ed, 0 );
490             }
491              
492             sub _num_compare
493             {
494 0     0   0 my ( $self, $other, $swapped ) = @_;
495 0 0       0 $swapped and return $other <=> $self->_numify;
496              
497 0 0       0 blessed $other or return $self->_numify <=> $other;
498 0         0 return $self->_numify <=> $other->_numify;
499             }
500              
501             sub _reduce
502             {
503 0     0   0 die "mising";
504             }
505              
506             sub _build_sign
507             {
508             #my ( $b, $e ) = ( $_[0]->basis, $_[0]->exponent );
509             #blessed $b and $b->sign < 0 and return dualvar( -1, "-" );
510             #$b < 0 and return dualvar( -1, "-" ) unless blessed $b;
511             # XXX check how to deal with even exponent
512 2     2   433 my ($f) = ( $_[0]->factor );
513 2 50 33     13 defined $f and $f < 0 and return dualvar( -1, "-" );
514 2         11 return dualvar( 1, "" );
515             }
516              
517             sub _abs
518             {
519 0     0   0 my ( $b, $e, $f, $m ) = ( $_[0]->basis, $_[0]->exponent, $_[0]->factor, $_[0]->mode );
520 0 0       0 $f = blessed $f ? $f->_abs : abs($f);
521 0         0 return ref( $_[0] )->new(
522             basis => $b,
523             exponent => $e,
524             factor => $f,
525             mode => $m
526             );
527             }
528             }
529              
530             {
531             package #
532             RomanNum;
533              
534 2     2   12 use Moo;
  2         4  
  2         9  
535              
536             extends "NatNum";
537              
538 2     2   655 use Carp qw/confess/;
  2         4  
  2         1065  
539              
540             around BUILDARGS => sub {
541             my $next = shift;
542             my $class = shift;
543             my $params = $class->$next(@_);
544             defined $params->{value}
545             and $params->{value} < 1
546             and confess( "Roman numerals starts at I - " . $params->{value} . " is to low" );
547             defined $params->{value}
548             and $params->{value} > 3888
549             and
550             confess( "Roman numerals ends at MMMDCCCLXXXVIII - " . $params->{value} . " is to big" );
551             return $params;
552             };
553              
554             my %sizes = (
555             M => 1000,
556             CM => 900,
557             D => 500,
558             CD => 400,
559             C => 100,
560             XC => 90,
561             L => 50,
562             XL => 40,
563             X => 10,
564             IX => 9,
565             V => 5,
566             IV => 4,
567             I => 1,
568             );
569              
570             sub _stringify
571             {
572 195     195   239 my $self = $_[0];
573 195         312 my $value = $self->value;
574 195         200 my $str = "";
575 195         645 my @order = sort { $sizes{$b} <=> $sizes{$a} } keys %sizes;
  6825         7684  
576 195         389 foreach my $sym (@order)
577             {
578 2535         4549 while ( $value >= $sizes{$sym} )
579             {
580 697         627 $str .= $sym;
581 697         1523 $value -= $sizes{$sym};
582             }
583             }
584 195         875 return $str;
585             }
586             }
587              
588             {
589             package #
590             Unit;
591              
592 2     2   12 use Moo;
  2         3  
  2         28  
593             use overload
594 2         12 '""' => "_stringify",
595             '0+' => "_numify",
596             'bool' => "_filled",
597 2     2   567 '<=>' => "_num_compare";
  2         4  
598 2     2   190 use Scalar::Util qw/blessed/;
  2         5  
  2         94  
599 2     2   10 use Math::Complex;
  2         9  
  2         1774  
600              
601             has type => (
602             is => "ro",
603             required => 1
604             );
605             has begin => (
606             is => "ro",
607             required => 1
608             );
609             has end => (
610             is => "ro",
611             required => 1
612             );
613             has parts => (
614             is => "ro",
615             required => 1
616             );
617              
618             sub _stringify
619             {
620 420     420   448 my @parts = @{ $_[0]->parts };
  420         1045  
621 420         442 my @res;
622 420         1010 for my $i ( $_[0]->begin .. $_[0]->end )
623             {
624 913         1054 my $num = shift @parts;
625 913 100       1497 $num or next;
626 909         1908 my $un = $_[0]->type->{spectrum}->[$i]->{unit};
627 909         1344 $un = "\\text{$un }";
628 909         2346 push( @res, "$num $un" );
629             }
630 420         4348 join( " ", @res );
631             #join(" ", @{ $_[0]->parts } );
632             }
633              
634             sub _numify
635             {
636 140     140   154 my @parts = @{ $_[0]->parts };
  140         348  
637 140         260 my $base = $_[0]->type->{base};
638 140         212 my $spectrum = $_[0]->type->{spectrum};
639 140         152 my $res = 0;
640 140         440 for my $i ( $_[0]->begin .. $_[0]->end )
641             {
642 310         321 my $num = shift @parts;
643 310 100       559 $num or next;
644 309         511 my $factor = $spectrum->[$i]->{factor};
645 309 100       786 $res = $i <= $base ? $res + $num * $factor : $res + $num / $factor;
646             }
647              
648 140 100       334 if ( defined $_[1] )
649             {
650 78         120 my $factor = $spectrum->[ $_[1] ]->{factor};
651 78 100       167 $res = $_[1] <= $base ? $res / $factor : $res * $factor;
652             }
653              
654 140         1212 $res;
655             }
656              
657             sub _filled
658             {
659 0     0   0 grep { $_ } @{ $_[0]->parts };
  0         0  
  0         0  
660             }
661              
662             sub _num_compare
663             {
664 45     45   71 my ( $self, $other, $swapped ) = @_;
665 45 50       89 $swapped and return $other <=> $self->_numify;
666              
667 45 50       156 blessed $other or return $self->_numify <=> $other;
668 45         51 my $rc;
669 45 100       171 0 != ( $rc = $other->begin <=> $self->begin )
670             and return $rc; # $self->begin < $other->begin => $self > $other
671 31         65 return $self->_numify <=> $other->_numify;
672             }
673             }
674              
675             =head1 LICENSE AND COPYRIGHT
676              
677             Copyright 2010-2014 Jens Rehsack.
678              
679             This program is free software; you can redistribute it and/or modify it
680             under the terms of either: the GNU General Public License as published
681             by the Free Software Foundation; or the Artistic License.
682              
683             See http://dev.perl.org/licenses/ for more information.
684              
685             =cut
686              
687             1;