mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-05 16:31:15 -05:00
345 lines
11 KiB
C++
345 lines
11 KiB
C++
/*
|
|
By Daniel Klostermann
|
|
Iowa Hills Software, LLC IowaHills.com
|
|
If you find a problem, please leave a note at:
|
|
http://www.iowahills.com/feedbackcomments.html
|
|
Sept 12, 2016 Rev H
|
|
|
|
This root solver code finds 1st, 2nd, 3rd, and 4th order roots algebraically, as
|
|
opposed to iteration.
|
|
|
|
It is composed of the functions: QuadCubicRoots, QuadRoots, CubicRoots, and BiQuadRoots.
|
|
This code originated at: http://www.netlib.org/toms/ Algorithm 326
|
|
Roots of Low Order Polynomials by Terence R.F.Nonweiler CACM (Apr 1968) p269
|
|
Original C translation by: M.Dow Australian National University, Canberra, Australia
|
|
|
|
We use the same basic mathematics used in that code, but in order to improve numerical
|
|
accuracy we made extensive modifications to the test conditions that control the flow of
|
|
the algorithm, and also added scaling and reversals.
|
|
|
|
The input array Coeff[] contains the coefficients arranged in descending order.
|
|
For example, a 4th order poly would be:
|
|
P(x) = Coeff[0]*x^4 + Coeff[1]*x^3 + Coeff[2]*x^2 + Coeff[3]*x + Coeff[4]
|
|
|
|
The roots are returned in RootsReal and RootsImag. N is the polynomial's order. 1 <= N <= 4
|
|
N is modified if there are leading or trailing zeros. N is returned.
|
|
Coeff needs to be length N+1. RealRoot and ImagRoot need to be length N.
|
|
|
|
Do not call QuadRoots, CubicRoots, and BiQuadRoots directly. They assume that QuadCubicRoots
|
|
has removed leading and trailing zeros and normalized P.
|
|
|
|
On a 2 GHz Pentium it takes about 2 micro sec for QuadCubicRoots to return.
|
|
|
|
ShowMessage is a C++ Builder function, and it usage has been commented out.
|
|
If you are using C++ Builder, include vcl.h for ShowMessage.
|
|
Otherwise replace ShowMessage with something appropriate for your compiler.
|
|
*/
|
|
|
|
#include "QuadRootsRevH.h"
|
|
#include <math.h>
|
|
|
|
namespace kitiirfir
|
|
{
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// Same interface as P51.
|
|
int QuadCubicRoots(long double *Coeff, int N, long double *RealRoot,
|
|
long double *ImagRoot) {
|
|
if (N < 1 || N > 4) {
|
|
//ShowMessage("Invalid Poly Order in QuadCubicRoots()");
|
|
return (0);
|
|
}
|
|
|
|
int j;
|
|
long double P[5];
|
|
|
|
// Must init to zero, in case N is reduced.
|
|
for (j = 0; j < N; j++)
|
|
RealRoot[j] = ImagRoot[j] = 0.0;
|
|
for (j = 0; j < 5; j++)
|
|
P[j] = 0.0;
|
|
|
|
// The functions below modify the coeff array, so we pass P instead of Coeff.
|
|
for (j = 0; j <= N; j++)
|
|
P[j] = (long double) Coeff[j];
|
|
|
|
// Remove trailing zeros. A tiny P[N] relative to P[N-1] is not allowed.
|
|
while (N > 0 && fabsl(P[N]) <= TINY_VALUE * fabsl(P[N - 1])) {
|
|
N--;
|
|
}
|
|
|
|
// Remove leading zeros.
|
|
while (N > 0 && P[0] == 0.0) {
|
|
for (j = 0; j < N; j++)
|
|
P[j] = P[j + 1];
|
|
N--;
|
|
}
|
|
|
|
// P[0] must = 1
|
|
if (P[0] != 1.0) {
|
|
for (j = 1; j <= N; j++)
|
|
P[j] /= P[0];
|
|
P[0] = 1.0;
|
|
}
|
|
|
|
// Calculate the roots.
|
|
if (N == 4)
|
|
BiQuadRoots(P, RealRoot, ImagRoot);
|
|
else if (N == 3)
|
|
CubicRoots(P, RealRoot, ImagRoot);
|
|
else if (N == 2)
|
|
QuadRoots(P, RealRoot, ImagRoot);
|
|
else if (N == 1) {
|
|
RealRoot[0] = -P[1] / P[0];
|
|
ImagRoot[0] = 0.0;
|
|
}
|
|
|
|
return (N);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// This function is the quadratic formula with P[0] = 1
|
|
// y = P[0]*x^2 + P[1]*x + P[2]
|
|
// Normally, we don't allow P[2] = 0, but this can happen in a call from BiQuadRoots.
|
|
// If P[2] = 0, the zero is returned in RealRoot[0].
|
|
void QuadRoots(long double *P, long double *RealRoot, long double *ImagRoot) {
|
|
long double D;
|
|
D = P[1] * P[1] - 4.0 * P[2];
|
|
if (D >= 0.0) // 1 or 2 real roots
|
|
{
|
|
RealRoot[0] = (-P[1] - sqrtl(D)) * 0.5; // = -P[1] if P[2] = 0
|
|
RealRoot[1] = (-P[1] + sqrtl(D)) * 0.5; // = 0 if P[2] = 0
|
|
ImagRoot[0] = ImagRoot[1] = 0.0;
|
|
} else // 2 complex roots
|
|
{
|
|
RealRoot[0] = RealRoot[1] = -P[1] * 0.5;
|
|
ImagRoot[0] = sqrtl(-D) * 0.5;
|
|
ImagRoot[1] = -ImagRoot[0];
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// This finds the roots of y = P0x^3 + P1x^2 + P2x+ P3 P[0] = 1
|
|
void CubicRoots(long double *P, long double *RealRoot, long double *ImagRoot) {
|
|
int j;
|
|
long double s, t, b, c, d, Scalar;
|
|
bool CubicPolyReversed = false;
|
|
|
|
// Scale the polynomial so that P[N] = +/-1. This moves the roots toward unit circle.
|
|
Scalar = powl(fabsl(P[3]), 1.0 / 3.0);
|
|
for (j = 1; j <= 3; j++)
|
|
P[j] /= powl(Scalar, (long double) j);
|
|
|
|
if (fabsl(P[3]) < fabsl(P[2]) && P[2] > 0.0) {
|
|
ReversePoly(P, 3);
|
|
CubicPolyReversed = true;
|
|
}
|
|
|
|
s = P[1] / 3.0;
|
|
b = (6.0 * P[1] * P[1] * P[1] - 27.0 * P[1] * P[2] + 81.0 * P[3]) / 162.0;
|
|
t = (P[1] * P[1] - 3.0 * P[2]) / 9.0;
|
|
c = t * t * t;
|
|
d = 2.0 * P[1] * P[1] * P[1] - 9.0 * P[1] * P[2] + 27.0 * P[3];
|
|
d = d * d / 2916.0 - c;
|
|
|
|
// if(d > 0) 1 complex and 1 real root. We use LDBL_EPSILON to account for round off err.
|
|
if (d > LDBL_EPSILON) {
|
|
d = powl((sqrtl(d) + fabsl(b)), 1.0 / 3.0);
|
|
if (d != 0.0) {
|
|
if (b > 0)
|
|
b = -d;
|
|
else
|
|
b = d;
|
|
c = t / b;
|
|
}
|
|
d = M_SQRT3_2 * (b - c);
|
|
b = b + c;
|
|
c = -b / 2.0 - s;
|
|
|
|
RealRoot[0] = (b - s);
|
|
ImagRoot[0] = 0.0;
|
|
RealRoot[1] = RealRoot[2] = c;
|
|
ImagRoot[1] = d;
|
|
ImagRoot[2] = -ImagRoot[1];
|
|
}
|
|
|
|
else // d < 0.0 3 real roots
|
|
{
|
|
if (b == 0.0)
|
|
d = M_PI_2 / 3.0; // b can be as small as 1.0E-25
|
|
else
|
|
d = atanl(sqrtl(fabsl(d)) / fabsl(b)) / 3.0;
|
|
|
|
if (b < 0.0)
|
|
b = 2.0 * sqrtl(fabsl(t));
|
|
else
|
|
b = -2.0 * sqrtl(fabsl(t));
|
|
|
|
c = cosl(d) * b;
|
|
t = -M_SQRT3_2 * sinl(d) * b - 0.5 * c;
|
|
|
|
RealRoot[0] = (t - s);
|
|
RealRoot[1] = -(t + c + s);
|
|
RealRoot[2] = (c - s);
|
|
ImagRoot[0] = 0.0;
|
|
ImagRoot[1] = 0.0;
|
|
ImagRoot[2] = 0.0;
|
|
}
|
|
|
|
// If we reversed the poly, the roots need to be inverted.
|
|
if (CubicPolyReversed)
|
|
InvertRoots(3, RealRoot, ImagRoot);
|
|
|
|
// Apply the Scalar to the roots.
|
|
for (j = 0; j < 3; j++)
|
|
RealRoot[j] *= Scalar;
|
|
for (j = 0; j < 3; j++)
|
|
ImagRoot[j] *= Scalar;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// This finds the roots of y = P0x^4 + P1x^3 + P2x^2 + P3x + P4 P[0] = 1
|
|
// This function calls CubicRoots and QuadRoots
|
|
void BiQuadRoots(long double *P, long double *RealRoot, long double *ImagRoot) {
|
|
int j;
|
|
long double a, b, c, d, e, Q3Limit, Scalar, Q[4], MinRoot;
|
|
bool QuadPolyReversed = false;
|
|
|
|
// Scale the polynomial so that P[N] = +/- 1. This moves the roots toward unit circle.
|
|
Scalar = powl(fabsl(P[4]), 0.25);
|
|
for (j = 1; j <= 4; j++)
|
|
P[j] /= powl(Scalar, (long double) j);
|
|
|
|
// Having P[1] < P[3] helps with the Q[3] calculation and test.
|
|
if (fabsl(P[1]) > fabsl(P[3])) {
|
|
ReversePoly(P, 4);
|
|
QuadPolyReversed = true;
|
|
}
|
|
|
|
a = P[2] - P[1] * P[1] * 0.375;
|
|
b = P[3] + P[1] * P[1] * P[1] * 0.125 - P[1] * P[2] * 0.5;
|
|
c = P[4] + 0.0625 * P[1] * P[1] * P[2]
|
|
- 0.01171875 * P[1] * P[1] * P[1] * P[1] - 0.25 * P[1] * P[3];
|
|
e = P[1] * 0.25;
|
|
|
|
Q[0] = 1.0;
|
|
Q[1] = P[2] * 0.5 - P[1] * P[1] * 0.1875;
|
|
Q[2] = (P[2] * P[2] - P[1] * P[1] * P[2]
|
|
+ 0.1875 * P[1] * P[1] * P[1] * P[1] - 4.0 * P[4] + P[1] * P[3])
|
|
* 0.0625;
|
|
Q[3] = -b * b * 0.015625;
|
|
|
|
/* The value of Q[3] can cause problems when it should have calculated to zero (just above) but
|
|
is instead ~ -1.0E-17 because of round off errors. Consequently, we need to determine whether
|
|
a tiny Q[3] is due to roundoff, or if it is legitimately small. It can legitimately have values
|
|
of ~ -1E-28. When this happens, we assume Q[2] should also be small. Q[3] can also be tiny with
|
|
2 sets of equal real roots. Then P[1] and P[3], are approx equal. */
|
|
|
|
Q3Limit = ZERO_MINUS;
|
|
if (fabsl(fabsl(P[1]) - fabsl(P[3])) >= ZERO_PLUS && Q[3] > ZERO_MINUS
|
|
&& fabsl(Q[2]) < 1.0E-5)
|
|
Q3Limit = 0.0;
|
|
|
|
if (Q[3] < Q3Limit && fabsl(Q[2]) < 1.0E20 * fabsl(Q[3])) {
|
|
CubicRoots(Q, RealRoot, ImagRoot);
|
|
|
|
// Find the smallest positive real root. One of the real roots is always positive.
|
|
MinRoot = 1.0E100;
|
|
for (j = 0; j < 3; j++) {
|
|
if (ImagRoot[j] == 0.0 && RealRoot[j] > 0 && RealRoot[j] < MinRoot)
|
|
MinRoot = RealRoot[j];
|
|
}
|
|
|
|
d = 4.0 * MinRoot;
|
|
a += d;
|
|
if (a * b < 0.0)
|
|
Q[1] = -sqrtl(d);
|
|
else
|
|
Q[1] = sqrtl(d);
|
|
b = 0.5 * (a + b / Q[1]);
|
|
} else {
|
|
if (Q[2] < 0.0) // 2 sets of equal imag roots
|
|
{
|
|
b = sqrtl(fabsl(c));
|
|
d = b + b - a;
|
|
if (d > 0.0)
|
|
Q[1] = sqrtl(fabsl(d));
|
|
else
|
|
Q[1] = 0.0;
|
|
} else {
|
|
if (Q[1] > 0.0)
|
|
b = 2.0 * sqrtl(fabsl(Q[2])) + Q[1];
|
|
else
|
|
b = -2.0 * sqrtl(fabsl(Q[2])) + Q[1];
|
|
Q[1] = 0.0;
|
|
}
|
|
}
|
|
|
|
// Calc the roots from two 2nd order polys and subtract e from the real part.
|
|
if (fabsl(b) > 1.0E-8) {
|
|
Q[2] = c / b;
|
|
QuadRoots(Q, RealRoot, ImagRoot);
|
|
|
|
Q[1] = -Q[1];
|
|
Q[2] = b;
|
|
QuadRoots(Q, RealRoot + 2, ImagRoot + 2);
|
|
|
|
for (j = 0; j < 4; j++)
|
|
RealRoot[j] -= e;
|
|
} else // b==0 with 4 equal real roots
|
|
{
|
|
for (j = 0; j < 4; j++)
|
|
RealRoot[j] = -e;
|
|
for (j = 0; j < 4; j++)
|
|
ImagRoot[j] = 0.0;
|
|
}
|
|
|
|
// If we reversed the poly, the roots need to be inverted.
|
|
if (QuadPolyReversed)
|
|
InvertRoots(4, RealRoot, ImagRoot);
|
|
|
|
// Apply the Scalar to the roots.
|
|
for (j = 0; j < 4; j++)
|
|
RealRoot[j] *= Scalar;
|
|
for (j = 0; j < 4; j++)
|
|
ImagRoot[j] *= Scalar;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// A reversed polynomial has its roots at the same angle, but reflected about the unit circle.
|
|
void ReversePoly(long double *P, int N) {
|
|
int j;
|
|
long double Temp;
|
|
for (j = 0; j <= N / 2; j++) {
|
|
Temp = P[j];
|
|
P[j] = P[N - j];
|
|
P[N - j] = Temp;
|
|
}
|
|
if (P[0] != 0.0) {
|
|
for (j = N; j >= 0; j--)
|
|
P[j] /= P[0];
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// This is used in conjunction with ReversePoly
|
|
void InvertRoots(int N, long double *RealRoot, long double *ImagRoot) {
|
|
int j;
|
|
long double Mag;
|
|
for (j = 0; j < N; j++) {
|
|
// complex math for 1/x
|
|
Mag = RealRoot[j] * RealRoot[j] + ImagRoot[j] * ImagRoot[j];
|
|
if (Mag != 0.0) {
|
|
RealRoot[j] /= Mag;
|
|
ImagRoot[j] /= -Mag;
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
} // namespace
|