View Raw SPL
/*****************************************************************************
*                                                                            *
*   RAT.SPL      Copyright (C) 2003 DSP Development Corporation              *
*                               All Rights Reserved                          *
*                                                                            *
*   Author:      Randy Race                                                  *
*                                                                            *
*   Synopsis:    Rational approximation using continued fraction expansion   *
*                                                                            *
*   Revisions:   17 Sep 2003  RRR  Creation - from PD source                 *
*                                                                            *
*****************************************************************************/

#include 

#if @HELP_RAT

    RAT

    Purpose: Calculates a rational approximation within a tolerance.

    Syntax:  RAT(x, tol)

             (n, d) = RAT(x, tol)

              x   - A scalar or series, the input value

              tol - Optional. A real, the tolerance of the approximation.
                    Defaults to 1.0e-6 * norm(x, 1).

    Returns: A series or string.

             (n, d) = RAT(x, tol) returns the numerator and denominator in
             separate variables as scalars or series.

    Example:
             rat(1.5)

             returns the string '3/2'

    Example:
             rat(pi)

             returns the string '104348/33215'

    Example:
             rat(pi, 1e-2)

             returns the string '22/7.'

    Example:
             (n, d) = rat(pi, 1e-2)

             n == 22, d == 7.

    Example:
             rat({pi, 1.5})

             returns the series: {{104348, 33215},
                                  {     3,     2}}

    Remarks:
             For series x, the first column of the result is the numerator
             value and the second column is the denominator.

             RAT uses the continued fraction expansion to produce the
             rational approximation of a value. This is the same result
             as SETFORMAT(7) with the default tolerance.

             The tolerance, tol, controls the result such that:

                 abs(n/d - x) <= abs(x) * tol

    See Also:
             Setformat
#endif


/* continued fraction rational approximation */
rat(x, tol)
{
        local y, n, d, frac, lastn, lastd, flip, step, nextn, nextd, len, str;
        local ymin, ymax;

        /* check input */
        if (argc < 2)
        {
                if (argc < 1) error("rat - input value required");

                /* non-inf max */
                (ymin, ymax) = yminmax({x});
                
                tol = 1.0e-6 * ymax;
        }

        /* convert input to 1 column series */
        y = unravel(castseries(x));

        /* number of values */
        len = length(y);

        /* initial approximation is integer part */
        n     = round(y);
        d     = ones(len, 1);
        frac  = y - n;
        lastn = ones(len, 1);
        lastd = zeros(len, 1);

        while (1)
        {
                /* check convergence */
                idx = find(abs(y - n / d) >= tol);
                
                if (isempty(idx)) break;

                /* next continued fraction */
                flip      = 1 / frac[idx];
                step      = round(flip);
                frac[idx] = flip - step;

                /* update values */
                nextn = n;
                nextd = d;

                n[idx] = n[idx] * step + lastn[idx];
                d[idx] = d[idx] * step + lastd[idx];

                lastn = nextn;
                lastd = nextd;
        }

        /* sign in numerator */
        n = n * sign(d);
        d = abs(d);

        if (isscalar(x))
        {
                /* convert back to scalar */
                if (iscomplex(x))
                {
                        n = castcomplex(n);
                        d = castcomplex(d);
                }
                else
                {
                        n = castreal(n);
                        d = castreal(d);
                }
        }
        else
        {
                /* reset to original rows & columns */
                n = ravel(n, numrows(x));
                d = ravel(d, numrows(x));
        }

        if (outargc < 2)
        {
                if (isscalar(x))
                {
                        /* return as string */
                        if (iscomplex(x))
                        {
                                str = sprintf("(%s)/(%s)", rat_format_cpx(n), rat_format_cpx(d));
                        }
                        else
                        {
                                str = sprintf("%.0f/%.0f", n, d);
                        }
                        return(str);
                }
                else
                {
                        /* combine numerator & denominator columns - return as series */
                        return(ravel(n, d));
                }
        }
        else
        {
                /* return in separate variables */
                return(n, d);
        }
}


/* stringify a complex */
rat_format_cpx(n)
{
        local sym, str;

        if (imag(n) < 0)
        {
                sym = "-";
        }
        else
        {
                sym = "+";
        }
        
        str = sprintf("%.0f %s %.0fi", real(n), sym, abs(imag(n)));
        
        return(str);
}