File Coverage

blib/lib/Math/Utils.pm
Criterion Covered Total %
statement 156 168 92.8
branch 78 94 82.9
condition 26 36 72.2
subroutine 33 35 94.2
pod 21 21 100.0
total 314 354 88.7


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