File Coverage

blib/lib/Math/Utils.pm
Criterion Covered Total %
statement 164 176 93.1
branch 78 94 82.9
condition 26 36 72.2
subroutine 34 36 94.4
pod 22 22 100.0
total 324 364 89.0


line stmt bran cond sub pod time code
1             package Math::Utils;
2              
3 18     18   1205491 use 5.010001;
  18         216  
4 18     18   107 use strict;
  18         35  
  18         396  
5 18     18   85 use warnings;
  18         73  
  18         618  
6 18     18   112 use Carp;
  18         30  
  18         1145  
7              
8 18     18   110 use Exporter;
  18         45  
  18         12506  
9             our @ISA = qw(Exporter);
10              
11             our %EXPORT_TAGS = (
12             compare => [ qw(generate_fltcmp generate_relational) ],
13             fortran => [ qw(log10 copysign) ],
14             utility => [ qw(log10 log2 copysign flipsign
15             sign floor ceil fsum
16             gcd hcf lcm moduli) ],
17             polynomial => [ qw(pl_evaluate pl_dxevaluate pl_translate
18             pl_add pl_sub pl_div pl_mult
19             pl_derivative pl_antiderivative) ],
20             );
21              
22             our @EXPORT_OK = (
23             @{ $EXPORT_TAGS{compare} },
24             @{ $EXPORT_TAGS{utility} },
25             @{ $EXPORT_TAGS{polynomial} },
26             );
27              
28             #
29             # Add an :all tag automatically.
30             #
31             $EXPORT_TAGS{all} = [@EXPORT_OK];
32              
33             our $VERSION = '1.13';
34              
35             =head1 NAME
36              
37             Math::Utils - Useful mathematical functions not in Perl.
38              
39             =head1 SYNOPSIS
40              
41             use Math::Utils qw(:utility); # Useful functions
42              
43             #
44             # Base 10 and base 2 logarithms.
45             #
46             $scale = log10($pagewidth);
47             $bits = log2(1/$probability);
48              
49             #
50             # Two uses of sign().
51             #
52             $d = sign($z - $w);
53              
54             @ternaries = sign(@coefficients);
55              
56             #
57             # Using copysign(), $dist will be doubled negative or
58             # positive $offest, depending upon whether ($from - $to)
59             # is positive or negative.
60             #
61             my $dist = copysign(2 * $offset, $from - $to);
62              
63             #
64             # Change increment direction if goal is negative.
65             #
66             $incr = flipsign($incr, $goal);
67              
68             #
69             # floor() and ceil() functions.
70             #
71             $point = floor($goal);
72             $limit = ceil($goal);
73              
74             #
75             # gcd() and lcm() functions.
76             #
77             $divisor = gcd(@multipliers);
78             $numerator = lcm(@multipliers);
79              
80             #
81             # Safe(r) summation.
82             #
83             $tot = fsum(@inputs);
84              
85             #
86             # The remainders of n after successive divisions of b, or
87             # remainders after a set of divisions.
88             #
89             @rems = moduli($n, $b);
90              
91             or
92              
93             use Math::Utils qw(:compare); # Make comparison functions with tolerance.
94              
95             #
96             # Floating point comparison function.
97             #
98             my $fltcmp = generate_fltmcp(1.0e-7);
99              
100             if (&$fltcmp($x0, $x1) < 0)
101             {
102             add_left($data);
103             }
104             else
105             {
106             add_right($data);
107             }
108              
109             #
110             # Or we can create single-operation comparison functions.
111             #
112             # Here we are only interested in the greater than and less than
113             # comparison functions.
114             #
115             my(undef, undef,
116             $approx_gt, undef, $approx_lt) = generate_relational(1.5e-5);
117              
118             or
119              
120             use Math::Utils qw(:polynomial); # Basic polynomial ops
121              
122             #
123             # Coefficient lists run from 0th degree upward, left to right.
124             #
125             my @c1 = (1, 3, 5, 7, 11, 13, 17, 19);
126             my @c2 = (1, 3, 1, 7);
127             my @c3 = (1, -1, 1)
128              
129             my $c_ref = pl_mult(\@c1, \@c2);
130             $c_ref = pl_add($c_ref, \@c3);
131              
132             =head1 EXPORT
133              
134             All functions can be exported by name, or by using the tag that they're
135             grouped under.
136              
137             =cut
138              
139             =head2 utility tag
140              
141             Useful, general-purpose functions, including those that originated in
142             FORTRAN and were implemented in Perl in the module L,
143             by J. A. R. Williams.
144              
145             There is a name change -- copysign() was known as sign()
146             in Math::Fortran.
147              
148             =head3 log10()
149              
150             $xlog10 = log10($x);
151             @xlog10 = log10(@x);
152              
153             Return the log base ten of the argument. A list form of the function
154             is also provided.
155              
156             =cut
157              
158             sub log10
159             {
160 6     6 1 1649 my $log10 = log(10);
161 6 50       44 return wantarray? map(log($_)/$log10, @_): log($_[0])/$log10;
162             }
163              
164             =head3 log2()
165              
166             $xlog2 = log2($x);
167             @xlog2 = log2(@x);
168              
169             Return the log base two of the argument. A list form of the function
170             is also provided.
171              
172             =cut
173              
174             sub log2
175             {
176 6     6 1 1668 my $log2 = log(2);
177 6 50       27 return wantarray? map(log($_)/$log2, @_): log($_[0])/$log2;
178             }
179              
180             =head3 sign()
181              
182             $s = sign($x);
183             @valsigns = sign(@values);
184              
185             Returns -1 if the argument is negative, 0 if the argument is zero, and 1
186             if the argument is positive.
187              
188             In list form it applies the same operation to each member of the list.
189              
190             =cut
191              
192             sub sign
193             {
194 6 100   6 1 1667 return wantarray? map{($_ < 0)? -1: (($_ > 0)? 1: 0)} @_:
  10 100       34  
    100          
    100          
    100          
195             ($_[0] < 0)? -1: (($_[0] > 0)? 1: 0);
196             }
197              
198             =head3 copysign()
199              
200             $ms = copysign($m, $n);
201             $s = copysign($x);
202              
203             Take the sign of the second argument and apply it to the first. Zero
204             is considered part of the positive signs.
205              
206             copysign(-5, 0); # Returns 5.
207             copysign(-5, 7); # Returns 5.
208             copysign(-5, -7); # Returns -5.
209             copysign(5, -7); # Returns -5.
210              
211             If there is only one argument, return -1 if the argument is negative,
212             otherwise return 1. For example, copysign(1, -4) and copysign(-4) both
213             return -1.
214              
215             =cut
216              
217             sub copysign
218             {
219 6 100   6 1 1751 return ($_[1] < 0)? -abs($_[0]): abs($_[0]) if (@_ == 2);
    100          
220 3 100       12 return ($_[0] < 0)? -1: 1;
221             }
222              
223             =head3 flipsign()
224              
225             $ms = flipsign($m, $n);
226              
227             Multiply the signs of the arguments and apply it to the first. As
228             with copysign(), zero is considered part of the positive signs.
229              
230             Effectively this means change the sign of the first argument if
231             the second argument is negative.
232              
233             flipsign(-5, 0); # Returns -5.
234             flipsign(-5, 7); # Returns -5.
235             flipsign(-5, -7); # Returns 5.
236             flipsign(5, -7); # Returns -5.
237              
238             If for some reason flipsign() is called with a single argument,
239             that argument is returned unchanged.
240              
241             =cut
242              
243             sub flipsign
244             {
245 0 0 0 0 1 0 return -$_[0] if (@_ == 2 and $_[1] < 0);
246 0         0 return $_[0];
247             }
248              
249             =head3 floor()
250              
251             $b = floor($a/2);
252              
253             @ilist = floor(@numbers);
254              
255             Returns the greatest integer less than or equal to its argument.
256             A list form of the function also exists.
257              
258             floor(1.5, 1.87, 1); # Returns (1, 1, 1)
259             floor(-1.5, -1.87, -1); # Returns (-2, -2, -1)
260              
261             =cut
262              
263             sub floor
264             {
265 4 100 100 4 1 440 return wantarray? map(($_ < 0 and int($_) != $_)? int($_ - 1): int($_), @_):
    100 66        
    100          
266             ($_[0] < 0 and int($_[0]) != $_[0])? int($_[0] - 1): int($_[0]);
267             }
268              
269             =head3 ceil()
270              
271             $b = ceil($a/2);
272              
273             @ilist = ceil(@numbers);
274              
275             Returns the lowest integer greater than or equal to its argument.
276             A list form of the function also exists.
277              
278             ceil(1.5, 1.87, 1); # Returns (2, 2, 1)
279             ceil(-1.5, -1.87, -1); # Returns (-1, -1, -1)
280              
281             =cut
282              
283             sub ceil
284             {
285 4 100 100 4 1 616 return wantarray? map(($_ > 0 and int($_) != $_)? int($_ + 1): int($_), @_):
    100 66        
    100          
286             ($_[0] > 0 and int($_[0]) != $_[0])? int($_[0] + 1): int($_[0]);
287             }
288              
289             =head3 fsum()
290              
291             Return a sum of the values in the list, done in a manner to avoid rounding
292             and cancellation errors. Currently this is done via
293             L.
294              
295             =cut
296              
297             sub fsum
298             {
299 3     3 1 13 my($sum, $c) = (0, 0);
300              
301 3         8 for my $v (@_)
302             {
303 24         34 my $y = $v - $c;
304 24         43 my $t = $sum + $y;
305              
306             #
307             # If we lost low-order bits of $y (usually because
308             # $sum is much larger than $y), save them in $c
309             # for the next loop iteration.
310             #
311 24         27 $c = ($t - $sum) - $y;
312 24         33 $sum = $t;
313             }
314              
315 3         9 return $sum;
316             }
317              
318             =head3 gcd
319              
320             =head3 hcf
321              
322             Return the greatest common divisor (also known as the highest
323             common factor) of a list of integers. These are simply synomyms:
324              
325             $factor = gcd(@values);
326             $factor = hfc(@numbers);
327              
328             =cut
329              
330             sub gcd
331             {
332 18     18   9234 use integer;
  18         269  
  18         96  
333 24     24 1 1285 my($x, $y, $r);
334              
335             #
336             # It could happen. Someone might type \$x instead of $x.
337             #
338 60 50       135 my @values = map{(ref $_ eq "ARRAY")? @$_:
    100          
339 24         44 ((ref $_ eq "SCALAR")? $$_: $_)} grep {$_} @_;
  60         90  
340              
341 24 50       54 return 0 if (scalar @values == 0);
342              
343 24         46 $y = abs pop @values;
344 24         30 $x = abs pop @values;
345              
346 24         31 while (1)
347             {
348 41 100       85 ($x, $y) = ($y, $x) if ($y < $x);
349              
350 41         50 $r = $y % $x;
351 41         47 $y = $x;
352              
353 41 100       73 if ($r == 0)
354             {
355 37 100       101 return $x if (scalar @values == 0);
356 13         19 $r = abs pop @values;
357             }
358              
359 17         19 $x = $r;
360             }
361              
362 0         0 return $y;
363             }
364              
365             #
366             #sub bgcd
367             #{
368             # my($x, $y) = map(abs($_), @_);
369             #
370             # return $y if ($x == 0);
371             # return $x if ($y == 0);
372             #
373             # my $lsbx = low_set_bit($x);
374             # my $lsby = low_set_bit($y);
375             # $x >>= $lsbx;
376             # $y >>= $lsby;
377             #
378             # while ($x != $y)
379             # {
380             # ($x, $y) = ($y, $x) if ($x > $y);
381             #
382             # $y -= $x;
383             # $y >>= low_set_bit($y);
384             # }
385             # return ($x << (($lsbx > $lsby)? $lsby: $lsbx));
386             #}
387              
388             *hcf = \&gcd;
389              
390             =head3 lcm
391              
392             Return the least common multiple of a list of integers.
393              
394             $factor = lcm(@values);
395              
396             =cut
397              
398             sub lcm
399             {
400             #
401             # It could happen. Someone might type \$x instead of $x.
402             #
403 0 0   0 1 0 my @values = map{(ref $_ eq "ARRAY")? @$_:
  0 0       0  
404             ((ref $_ eq "SCALAR")? $$_: $_)} @_;
405              
406 0         0 my $x = pop @values;
407              
408 0         0 for my $m (@values)
409             {
410 0         0 $x *= $m/gcd($m, $x);
411             }
412              
413 0         0 return abs $x;
414             }
415              
416             =head3 moduli()
417              
418             Return the moduli of an integer after repeated divisions. The remainders are
419             returned in a list from left to right.
420              
421             @digits = moduli(1899, 10); # Returns (9, 9, 8, 1)
422             @rems = moduli(29, 3); # Returns (2, 0, 0, 1)
423              
424             =cut
425              
426             sub moduli
427             {
428 2     2 1 529 my($n, $b) = (abs($_[0]), abs($_[1]));
429 2         4 my @mlist;
430 18     18   6329 use integer;
  18         39  
  18         90  
431              
432 2         4 for (;;)
433             {
434 16         25 push @mlist, $n % $b;
435 16         19 $n /= $b;
436 16 100       44 return @mlist if ($n == 0);
437             }
438 0         0 return ();
439             }
440              
441             =head2 compare tag
442              
443             Create comparison functions for floating point (non-integer) numbers.
444              
445             Since exact comparisons of floating point numbers tend to be iffy,
446             the comparison functions use a tolerance chosen by you. You may
447             then use those functions from then on confident that comparisons
448             will be consistent.
449              
450             If you do not provide a tolerance, a default tolerance of 1.49012e-8
451             (approximately the square root of an Intel Pentium's
452             L)
453             will be used.
454              
455             =head3 generate_fltcmp()
456              
457             Returns a comparison function that will compare values using a tolerance
458             that you supply. The generated function will return -1 if the first
459             argument compares as less than the second, 0 if the two arguments
460             compare as equal, and 1 if the first argument compares as greater than
461             the second.
462              
463             my $fltcmp = generate_fltcmp(1.5e-7);
464              
465             my(@xpos) = grep {&$fltcmp($_, 0) == 1} @xvals;
466              
467             =cut
468              
469             my $default_tolerance = 1.49012e-8;
470              
471             sub generate_fltcmp
472             {
473 4   66 4 1 350 my $tol = $_[0] // $default_tolerance;
474              
475             return sub {
476 56     56   1043 my($x, $y) = @_;
477 56 50       244 return 0 if (abs($x - $y) <= $tol);
478 0 0       0 return -1 if ($x < $y);
479 0         0 return 1;
480             }
481 4         27 }
482              
483             =head3 generate_relational()
484              
485             Returns a list of comparison functions that will compare values using a
486             tolerance that you supply. The generated functions will be the equivalent
487             of the equal, not equal, greater than, greater than or equal, less than,
488             and less than or equal operators.
489              
490             my($eq, $ne, $gt, $ge, $lt, $le) = generate_relational(1.5e-7);
491              
492             my(@approx_5) = grep {&$eq($_, 5)} @xvals;
493              
494             Of course, if you were only interested in not equal, you could use:
495              
496             my(undef, $ne) = generate_relational(1.5e-7);
497              
498             my(@not_around5) = grep {&$ne($_, 5)} @xvals;
499              
500             =cut
501              
502             sub generate_relational
503             {
504 2   33 2 1 108 my $tol = $_[0] // $default_tolerance;
505              
506             #
507             # In order: eq, ne, gt, ge, lt, le.
508             #
509             return (
510 24 100   24   3453 sub {return (abs($_[0] - $_[1]) <= $tol)? 1: 0;}, # eq
511 12 100   12   45 sub {return (abs($_[0] - $_[1]) > $tol)? 1: 0;}, # ne
512              
513 12 100 100 12   64 sub {return ((abs($_[0] - $_[1]) > $tol) and ($_[0] > $_[1]))? 1: 0;}, # gt
514 12 100 100 12   55 sub {return ((abs($_[0] - $_[1]) <= $tol) or ($_[0] > $_[1]))? 1: 0;}, # ge
515              
516 12 100 100 12   54 sub {return ((abs($_[0] - $_[1]) > $tol) and ($_[0] < $_[1]))? 1: 0;}, # lt
517 12 100 100 12   88 sub {return ((abs($_[0] - $_[1]) <= $tol) or ($_[0] < $_[1]))? 1: 0;} # le
518 2         26 );
519             }
520              
521             =head2 polynomial tag
522              
523             Perform some polynomial operations on plain lists of coefficients.
524              
525             #
526             # The coefficient lists are presumed to go from low order to high:
527             #
528             @coefficients = (1, 2, 4, 8); # 1 + 2x + 4x**2 + 8x**3
529              
530             In all functions the coeffcient list is passed by reference to the function,
531             and the functions that return coefficients all return references to a
532             coefficient list.
533              
534             B
535             already been removed before calling these functions, and that any leading
536             zeros found in the returned lists will be handled by the caller.> This caveat
537             is particularly important to note in the case of C.
538              
539             Although these functions are convenient for simple polynomial operations,
540             for more advanced polynonial operations L is recommended.
541              
542             =head3 pl_evaluate()
543              
544             $y = pl_evaluate(\@coefficients, $x);
545             @yvalues = pl_evaluate(\@coefficients, \@xvalues);
546              
547             You can also use lists of the X values or X array references:
548              
549             @yvalues = pl_evaluate(\@coefficients, \@xvalues, \@primes, $x, @negatives);
550              
551             Returns either a y-value for a corresponding x-value, or a list of
552             y-values on the polynomial for a corresponding list of x-values,
553             using Horner's method.
554              
555             =cut
556              
557             sub pl_evaluate
558             {
559 8     8 1 4070 my @coefficients = @{$_[0]};
  8         16  
560              
561             #
562             # It could happen. Someone might type \$x instead of $x.
563             #
564 8 100       26 my @xvalues = map{(ref $_ eq "ARRAY")? @$_:
  12 100       62  
565             ((ref $_ eq "SCALAR")? $$_: $_)} @_[1 .. $#_];
566              
567             #
568             # Move the leading coefficient off the polynomial list
569             # and use it as our starting value(s).
570             #
571 8         24 my @results = (pop @coefficients) x scalar @xvalues;
572              
573 8         16 for my $c (reverse @coefficients)
574             {
575 24         1178 for my $j (0..$#xvalues)
576             {
577 84         4544 $results[$j] = $results[$j] * $xvalues[$j] + $c;
578             }
579             }
580              
581 8 50       365 return wantarray? @results: $results[0];
582             }
583              
584             =head3 pl_dxevaluate()
585              
586             ($y, $dy, $ddy) = pl_dxevaluate(\@coefficients, $x);
587              
588             Returns p(x), p'(x), and p"(x) of the polynomial for an
589             x-value, using Horner's method. Note that unlike C
590             above, the function can only use one x-value.
591              
592             If the polynomial is a linear equation, the second derivative value
593             will be zero. Similarly, if the polynomial is a simple constant,
594             the first derivative value will be zero.
595              
596             =cut
597              
598             sub pl_dxevaluate
599             {
600 12     12 1 31 my($coef_ref, $x) = @_;
601 12         28 my(@coefficients) = @$coef_ref;
602 12         21 my $n = $#coefficients;
603 12         33 my $val = pop @coefficients;
604 12         24 my $d1val = $val * $n;
605 12         16 my $d2val = 0;
606              
607             #
608             # Special case for the linear eq'n (the y = constant eq'n
609             # takes care of itself).
610             #
611 12 100       37 if ($n == 1)
    100          
612             {
613 1         2 $val = $val * $x + $coefficients[0];
614             }
615             elsif ($n >= 2)
616             {
617 10         16 my $lastn = --$n;
618 10         14 $d2val = $d1val * $n;
619              
620             #
621             # Loop through the coefficients, except for
622             # the linear and constant terms.
623             #
624 10         22 for my $c (reverse @coefficients[2..$lastn])
625             {
626 38         45 $val = $val * $x + $c;
627 38         47 $d1val = $d1val * $x + ($c *= $n--);
628 38         58 $d2val = $d2val * $x + ($c * $n);
629             }
630              
631             #
632             # Handle the last two coefficients.
633             #
634 10         16 $d1val = $d1val * $x + $coefficients[1];
635 10         17 $val = ($val * $x + $coefficients[1]) * $x + $coefficients[0];
636             }
637              
638 12         56 return ($val, $d1val, $d2val);
639             }
640              
641             =head3 pl_translate()
642              
643             $x = [8, 3, 1];
644             $y = [3, 1];
645              
646             #
647             # Translating C by C returns [26, 9, 1]
648             #
649             $z = pl_translate($x, $y);
650              
651             Returns a polynomial transformed by substituting a polynomial variable with another polynomial.
652             For example, a simple linear translation by 1 to the polynomial C
653             would be accomplished by setting x = (y - 1); resulting in C.
654              
655             $x = [4, 4, 1, 1];
656             $y = [-1, 1];
657             $z = pl_translate($x, $y); # Becomes [0, 5, -2, 1]
658              
659             =cut
660              
661             sub pl_translate
662             {
663 4     4 1 1920 my($x, $y) = @_;
664              
665 4         10 my @x_arr = @$x;
666 4         8 my @z = pop @x_arr;
667              
668 4         9 for my $c (reverse @x_arr)
669             {
670 8         9 @z = @{ pl_mult(\@z, $y) };
  8         28  
671 8         18 $z[0] += $c;
672             }
673              
674 4         11 return [@z];
675             }
676              
677             =head3 pl_add()
678              
679             $polyn_ref = pl_add(\@m, \@n);
680              
681             Add two lists of numbers as though they were polynomial coefficients.
682              
683             =cut
684              
685             sub pl_add
686             {
687 41     41 1 1281 my(@av) = @{$_[0]};
  41         70  
688 41         52 my(@bv) = @{$_[1]};
  41         58  
689 41         54 my $ldiff = scalar @av - scalar @bv;
690              
691 41 100       117 my @result = ($ldiff < 0)?
692             splice(@bv, scalar @bv + $ldiff, -$ldiff):
693             splice(@av, scalar @av - $ldiff, $ldiff);
694              
695 41         138 unshift @result, map($av[$_] + $bv[$_], 0.. $#av);
696              
697 41         98 return \@result;
698             }
699              
700             =head3 pl_sub()
701              
702             $polyn_ref = pl_sub(\@m, \@n);
703              
704             Subtract the second list of numbers from the first as though they
705             were polynomial coefficients.
706              
707             =cut
708              
709             sub pl_sub
710             {
711 3     3 1 997 my(@av) = @{$_[0]};
  3         8  
712 3         4 my(@bv) = @{$_[1]};
  3         8  
713 3         7 my $ldiff = scalar @av - scalar @bv;
714              
715             my @result = ($ldiff < 0)?
716 3 100       27 map {-$_} splice(@bv, scalar @bv + $ldiff, -$ldiff):
  4         8  
717             splice(@av, scalar @av - $ldiff, $ldiff);
718              
719 3         28 unshift @result, map($av[$_] - $bv[$_], 0.. $#av);
720              
721 3         12 return \@result;
722             }
723              
724             =head3 pl_div()
725              
726             ($q_ref, $r_ref) = pl_div(\@numerator, \@divisor);
727              
728             Synthetic division for polynomials. Divides the first list of coefficients
729             by the second list.
730              
731             Returns references to the quotient and the remainder.
732              
733             Remember to check for leading zeros (which are rightmost in the list) in
734             the returned values. For example,
735              
736             my @n = (4, 12, 9, 3);
737             my @d = (1, 3, 3, 1);
738              
739             my($q_ref, $r_ref) = pl_div(\@n, \@d);
740              
741             After division you will have returned C<(3)> as the quotient,
742             and C<(1, 3, 0)> as the remainder. In general, you will want to remove
743             the leading zero, or for that matter values within epsilon of zero, in
744             the remainder.
745              
746             my($q_ref, $r_ref) = pl_div($f1, $f2);
747              
748             #
749             # Remove any leading zeros (i.e., numbers smaller in
750             # magnitude than machine epsilon) in the remainder.
751             #
752             my @remd = @{$r_ref};
753             pop @remd while (@remd and abs($remd[$#remd]) < $epsilon);
754              
755             $f1 = $f2;
756             $f2 = [@remd];
757              
758             If C<$f1> and C<$f2> were to go through that bit of code again, not
759             removing the leading zeros would lead to a divide-by-zero error.
760              
761             If either list of coefficients is empty, pl_div() returns undefs for
762             both quotient and remainder.
763              
764             =cut
765              
766             sub pl_div
767             {
768 5     5 1 1813 my @numerator = @{$_[0]};
  5         15  
769 5         10 my @divisor = @{$_[1]};
  5         9  
770              
771 5         6 my @quotient;
772              
773 5         24 my $n_degree = $#numerator;
774 5         6 my $d_degree = $#divisor;
775              
776             #
777             # Sanity checks: a numerator less than the divisor
778             # is automatically the remainder; and return a pair
779             # of undefs if either set of coefficients are
780             # empty lists.
781             #
782 5 50       14 return ([0], \@numerator) if ($n_degree < $d_degree);
783 5 50 33     23 return (undef, undef) if ($d_degree < 0 or $n_degree < 0);
784              
785 5         9 my $lead_coefficient = $divisor[$#divisor];
786              
787             #
788             # Perform the synthetic division. The remainder will
789             # be what's left in the numerator.
790             # (4, 13, 4, -9, 6) / (1, 2) = (4, 5, -6, 3)
791             #
792             @quotient = reverse map {
793             #
794             # Get the next term for the quotient. We pop
795             # off the lead numerator term, which would become
796             # zero due to subtraction anyway.
797             #
798 5         14 my $q = (pop @numerator)/$lead_coefficient;
  20         32  
799              
800 20         37 for my $k (0..$d_degree - 1)
801             {
802 67         104 $numerator[$#numerator - $k] -= $q * $divisor[$d_degree - $k - 1];
803             }
804              
805 20         39 $q;
806             } reverse (0 .. $n_degree - $d_degree);
807              
808 5         16 return (\@quotient, \@numerator);
809             }
810              
811             =head3 pl_mult()
812              
813             $m_ref = pl_mult(\@coefficients1, \@coefficients2);
814              
815             Returns the reference to the product of the two multiplicands.
816              
817             =cut
818              
819             sub pl_mult
820             {
821 51     51 1 18800 my($av, $bv) = @_;
822 51         63 my $a_degree = $#{$av};
  51         87  
823 51         109 my $b_degree = $#{$bv};
  51         74  
824              
825             #
826             # Rather than multiplying left to right for each element,
827             # sum to each degree of the resulting polynomial (the list
828             # after the map block). Still an O(n**2) operation, but
829             # we don't need separate storage variables.
830             #
831             return [ map {
832 51 100       113 my $a_idx = ($a_degree > $_)? $_: $a_degree;
  186         299  
833 186 100       279 my $b_to = ($b_degree > $_)? $_: $b_degree;
834 186         226 my $b_from = $_ - $a_idx;
835              
836 186         259 my $c = $av->[$a_idx] * $bv->[$b_from];
837              
838 186         2601 for my $b_idx ($b_from+1 .. $b_to)
839             {
840 98         839 $c += $av->[--$a_idx] * $bv->[$b_idx];
841             }
842 186         3086 $c;
843             } (0 .. $a_degree + $b_degree) ];
844             }
845              
846             =head3 pl_derivative()
847              
848             $poly_ref = pl_derivative(\@coefficients);
849              
850             Returns the derivative of a polynomial.
851              
852             =cut
853              
854             sub pl_derivative
855             {
856 8     8 1 2673 my @coefficients = @{$_[0]};
  8         17  
857 8         15 my $degree = $#coefficients;
858              
859 8 100       21 return [] if ($degree < 1);
860              
861 7         23 $coefficients[$_] *= $_ for (2..$degree);
862              
863 7         11 shift @coefficients;
864 7         14 return \@coefficients;
865             }
866              
867             =head3 pl_antiderivative()
868              
869             $poly_ref = pl_antiderivative(\@coefficients);
870              
871             Returns the antiderivative of a polynomial. The constant value is
872             always set to zero and will need to be changed by the caller if a
873             different constant is needed.
874              
875             my @coefficients = (1, 2, -3, 2);
876             my $integral = pl_antiderivative(\@coefficients);
877              
878             #
879             # Integral needs to be 0 at x = 1.
880             #
881             my @coeff1 = @{$integral};
882             $coeff1[0] = - pl_evaluate($integral, 1);
883              
884             =cut
885              
886             sub pl_antiderivative
887             {
888 8     8 1 2954 my @coefficients = @{$_[0]};
  8         37  
889 8         12 my $degree = scalar @coefficients;
890              
891             #
892             # Sanity check if its an empty list.
893             #
894 8 100       23 return [0] if ($degree < 1);
895              
896 7         31 $coefficients[$_ - 1] /= $_ for (2..$degree);
897              
898 7         20 unshift @coefficients, 0;
899 7         15 return \@coefficients;
900             }
901              
902             =head1 AUTHOR
903              
904             John M. Gamble, C<< >>
905              
906             =head1 SEE ALSO
907              
908             L for a complete set of polynomial operations, with the
909             added convenience that objects bring.
910              
911             Among its other functions, L has the mathematically useful
912             functions max(), min(), product(), sum(), and sum0().
913              
914             L has the function minmax().
915              
916             L has gcd() and lcm() functions, as well as vecsum(),
917             vecprod(), vecmin(), and vecmax(), which are like the L
918             functions but which can force integer use, and when appropriate use
919             L.
920              
921             L Likewise has min(), max(), sum() (which can take
922             as arguments array references as well as arrays), plus maxabs(),
923             minabs(), sumbyelement(), convolute(), and other functions.
924              
925             =head1 BUGS
926              
927             Please report any bugs or feature requests to C, or through
928             the web interface at L. I will be notified, and then you'll
929             automatically be notified of progress on your bug as I make changes.
930              
931             =head1 SUPPORT
932              
933             This module is on Github at L.
934              
935             You can also look for information at:
936              
937             =over 4
938              
939             =item * RT: CPAN's request tracker (report bugs here)
940              
941             L
942              
943             =item * AnnoCPAN: Annotated CPAN documentation
944              
945             L
946              
947             =item * CPAN Ratings
948              
949             L
950              
951             =item * Search CPAN
952              
953             L
954              
955             =back
956              
957              
958             =head1 ACKNOWLEDGEMENTS
959              
960             To J. A. R. Williams who got the ball rolling with L.
961              
962             =head1 LICENSE AND COPYRIGHT
963              
964             Copyright (c) 2017 John M. Gamble. All rights reserved. This program is
965             free software; you can redistribute it and/or modify it under the same
966             terms as Perl itself.
967              
968             =cut
969              
970             1; # End of Math::Utils