File Coverage

blib/lib/Math/Polynom.pm
Criterion Covered Total %
statement 288 296 97.3
branch 167 190 87.8
condition 31 39 79.4
subroutine 34 34 100.0
pod 13 13 100.0
total 533 572 93.1


line stmt bran cond sub pod time code
1             #################################################################
2             #
3             # Math::Polynom - Operations on polynoms
4             #
5             # $Id: Polynom.pm,v 1.10 2007/07/11 13:01:48 erwan_lemonnier Exp $
6             #
7             # 061025 erwan Started implementation
8             # 061206 erwan Added the secant method
9             # 061214 erwan Added Brent's method
10             # 070112 erwan Fixed bug in identification of nan scalars
11             # 070220 erwan Updated POD to warn for convergence toward non roots
12             # 070404 erwan Added $DEBUG
13             # 070412 erwan Updated POD
14             # 070417 erwan Modified disclaimer
15             # 070711 erwan Use looks_like_number and float_is_nan
16             #
17              
18             package Math::Polynom;
19              
20 15     15   165869 use 5.006;
  15         53  
  15         611  
21 15     15   85 use strict;
  15         31  
  15         500  
22 15     15   87 use warnings;
  15         30  
  15         744  
23 15     15   97 use Carp qw(confess croak);
  15         25  
  15         1205  
24 15     15   19282 use Data::Dumper;
  15         186655  
  15         1380  
25 15     15   23461 use Data::Float qw(float_is_nan);
  15         199983  
  15         1935  
26 15     15   158 use Scalar::Util qw(looks_like_number);
  15         56  
  15         2190  
27              
28 15     15   14651 use accessors qw(error error_message iterations xpos xneg);
  15         15386  
  15         98  
29              
30 15     15   3216 use constant NO_ERROR => 0;
  15         28  
  15         692  
31 15     15   116 use constant ERROR_NAN => 1;
  15         31  
  15         575  
32 15     15   124 use constant ERROR_MAX_DEPTH => 2;
  15         31  
  15         573  
33 15     15   71 use constant ERROR_EMPTY_POLYNOM => 3;
  15         29  
  15         870  
34 15     15   76 use constant ERROR_DIVIDE_BY_ZERO => 4;
  15         29  
  15         636  
35 15     15   72 use constant ERROR_WRONG_SIGNS => 5;
  15         30  
  15         710  
36 15     15   74 use constant ERROR_NOT_A_ROOT => 6;
  15         24  
  15         75207  
37              
38             our $VERSION = '0.13';
39             our $DEBUG = 0;
40              
41             #----------------------------------------------------------------
42             #
43             # _debug
44             #
45              
46             sub _debug {
47 414     414   516 my $msg = shift;
48 414 50       885 print STDOUT "Math::Polynom: $msg\n" if ($DEBUG);
49             }
50              
51             #----------------------------------------------------------------
52             #
53             # _add_monom - add a monom to a polynomial, ie a $coef**$power
54             #
55              
56             sub _add_monom {
57 134     134   190 my($self,$coef,$power) = @_;
58              
59 134 100       405 if (exists $self->{polynom}->{$power}) {
60 13         34 $self->{polynom}->{$power} += $coef;
61             } else {
62 121         334 $self->{polynom}->{$power} = $coef;
63             }
64 134         289 return $self;
65             }
66              
67             #----------------------------------------------------------------
68             #
69             # _clean - remove terms with zero as coefficient
70             #
71              
72             sub _clean {
73 238     238   325 my $self = shift;
74              
75 238         292 while (my($power,$coef) = each %{$self->{polynom}}) {
  726         2389  
76 488 100       1344 if ($coef == 0) {
77 16         42 delete $self->{polynom}->{$power};
78             }
79             }
80              
81 238         794 return $self;
82             }
83              
84             #----------------------------------------------------------------
85             #
86             # _is_root - return true if the polynomial evaluates to something close enough to 0 on the root
87             #
88              
89             sub _is_root {
90 42     42   62 my ($self,$value) = @_;
91 42         77 return (abs($self->eval($value)) < 1);
92             }
93              
94             #----------------------------------------------------------------
95             #
96             # _error - die nicely
97             #
98              
99             sub _error {
100 44     44   346 my $msg = shift;
101 44         6705 croak __PACKAGE__." ERROR: $msg\n";
102             }
103              
104             sub _exception {
105 10     10   60 my ($self,$code,$msg,$args) = @_;
106              
107 10         43 $msg = "ERROR: $msg\nwith polynom:\n".$self->stringify."\n";
108 10 50       49 if (defined $args) {
109 10         48 $msg .= "with arguments:\n".Dumper($args);
110             }
111 10         1014 $msg .= "at iteration ".$self->iterations."\n";
112              
113 10         82 $self->error_message($msg);
114 10         63 $self->error($code);
115              
116 10         50 croak $self->error_message;
117             }
118              
119             #################################################################
120             #
121             #
122             # PUBLIC
123             #
124             #
125             #################################################################
126              
127             #----------------------------------------------------------------
128             #
129             # new - construct a new polynomial
130             #
131              
132             sub new {
133 155     155 1 52138 my($pkg,@args) = @_;
134 155   33     1026 $pkg = ref $pkg || $pkg;
135              
136 155 100       451 _error("new() got odd number of arguments. can not be a hash") if (scalar(@args) % 2);
137              
138 154         637 my %hash = @args;
139 154         303 foreach my $n (@args) {
140 483 100       1806 _error("at least one argument of new() is not numeric:\n".Dumper(\%hash)) if (!looks_like_number($n));
141             }
142              
143 153         959 my $self = bless({polynom => \%hash},$pkg)->_clean;
144 153         12764 $self->error(NO_ERROR);
145 153         1024 $self->iterations(0);
146              
147 153         917 return $self;
148             }
149              
150             #----------------------------------------------------------------
151             #
152             # clone - return a clone of self
153             #
154              
155             sub clone {
156 36     36 1 70 my $self = shift;
157 36         45 return __PACKAGE__->new(%{$self->{polynom}});
  36         156  
158             }
159              
160             #----------------------------------------------------------------
161             #
162             # stringify - return current polynomial as a string
163             #
164              
165             sub stringify {
166 118     118 1 562 my $self = shift;
167 118         172 return join(" + ", map { $self->{polynom}->{$_}."*x^".$_ } reverse sort keys %{$self->{polynom}});
  208         1314  
  118         514  
168             }
169              
170             #----------------------------------------------------------------
171             #
172             # derivate - return the polynomial's derivate
173             #
174              
175             sub derivate {
176 20     20 1 39 my $self = shift;
177 20         60 my $result = __PACKAGE__->new();
178 20         40 while (my($power,$coef) = each %{$self->{polynom}} ) {
  66         223  
179 46         153 $result->_add_monom($coef*$power,$power-1);
180             }
181 20         201 return $result->_clean;
182             }
183              
184             #----------------------------------------------------------------
185             #
186             # eval - evaluate the polynomial on a given value, return result
187             #
188              
189             sub eval {
190 1641     1641 1 20527 my($self,$x) = @_;
191              
192 1641 100       2850 _error("eval() got wrong number of arguments") if (scalar @_ != 2);
193 1639 100       8087 _error("eval() got undefined argument") if (!defined $x);
194 1638 100       4351 _error("eval()'s argument is not numeric ($x)") if (!looks_like_number($x));
195              
196 1634         1649 my $r = 0;
197 1634         1624 while (my($power,$coef) = each %{$self->{polynom}} ) {
  5870         16063  
198 4236         11148 $r += $coef*($x**$power);
199             }
200              
201 1634 100       35326 if (!float_is_nan($r)) {
202 1630 100 100     23732 if (!defined $self->xpos && $r > 0) {
    100 100        
203 37         334 $self->xpos($x);
204             } elsif (!defined $self->xneg && $r < 0) {
205 18         294 $self->xneg($x);
206             }
207             }
208              
209 1634         18007 return $r;
210             }
211              
212             #----------------------------------------------------------------
213             #
214             # add - add a polynomial/number to current polynomial
215             #
216              
217             sub add {
218 27     27 1 1452 my($self,$p) = @_;
219              
220 27 100       77 _error("add() got wrong number of arguments") if (scalar @_ != 2);
221 25 100       54 _error("add() got undefined argument") if (!defined $p);
222              
223             # adding 2 polynomials
224 24 100       69 if (ref $p eq __PACKAGE__) {
225 20         47 my $result = $self->clone;
226 20         35 while (my($power,$coef) = each %{$p->{polynom}}) {
  68         236  
227 48         93 $result->_add_monom($coef,$power);
228             }
229 20         44 return $result->_clean;
230             }
231              
232             # adding a constant to a polynomial
233 4 100       19 _error("add() got non numeric argument") if (!looks_like_number($p));
234              
235 3         8 return $self->clone->_add_monom($p,0)->_clean;
236             }
237              
238             #----------------------------------------------------------------
239             #
240             # minus - substract a polynomial/number to current polynomial
241             #
242              
243             sub minus {
244 15     15 1 1497 my($self,$p) = @_;
245              
246 15 100       48 _error("minus() got wrong number of arguments") if (scalar @_ != 2);
247 13 100       35 _error("minus() got undefined argument") if (!defined $p);
248              
249 12 100       32 if (ref $p eq __PACKAGE__) {
250 8         25 return $self->clone->add($p->negate)->_clean;
251             }
252              
253 4 100       18 _error("minus() got non numeric argument") if (!looks_like_number($p));
254              
255 3         9 return $self->clone->_add_monom(-$p,0)->_clean;
256             }
257              
258             #----------------------------------------------------------------
259             #
260             # negate - negate current polynomial
261             #
262              
263             sub negate {
264 10     10 1 27 my $self = shift;
265 10         18 return __PACKAGE__->new(map { $_, - $self->{polynom}->{$_} } keys %{$self->{polynom}})->_clean;
  23         76  
  10         33  
266             }
267              
268             #----------------------------------------------------------------
269             #
270             # multiply - multiply current polynomial with a polynomial/number
271             #
272              
273             sub multiply {
274 22     22 1 18054 my($self,$p) = @_;
275              
276 22 100       68 _error("multiply() got wrong number of arguments") if (scalar @_ != 2);
277 20 100       47 _error("multiply() got undefined argument") if (!defined $p);
278              
279 19 100       51 if (ref $p eq __PACKAGE__) {
280 18         43 my $result = __PACKAGE__->new;
281 18         27 while (my($power1,$coef1) = each %{$self->{polynom}}) {
  39         127  
282 21         29 while (my($power2,$coef2) = each %{$p->{polynom}}) {
  55         165  
283 34         107 $result->_add_monom($coef1 * $coef2, $power1 + $power2);
284             }
285             }
286 18         40 return $result->_clean;
287             }
288              
289 1 50       41 _error("multiply() got non numeric argument") if (!looks_like_number($p));
290              
291 0         0 return __PACKAGE__->new(map { $_, $p * $self->{polynom}->{$_} } keys %{$self->{polynom}})->_clean;
  0         0  
  0         0  
292             }
293              
294             #----------------------------------------------------------------
295             #
296             # divide - divide the current polynomial with a float
297             #
298              
299             sub divide {
300 8     8 1 1390 my($self,$x) = @_;
301              
302 8 100       18 _error("divide() got wrong number of arguments") if (scalar @_ != 2);
303 7 100       14 _error("divide() got undefined argument") if (!defined $x);
304 6 100       20 _error("divide() got non numeric argument") if (!looks_like_number($x));
305 4 100       9 _error("cannot divide by 0") if ($x == 0);
306              
307 3         2 return __PACKAGE__->new(map { $_, $self->{polynom}->{$_}/$x } keys %{$self->{polynom}})->_clean;
  6         19  
  3         8  
308             }
309              
310             #----------------------------------------------------------------
311             #
312             # newton_raphson - attempt to find a polynomial's root with Newton Raphson
313             #
314              
315             sub newton_raphson {
316 19     19 1 24038 my($self,%hash) = @_;
317 19         65 my $new_guess = 1;
318 19         30 my $precision = 0.1;
319 19         19 my $max_depth = 100;
320              
321 19         68 $self->iterations(0);
322 19         119 $self->error(NO_ERROR);
323              
324 19 100       112 $new_guess = $hash{guess} if (exists $hash{guess});
325 19 100       54 $precision = $hash{precision} if (exists $hash{precision});
326 19 100       49 $max_depth = $hash{max_depth} if (exists $hash{max_depth});
327              
328 19 100       45 _error("newton_raphson() got undefined guess") if (!defined $new_guess);
329 18 100       42 _error("newton_raphson() got undefined precision") if (!defined $precision);
330 17 50       33 _error("newton_raphson() got undefined max_depth") if (!defined $max_depth);
331 17 100       59 _error("newton_raphson() got non numeric guess") if (!looks_like_number($new_guess));
332 16 100       38 _error("newton_raphson() got non numeric precision") if (!looks_like_number($precision));
333 15 50       82 _error("newton_raphson() got non integer max_depth") if ($max_depth !~ /^\d+$/);
334              
335 15         54 $self->_exception(ERROR_EMPTY_POLYNOM,"cannot find the root of an empty polynom",\%hash)
336 15 100       16 if (scalar keys %{$self->{polynom}} == 0);
337              
338 14         40 my $derivate = $self->derivate;
339 14         26 my $old_guess = $new_guess - 2*$precision; # pass the while condition first time
340              
341 14         41 while (abs($new_guess - $old_guess) > $precision) {
342 126         781 $old_guess = $new_guess;
343              
344 126         252 my $dividend = $derivate->eval($old_guess);
345 126 50       270 $self->_exception(ERROR_DIVIDE_BY_ZERO,"division by zero: polynomial's derivate is 0 at $old_guess",\%hash)
346             if ($dividend == 0);
347              
348 126         257 $new_guess = $old_guess - $self->eval($old_guess)/$dividend;
349              
350 126         332 $self->iterations($self->iterations + 1);
351 126 100       946 $self->_exception(ERROR_MAX_DEPTH,"reached maximum number of iterations [$max_depth] without getting close enough to the root.",\%hash)
352             if ($self->iterations > $max_depth);
353              
354 125 100       3157 $self->_exception(ERROR_NAN,"new guess is not a real number in newton_raphson().",\%hash)
355             if (float_is_nan($new_guess));
356             }
357              
358 12 50       109 if (!$self->_is_root($new_guess)) {
359 0         0 $self->_exception(ERROR_NOT_A_ROOT,"newton_raphson() converges toward $new_guess but that doesn't appear to be a root.",\%hash);
360             }
361              
362 12         137 return $new_guess;
363             }
364              
365             #----------------------------------------------------------------
366             #
367             # secant - implement the Secant algorithm to approximate the root of this polynomial
368             #
369              
370             sub secant {
371 23     23 1 11532 my ($self,%hash) = @_;
372 23         36 my $precision = 0.1;
373 23         23 my $max_depth = 100;
374 23         26 my ($p0,$p1);
375              
376 23         72 $self->iterations(0);
377 23         142 $self->error(NO_ERROR);
378              
379 23 100       120 $precision = $hash{precision} if (exists $hash{precision});
380 23 100       52 $max_depth = $hash{max_depth} if (exists $hash{max_depth});
381 23 100       61 $p0 = $hash{p0} if (exists $hash{p0});
382 23 100       49 $p1 = $hash{p1} if (exists $hash{p1});
383              
384 23 100       48 _error("secant() got undefined precision") if (!defined $precision);
385 22 50       39 _error("secant() got undefined max_depth") if (!defined $max_depth);
386 22 100       76 _error("secant() got non numeric precision") if (!looks_like_number($precision));
387 21 50       105 _error("secant() got non integer max_depth") if ($max_depth !~ /^\d+$/);
388 21 100       40 _error("secant() got undefined p0") if (!defined $p0);
389 20 100       37 _error("secant() got undefined p1") if (!defined $p1);
390 19 100       48 _error("secant() got non numeric p0") if (!looks_like_number($p0));
391 18 100       41 _error("secant() got non numeric p1") if (!looks_like_number($p1));
392 17 100       41 _error("secant() got same value for p0 and p1") if ($p0 == $p1);
393              
394 16         48 $self->_exception(ERROR_EMPTY_POLYNOM,"cannot find the root of an empty polynomial",\%hash)
395 16 100       22 if (scalar keys %{$self->{polynom}} == 0);
396              
397             # NOTE: this code is almost a copy/paste from Math::Function::Roots, I just added exception handling
398              
399 15         34 my $q0 = $self->eval($p0);
400 15         27 my $q1 = $self->eval($p1);
401 15         21 my $p;
402              
403 15 50 33     322 $self->_exception(ERROR_NAN,"q0 or q1 are not a real number in first eval in secant()",\%hash)
404             if (float_is_nan($q0) || float_is_nan($q1));
405              
406 15 50       476 return $p0 if ($q0 == 0);
407 15 100       28 return $p1 if ($q1 == 0);
408              
409 14         34 for (my $depth = 1; $depth <= $max_depth; $depth++) {
410              
411 119         311 $self->iterations($depth);
412              
413 119 50       580 $self->_exception(ERROR_DIVIDE_BY_ZERO,"division by zero with p0=$p0, p1=$p1, q1=q0=$q1 in secant()",\%hash)
414             if (($q1 - $q0) == 0);
415              
416 119         190 $p = ($q1 * $p0 - $p1 * $q0) / ($q1 - $q0);
417              
418 119 50       2612 $self->_exception(ERROR_NAN,"p is not a real number in secant()",\%hash)
419             if (float_is_nan($p));
420              
421 119         781 my $debug = "secant at depth ".$self->iterations.", p0=$p0, p1=$p1, p=$p";
422              
423 119         1188 $p0 = $p1;
424 119         202 $q0 = $q1;
425 119         236 $q1 = $self->eval($p);
426              
427 119 100       2470 $self->_exception(ERROR_NAN,"q1 is not a real number in secant()",\%hash)
428             if (float_is_nan($q1));
429              
430 118         1059 _debug($debug.", poly(p)=$q1");
431              
432 118 100 100     546 if ($q1 == 0 || abs($p - $p1) <= $precision) {
433 12 50       30 if (!$self->_is_root($p)) {
434 0         0 $self->_exception(ERROR_NOT_A_ROOT,"secant() converges toward $p but that doesn't appear to be a root.",\%hash);
435             }
436 12         50 return $p;
437             }
438              
439 106         265 $p1 = $p;
440             }
441              
442 1         7 $self->_exception(ERROR_MAX_DEPTH,"reached maximum number of iterations [$max_depth] without getting close enough to the root in secant()",\%hash);
443             }
444              
445             #----------------------------------------------------------------
446             #
447             # brent - implement Brent's method to approximate the root of this polynomial
448             #
449              
450             sub brent {
451 32     32 1 12186 my($self,%hash) = @_;
452 32         39 my $precision = 0.1;
453 32         32 my $max_depth = 100;
454 32         31 my $mflag;
455 32         32 my($a,$b,$c,$s,$d);
456 0         0 my($f_a,$f_b,$f_c,$f_s);
457              
458 32         84 $self->iterations(0);
459 32         163 $self->error(NO_ERROR);
460              
461 32 100       151 $precision = $hash{precision} if (exists $hash{precision});
462 32 100       73 $max_depth = $hash{max_depth} if (exists $hash{max_depth});
463 32 100       67 $a = $hash{a} if (exists $hash{a});
464 32 100       80 $b = $hash{b} if (exists $hash{b});
465              
466 32 100       59 _error("brent() got undefined precision") if (!defined $precision);
467 31 50       53 _error("brent() got undefined max_depth") if (!defined $max_depth);
468 31 100       85 _error("brent() got non numeric precision") if (!looks_like_number($precision));
469 30 50       123 _error("brent() got non integer max_depth") if ($max_depth !~ /^\d+$/);
470 30 100       61 _error("brent() got undefined a") if (!defined $a);
471 29 100       44 _error("brent() got undefined b") if (!defined $b);
472 28 100       68 _error("brent() got non numeric a") if (!looks_like_number($a));
473 27 100       63 _error("brent() got non numeric b") if (!looks_like_number($b));
474 26 100       46 _error("brent() got same value for a and b") if ($a == $b);
475              
476 25         86 $self->_exception(ERROR_EMPTY_POLYNOM,"cannot find the root of an empty polynomial in brent()",\%hash)
477 25 100       24 if (scalar keys %{$self->{polynom}} == 0);
478              
479             # The following is an implementation of Brent's method as described on wikipedia
480             # variable names are chosen to match the pseudocode listed on wikipedia
481             # There are a few differences between this code and the pseudocode on wikipedia though...
482              
483 24         53 $f_a = $self->eval($a);
484 24         46 $f_b = $self->eval($b);
485              
486             # if the polynom evaluates to a complex number on $a or $b (ex: square root, when $a = -1)
487 24 50 33     552 $self->_exception(ERROR_NAN,"polynomial is not defined on interval [a=$a, b=$b] in brent()",\%hash)
488             if (float_is_nan($f_a) || float_is_nan($f_b));
489              
490             # did we hit the root by chance?
491 24 100       698 return $a if ($f_a == 0);
492 23 100       46 return $b if ($f_b == 0);
493              
494             # $a and $b should be chosen so that poly($a) and poly($b) have opposite signs.
495             # It is a prerequisite for the bisection part of Brent's method to work
496 21 100       56 $self->_exception(ERROR_WRONG_SIGNS,"polynomial does not have opposite signs at a=$a and b=$b in brent()",\%hash)
497             if ($f_a*$f_b > 0);
498              
499             # eventually swap $a and $b (don't forget to even switch f(c))
500 19 100       45 if (abs($f_a) < abs($f_b)) {
501 14         26 ($a,$b) = ($b,$a);
502 14         24 ($f_a,$f_b) = ($f_b,$f_a);
503             }
504              
505 19         22 $c = $a;
506 19         19 $f_c = $f_a;
507              
508 19         22 $mflag = 1;
509              
510             # repeat while we haven't found the root nor are close enough to it
511 19   100     103 while ($f_b != 0 && abs($b - $a) > $precision) {
512              
513             # did we reach the maximum number of iterations?
514 297 100       2866 $self->_exception(ERROR_MAX_DEPTH,"reached maximum number of iterations [$max_depth] without getting close enough to the root in brent()",\%hash)
515             if ($self->iterations > $max_depth);
516              
517             # evaluate f(a), f(b) and f(c) if necessary
518 296 100       1458 if ($self->iterations != 0) {
519 277         1211 $f_a = $self->eval($a);
520 277         498 $f_b = $self->eval($b);
521 277         460 $f_c = $self->eval($c);
522              
523 277 50       5057 $self->_exception(ERROR_NAN,"polynomial leads to an imaginary number on a=$a in brent()",\%hash) if (float_is_nan($f_a));
524 277 50       5930 $self->_exception(ERROR_NAN,"polynomial leads to an imaginary number on b=$b in brent()",\%hash) if (float_is_nan($f_b));
525 277 50       5953 $self->_exception(ERROR_NAN,"polynomial leads to an imaginary number on c=$c in brent()",\%hash) if (float_is_nan($f_c));
526             }
527              
528 296         1677 my $debug = "brent at depth ".$self->iterations.", a=$a, b=$b";
529              
530             # calculate the next root candidate
531 296 50 100     3285 if ($f_a == $f_b) {
    100          
532             # we should not be able to get $f_b == $f_a since it's a prerequisite of the method. that would be a bug
533 0         0 _error("BUG: got same values for polynomial at a=$a and b=$b:\n".$self->stringify);
534              
535             } elsif ( ($f_a != $f_c) && ($f_b != $f_c) ) {
536             # use quadratic interpolation
537 51         155 $s = ($a*$f_b*$f_c)/(($f_a - $f_b)*($f_a - $f_c)) +
538             ($b*$f_a*$f_c)/(($f_b - $f_a)*($f_b - $f_c)) +
539             ($c*$f_a*$f_b)/(($f_c - $f_a)*($f_c - $f_b));
540             } else {
541             # otherwise use the secant
542 245         438 $s = $b - $f_b*($b - $a)/($f_b - $f_a);
543             }
544              
545             # now comes the main difference between Brent's method and Dekker's method: we want to use bisection when appropriate
546 296 100 100     1845 if ( ( ($s < (3*$a+$b)/4) && ($s > $b) ) ||
      100        
      66        
      100        
      66        
547             ( $mflag && (abs($s-$b) >= (abs($b-$c)/2)) ) ||
548             ( !$mflag && (abs($s-$b) >= (abs($c-$d)/2)) ) ) {
549             # in that case, use the bisection to get $s
550 225         240 $s = ($a + $b)/2;
551 225         215 $mflag = 1;
552             } else {
553 71         80 $mflag = 0;
554             }
555              
556             # calculate f($s)
557 296         537 $f_s = $self->eval($s);
558              
559 296 50       5628 $self->_exception(ERROR_NAN,"polynomial leads to an imaginary number on s=$s in brent()",\%hash) if (float_is_nan($f_s));
560              
561 296         2843 _debug($debug.", s=$s, poly(s)=$f_s");
562              
563 296         366 $d = $c;
564 296         286 $c = $b;
565 296         263 $f_c = $f_b;
566              
567 296 100       565 if ($f_a*$f_s <= 0) {
568             # important that b=s if f(s)=0 since the while loop checks f(b)
569             # if f(a)=0, and f(b)!=0, then a and b will be swaped and we will therefore have f(b)=0
570 62         62 $b = $s;
571 62         66 $f_b = $f_s;
572             } else {
573 234         221 $a = $s;
574 234         256 $f_a = $f_s;
575             }
576              
577             # eventually swap $a and $b
578 296 100       627 if (abs($f_a) < abs($f_b)) {
579             # in the special case when
580 73         114 ($a,$b) = ($b,$a);
581 73         108 ($f_a,$f_b) = ($f_b,$f_a);
582             }
583              
584 296         654 $self->iterations($self->iterations + 1);
585             }
586              
587 18 50       196 if (!$self->_is_root($b)) {
588 0         0 $self->_exception(ERROR_NOT_A_ROOT,"brent() converges toward $b but that doesn't appear to be a root.",\%hash);
589             }
590              
591 18         77 return $b;
592             }
593              
594             1;
595              
596             __END__