File Coverage

blib/lib/Math/Polynomial/Horner.pm
Criterion Covered Total %
statement 165 169 97.6
branch 53 60 88.3
condition 48 66 72.7
subroutine 18 18 100.0
pod 1 1 100.0
total 285 314 90.7


line stmt bran cond sub pod time code
1             # Copyright 2007, 2008, 2009, 2010, 2011, 2019 Kevin Ryde
2              
3             # This file is part of Math-Polynomial-Horner.
4             #
5             # Math-Polynomial-Horner is free software; you can redistribute it and/or
6             # modify it under the terms of the GNU General Public License as published
7             # by the Free Software Foundation; either version 3, or (at your option) any
8             # later version.
9             #
10             # Math-Polynomial-Horner is distributed in the hope that it will be useful,
11             # but WITHOUT ANY WARRANTY; without even the implied warranty of
12             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13             # Public License for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Math-Polynomial-Horner. If not, see .
17              
18             package Math::Polynomial::Horner;
19 1     1   661 use 5.006;
  1         3  
20 1     1   5 use strict;
  1         2  
  1         21  
21 1     1   5 use warnings;
  1         3  
  1         28  
22 1     1   6 use vars '$VERSION';
  1         2  
  1         167  
23              
24             # uncomment this to run the ### lines
25             #use Smart::Comments;
26              
27             $VERSION = 4;
28              
29             sub _stringize {
30 262     262   2306 return "$_[0]";
31             }
32              
33 1         109 use constant _config_defaults =>
34             (ascending => 0,
35             with_variable => 1,
36             fold_sign => 0,
37             fold_zero => 1,
38             fold_one => 1,
39             fold_exp_zero => 1,
40             fold_exp_one => 1,
41             convert_coeff => \&_stringize,
42             plus => q{ + },
43             minus => q{ - },
44             leading_plus => q{},
45             leading_minus => q{- },
46             times => q{ },
47             power => q{^},
48             variable => q{x},
49             prefix => q{(},
50             suffix => q{)},
51              
52             # extras
53             left_paren => '(',
54             right_paren => ')',
55              
56             # secret extras
57             fold_sign_swap_end => 0,
58             power_by_times_upto => 0,
59 1     1   7 );
  1         2  
60              
61 1         52 use constant _config_perl =>
62             (fold_sign => 1,
63             fold_sign_swap_end => 1,
64             leading_minus => q{-},
65             power => q{**},
66 1     1   7 power_by_times_upto => 3);
  1         2  
67              
68 1     1   6 use constant _EMPTY => 0;
  1         2  
  1         71  
69 1     1   7 use constant _FACTOR => 1;
  1         2  
  1         52  
70 1     1   6 use constant _SUM => 2;
  1         2  
  1         1364  
71              
72             sub as_string {
73 168     168 1 123685 my ($poly, $string_config) = @_;
74 168         377 my $degree = $poly->degree;
75             ### $degree
76              
77 168   33     1137 $string_config ||= ($poly->string_config
      33        
78             || (ref $poly)->string_config);
79 168         1904 my %config = do {
80             (_config_defaults(),
81 168 50       1896 ($string_config->{'for_perl'} ? _config_perl() : ()),
82             %$string_config)
83             };
84              
85 168 100       517 if ($degree <= 0) {
86             ### empty or constant
87 35         94 return $poly->as_string(\%config);
88             }
89              
90 133         309 my $zero = $poly->coeff_zero;
91 133         488 my $one = $poly->coeff_one;
92 133         416 my $convert = $config{'convert_coeff'};
93 133         260 my $ret = '';
94 133         208 my $pre = '';
95 133         188 my $post = '';
96 133         193 my $last = _EMPTY;
97              
98             my $leading_const = sub {
99 26     26   41 my ($coeff) = @_;
100             ### leading_const: "$coeff"
101 26 50 66     74 if ($config{'fold_sign'} && $coeff < $zero) {
102 0         0 $ret .= $config{'leading_minus'};
103 0         0 $coeff = -$coeff;
104             } else {
105 26         41 $ret .= $config{'leading_plus'};
106             }
107 26         47 $ret .= $convert->($coeff);
108 26         52 $last = _FACTOR;
109 133         595 };
110              
111             my $leading_factor = sub {
112 102     102   189 my ($coeff) = @_;
113             ### leading_factor: "$coeff"
114 102         159 my $pm = '';
115 102 100 100     287 if ($config{'fold_sign'} && $coeff < $zero) {
116 26         45 $ret .= $config{'leading_minus'};
117 26         42 $coeff = -$coeff;
118             } else {
119 76         140 $ret .= $config{'leading_plus'};
120             }
121 102 100 100     307 if ($config{'fold_one'} && $coeff == $one) {
122             ### fold_one skip to ret: $ret
123 26         45 $last = _EMPTY;
124 26         49 return;
125             }
126 76         158 $ret .= $pm . $convert->($coeff);
127 76         142 $last = _FACTOR;
128             ### gives ret: $ret
129 133         432 };
130              
131             my $times_coeff = sub {
132 22     22   33 my ($coeff) = @_;
133             ### times_coeff: "$coeff"
134 22 50 66     78 if ($config{'fold_one'}
135             && $coeff == $one) {
136             ### fold_one skip
137 0         0 return;
138             }
139 22 50       61 if ($last ne _EMPTY) {
140 22         36 $ret .= $config{'times'};
141             }
142 22         37 $ret .= $convert->($coeff);
143 22         40 $last = _FACTOR;
144             ### times_coeff gives ret: $ret
145 133         363 };
146              
147             my $plus_coeff = sub {
148 103     103   189 my ($coeff) = @_;
149 103 100 100     265 if ($config{'fold_sign'} && $coeff < $zero) {
150 23         36 $ret .= $config{'minus'};
151 23         37 $coeff = -$coeff;
152             } else {
153 80         125 $ret .= $config{'plus'};
154             }
155 103         163 $ret .= $convert->($coeff);
156 103         278 $last = _SUM;
157 133         354 };
158              
159 133         218 my $xpow = 0;
160             my $show_xpow = sub {
161             ### show_xpow: $xpow
162 155 100   155   293 return if ($xpow == 0);
163 154         229 $ret .= $config{'variable'};
164 154 100 66     458 if ($xpow == 1 && $config{'fold_exp_one'}) {
    100          
165             # x^1 -> x
166             } elsif ($xpow <= $config{'power_by_times_upto'}) {
167             # x*x*...*x
168 11         34 $ret .= ($config{'times'} . $config{'variable'}) x ($xpow-1);
169             } else {
170             # x^123
171 40         83 $ret .= $config{'power'} . $xpow;
172             }
173 154         225 $xpow = 0;
174 154         198 $last = _FACTOR;
175 133         360 };
176              
177             my $times_xpow = sub {
178             ### times_xpow: $xpow, $ret
179 207 100   207   388 if ($xpow) {
180 120 100       241 if ($last eq _SUM) {
181 14         23 $pre .= $config{'left_paren'};
182 14         21 $ret .= $config{'right_paren'};
183 14         19 $last = _FACTOR;
184             }
185 120 100       229 if ($last ne _EMPTY) {
186 94         142 $ret .= $config{'times'};
187             }
188 120         248 $show_xpow->();
189 120         165 $last = _FACTOR;
190             }
191             ### times_xpow gives: "pre=$pre ret=$ret"
192 133         359 };
193              
194 133 100       298 if ($config{'ascending'}) {
195             ### ascending
196              
197 29         41 my $limit = $degree;
198             {
199 29         46 my ($j, $high, $second);
  29         43  
200 29 50 100     88 if ($config{'fold_sign'} && $config{'fold_sign_swap_end'}
      66        
      66        
      33        
201             && ($high = $poly->coeff($degree)) > $zero
202             && (($j,$second) = _second_highest_coeff($poly,$config{'fold_zero'}))
203             && $second < $zero) {
204 2         6 $leading_const->($high);
205 2         3 $last = _FACTOR;
206              
207 2         4 $xpow = $degree - $j;
208 2         4 $times_xpow->();
209              
210 2         6 $plus_coeff->($second);
211 2         3 $limit = $j - 1;
212 2         5 $post = $ret;
213 2 100       6 if ($limit >= 0) {
214             $post = $config{'times'}
215 1         4 . $config{'left_paren'} . $post . $config{'right_paren'};
216             }
217 2         3 $ret = '';
218 2         3 $last = _EMPTY;
219             ### fold_sign_swap_end gives
220             ### $post
221             ### $limit
222             }
223             }
224              
225 29         49 $xpow = -1;
226 29         70 foreach my $i (0 .. $limit) {
227             ### $i
228 117         168 $xpow++;
229 117         213 my $coeff = $poly->coeff($i);
230 117 100 66     1068 if ($config{'fold_zero'} && $coeff == $zero) {
231 68         104 next;
232             }
233              
234 49 100       97 if ($xpow) {
235 33 100       64 if (length($ret)) {
236 21 100 100     77 if ($i == $degree
      66        
237             && $config{'fold_sign'}
238             && $coeff < $zero) {
239             ### highest coeff fold ... + x*-5 -> ... - x*5
240 2         3 $coeff = - $coeff;
241 2         5 $ret .= $config{'minus'};
242             } else {
243             # other coeffs ... + x*(...) or highest ... + x*5
244 19         30 $ret .= $config{'plus'};
245             }
246             }
247 33         70 $show_xpow->();
248 33 100       69 if ($i == $degree) {
249 27 100 100     106 if ($config{'fold_one'}
250             && $coeff == $one) {
251             ### highest coeff x*1 -> x
252             } else {
253             ### highest coeff: "$coeff"
254 22         43 $times_coeff->($coeff);
255             }
256 27         49 last;
257             }
258 6         13 $ret .= $config{'times'} . $config{'left_paren'};
259 6         10 $post .= $config{'right_paren'};
260             }
261 22         46 $leading_const->($coeff);
262             }
263              
264             ### final xpow: $xpow
265 29 100       63 if ($limit != $degree) {
266 2 100       5 if ($last != _EMPTY) {
267 1         3 $ret .= $config{'plus'};
268             }
269 2         3 $xpow++;
270 2         6 $show_xpow->();
271             }
272              
273             } else {
274             ### descending
275              
276 104         233 my $coeff = $poly->coeff($degree);
277             ### highest coeff: "$coeff"
278 104         886 my $i = $degree;
279              
280             {
281 104         148 my ($j, $second);
  104         173  
282 104 50 100     361 if ($config{'fold_sign'} && $config{'fold_sign_swap_end'}
      66        
      66        
      33        
283             && $coeff < $zero
284             && (($j,$second) = _second_highest_coeff($poly,$config{'fold_zero'}))
285             && $second > $zero) {
286 2         5 $leading_const->($second);
287 2         4 $plus_coeff->($coeff);
288 2         4 $last = _FACTOR;
289 2         3 $xpow = $degree - $j;
290 2         4 $times_xpow->();
291 2         2 $i = $j - 1;
292 2         4 $last = _SUM;
293             ### fold_sign_swap_end gives
294             ### $ret
295             ### $i
296             }
297             }
298              
299             # normal start from high coeff, ie. not the swap bit
300 104 100       210 if ($i == $degree) {
301 102         235 $leading_factor->($coeff);
302 102         140 $i--;
303             }
304 104         211 for ( ; $i >= 0; $i--) {
305             ### $i
306 195         266 $xpow++;
307 195         356 $coeff = $poly->coeff($i);
308 195 100 100     1737 if ($config{'fold_zero'} && $coeff == $zero) {
309 96         183 next;
310             }
311 99         217 $times_xpow->();
312 99         166 $plus_coeff->($coeff);
313             }
314 104         186 $times_xpow->();
315             }
316              
317             ### prefix: $config{'prefix'}
318             ### $pre
319             ### $ret
320             ### $post
321             ### suffix: $config{'suffix'}
322 133         1958 return $config{'prefix'} . $pre . $ret . $post . $config{'suffix'};
323             }
324              
325             sub _second_highest_coeff {
326 4     4   30 my ($poly, $fold_zero) = @_;
327 4         10 my $j = $poly->degree;
328             ### assert: $j >= 0
329              
330 4         18 for (;;) {
331 10 50       38 if (--$j < 0) {
332 0         0 return; # not found
333             }
334 10         19 my $coeff = $poly->coeff($j);
335 10 100 66     83 unless ($fold_zero && $coeff == $poly->coeff_zero) {
336 4         38 return ($j, $coeff); # found
337             }
338             }
339             }
340              
341             1;
342             __END__