File Coverage

blib/lib/App/Math/Tutor/Numbers.pm
Criterion Covered Total %
statement 158 298 53.0
branch 35 140 25.0
condition 9 31 29.0
subroutine 50 78 64.1
pod 0 4 0.0
total 252 551 45.7


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