Finding real roots of polynomials

real roots

This section deals with finding roots of polynomials in the field of real numbers.

Without loss of generality, the coefficients \(a_i\) of a polynomial

\[p = a_nx^n+\ldots+a_0\]

can be considered to be rational numbers, as real-valued numbers are truncated in practice, when doing calculations on a computer.

Assuming that the leading coefficient \(a_n=1\), the polynomial \(p\) can also be written as

\[p = p_1^{n_1}\ldots p_m^{n_m}\]

where \(p_i\) are the \(m\) distinct irreducible monic factors of the form \(p_i=x-x_i\), and \(n_i\) are multiplicities of the factors. Here the roots are \(x_i\) and some of them may be complex. However, complex roots of a polynomial with real coefficients always come in conjugate pairs, so the corresponding irreducible factors should be taken as \(p_i=x^2+c_ix+d_i\). In this case, there will be less than \(m\) irreducible factors, and all coefficients will be real.

square free decomposition

To find roots, it is useful to first remove the multiplicities, i.e. to convert the polynomial to one with multiplicity 1 for all irreducible factors, i.e. find the polynomial \(p_1\ldots p_m\). This is called the square-free part of the original polynomial \(p\).

The square-free part of the polynomial \(p\) can be easily found using the polynomial GCD algorithm. The derivative of a polynomial \(p\) can be written as:

\[p' = \sum_{i=1}^m p_1^{n_1}\ldots n_ip_i^{n_i-1}\frac{dp_i}{dx}\ldots p_m^{n_m}\]

The GCD of \(p\) and \(p'\) equals

\[\gcd(p,p') = p_1^{n_1-1}\ldots p_m^{n_m-1}\]

So if we divide \(p\) by \(\gcd(p,p')\), we get the square-free part of the polynomial:

\[\mathrm{SquareFree}(p) := \frac{p}{\gcd(p,p')} = p_1\ldots p_m.\]

In what follows we shall assume that all polynomials are square-free with rational coefficients. Given any polynomial, we can apply the functions SquareFree() and Rationalize() and reduce it to this form. The function Rationalize() converts all numbers in an expression to rational numbers. The function SquareFree() returns the square-free part of a polynomial. For example:

In> Expand((x+1.5)^5)
Out> x^5+7.5*x^4+22.5*x^3+33.75*x^2+25.3125*x +7.59375;
In> SquareFree(Rationalize(%))
Out> x/5+3/10;
In> Simplify(%*5)
Out> (2*x+3)/2;
In> Expand(%)
Out> x+3/2;

Sturm sequences

For a polynomial \(p(x)\) of degree \(n\), the Sturm sequence \(p_0, p_1,\dots,p_n\) is defined by the following equations (following [DST88]):

\begin{eqnarray} p_0 & = & p(x) \\ p_1 & = & p'(x) \\ p_i & = & -(p_{i-2} \bmod p_{i-1}). \end{eqnarray}

The polynomial \(p\) can be assumed to have no multiple factors, and thus \(p\) and \(p'\) are relatively prime. The sequence of polynomials in the Sturm sequence are (up to a minus sign) the consecutive polynomials generated by Euclid’s algorithm for the calculation of a greatest common divisor for \(p\) and \(p'\), so the last polynomial \(p_n\) will be a constant.

In yacas, the function SturmSequence() returns the Sturm sequence of \(p\), assuming \(p\) is a univariate polynomial in \(x\), \(p = p(x)\).

variations in Sturm sequences

Given a Sturm sequence \(S\) of a polynomial \(p\), the variation in the Sturm sequence \(V(S,y)\) is the number of sign changes in the sequence \(p_0,p_1,\ldots,p_n\), evaluated at point \(y\), and disregarding zeroes in the sequence.

Sturm’s theorem states that if \(a\) and \(b\) are two real numbers which are not roots of \(p\), and \(a < b\), then the number of roots between \(a\) and \(b\) is \(V(S,a) - V(S,b)\). A proof can be found in Knuth, <I>The Art of Computer Programming, Volume 2, Seminumerical Algorithms</I>.

For \(a\) and \(b\), the values \(-\infty\) and \(\infty\) can also be used. In these cases, \(V(S,\infty)\) is the number of sign changes between the leading coefficients of the elements of the Sturm sequence, and \(V(S,-\infty)\) the same, but with a minus sign for the leading coefficients for which the degree is odd.

Number of real roots

Thus, the number of real roots of a polynomial is \(V(S,-\infty) - V(S,\infty)\). The function NumRealRoots() returns the number of real roots of \(p\).

The function SturmVariations() returns the number of sign changes between the elements in the Sturm sequence \(S\), at point \(x = y\):

In> p:=x^2-1
Out> x^2-1;
In> S:=SturmSequence(p)
Out> {x^2-1,2*x,1};
In> SturmVariations(S,-Infinity)-SturmVariations(S,Infinity)
Out> 2;
In> NumRealRoots(p)
Out> 2;
In> p:=x^2+1
Out> x^2+1;
In> S:=SturmSequence(p)
Out> {x^2+1,2*x,-1};
In> SturmVariations(S,-Infinity)-SturmVariations(S,Infinity)
Out> 0;
In> NumRealRoots(p)
Out> 0;

Finding bounds on real roots

Armed with the variations in the Sturm sequence given in the previous section, we can now find the number of real roots in a range \((a,b)\), for \(a < b\). We can thus bound all the roots by subdividing ranges until there is only one root in each range. To be able to start this process, we first need some upper bounds of the roots, or an interval that contains all roots. Davenport gives limits on the roots of a polynomial given the coefficients of the polynomial, as

\[|a| \le \max\left(\left|\frac{a_{n-1}}{a_n}\right|,\left|\frac{a_{n-2}}{a_{n}}\right|^{\frac{1}{2}},\ldots,\left|\frac{a_{0}}{a_{n}}\right|^{\frac{1}{n}}\right)\]

for all real roots \(a\) of \(p\). This gives the upper bound on the absolute value of the roots of the polynomial in question. If \(p(0)\ne0\) the minimum bound can be obtained also by considering the upper bound of \(p(\frac{1}{x})x^n\), and taking \(\frac{1}{bound}\).

We thus know that given

\[a_{max} = \mathrm{MaximumBound}(p)\]


\[a_{min} = \mathrm{MinimumBound}(p)\]

for all roots \(a\) of polynomial, either

\[-a_{max}\le a\le -a_{min}\]


\[a_{min}\le a\le a_{max}\]

Now we can start the search for the bounds on all roots. The search starts with initial upper and lower bounds on ranges, subdividing ranges until a range contains only one root, and adding that range to the resulting list of bounds. If, when dividing a range, the middle of the range lands on a root, care must be taken, because the bounds should not be on a root themselves. This can be solved by observing that if \(c\) is a root, \(p\) contains a factor \(x-c\), and thus taking \(p(x+c)\) results in a polynomial with all the roots shifted by a constant \(-c\), and the root \(c\) moved to zero, e.g. \(p(x+c)\) contains a factor \(x\). Thus a new ranges to the left and right of \(c\) can be determined by first calculating the minimum bound \(M\) of \(\frac{p(x+c)}{x}\). When the original range was \((a,b)\), and \(c = \frac{a+b}{2}\) is a root, the new ranges should become \((a,c-M)\) and \((c+M,b)\).

In yacas, MimimumBound() returns the lower bound described above, and MaximumBound() returns the upper bound on the roots in \(p\). These bounds are returned as rational numbers. BoundRealRoots() returns a list with sublists with the bounds on the roots of a polynomial:

In> p:=(x+20)*(x+10)
Out> (x+20)*(x+10);
In> MinimumBound(p)
Out> 10/3;
In> MaximumBound(p)
Out> 60;
In> BoundRealRoots(p)
Out> {{-95/3,-35/2},{-35/2,-10/3}};
In> N(%)
Out> {{-31.6666666666,-17.5}, {-17.5,-3.3333333333}};

It should be noted that since all calculations are done with rational numbers, the algorithm for bounding the roots is very robust. This is important, as the roots can be very unstable for small variations in the coefficients of the polynomial in question (see Davenport).

Finding real roots given the bounds on the roots

Given the bounds on the real roots as determined in the previous section, two methods for finding roots are available: the secant method or the Newton method, where the function is locally approximated by a line, and extrapolated to find a new estimate for a root. This method converges quickly when “sufficiently” near a root, but can easily fail otherwise. The secant method can easily send the search to infinity.

The bisection method is more robust, but slower. It works by taking the middle of the range, and checking signs of the polynomial to select the half-range where the root is. As there is only one root in the range \((a,b)\), in general it will be true that \(p(a)p(b) < 0\), which is assumed by this method.

Yacas finds the roots by first trying the secant method, starting in the middle of the range, \(c = \frac{a+b}{2}\). If this fails the bisection method is tried.

The function call to find the real roots of a polynomial \(p\) in variable \(x\) is FindRealRoots(), for example:

In> p:=Expand((x+3.1)*(x-6.23))
Out> x^2-3.13*x-19.313;
In> FindRealRoots(p)
Out> {-3.1,6.23};
In> p:=Expand((x+3.1)^3*(x-6.23))
Out> x^4+3.07*x^3-29.109*x^2-149.8199\
In> *x-185.59793;
In> p:=SquareFree(Rationalize( \
In> Expand((x+3.1)^3*(x-6.23))))
Out> (-160000*x^2+500800*x+3090080)/2611467;
In> FindRealRoots(p)
Out> {-3.1,6.23};