\chapter{Transcendental Functions} \begin{verbatim} $Log: Transcendentals.lhs,v $ Revision 1.1 2004/08/05 11:12:53 malcolm Add a regression testsuite for the nhc98 compiler. It isn't very good, but it is better than nothing. I've been using it for about four years on nightly builds, so it's about time it entered the repository! It includes a slightly altered version of the nofib suite. Instructions are in the README. Revision 1.2 1999/11/02 16:10:42 simonpj Haskell 98 changes Revision 1.1 1996/01/08 20:05:19 partain Initial revision \end{verbatim} > module Transcendentals > (module ContinuedFractions, > exp1, expQ, expR, log10, logQ, logR, tanQ, tanR, cotR, atan1, atanQ, atanR) > where > import ContinuedFractions \section{Basic Transcendental Functions} In this section we show how to use Gauss' continued fraction representation of $F(\alpha , 1, \gamma ; x)$ to implement the basic transcendental functions: $\exp$, $\log$, $\tan$, and $\arctan$. As a reminder, $F(\alpha , \beta , \gamma ; x)$ is the following series: \[1 + \frac{\alpha \beta}{1! \gamma}x + \frac{\alpha(\alpha +1) \beta (\beta +1)}{2! \gamma(\gamma +1)}x^2 + \frac{\alpha(\alpha +1)(\alpha +2) \beta (\beta +1)(\beta +2)}% {3! \gamma(\gamma +1)(\gamma +2)}x^3 + \ldots \] Modifying Gauss' formulation of the continued fraction we get that $F(\alpha , 1, \gamma ; x)$ is: \[ [0,~1,~\frac{1}{\alpha}(\frac{\gamma}{-x}), ~\frac{\alpha}{\gamma-\alpha}(\gamma+1),~ p_0(\frac{\gamma +2}{-x}),~p'_0(\gamma +3)~\ldots~ p_{n}(\frac{\gamma +2n+2}{-x}),~ p'_{n}(\gamma +2n+3),~ \ldots ] \] where \[p_n = \frac{1}{\alpha}\prod_{i=0}^n \frac{(\gamma - \alpha +i)(i+1)}% {(\alpha +i+1)(\gamma +i)}\] and \[p'_n = \frac{\alpha}{\gamma-\alpha} \prod_{i=0}^n \frac{(\alpha +i+1)(\gamma +i)}% {(\gamma - \alpha +i+1)(i+2)}\] This often simplifies. \subsection{Exponential} Gauss gives the following representation of $\exp(x)$: $\lim_{k\to\infty}F(k,1,1;\frac{x}{k})$. If we expand this we have the following continued fraction: \[ [0,~1,~1.(\frac{-1}{x}),-2,~3.(\frac{1}{x}),~\frac{4}{2},~ 5.(\frac{-1}{x}),~\frac{-6}{3},~\ldots~]\] We then observe that $\exp(x) = \frac{1}{\exp(-x)}$ getting: \[ [1,~\frac{1}{x},~-2,~\frac{-3}{x},~2,~\frac{5}{x},~-2,~\ldots ] \] Setting $x$ to $1$ we have the following continued fraction for $e$: > exp1CF :: [QRational] > exp1CF = 1: f 0 where f n = (4*n+1): (-2): (-(4*n+3)): 2: f (n+1) To use this continued fraction to generate the continued fraction for $\exp(x)$, where $x\in {\cal Q}$, we simply multiply the even terms of the continued fraction for $e$ by $\frac{1}{x}$ (treating the first term as an even one). The starting homography for the modified Algebraic Algorithm is generated by absorbing the first six terms of the continued fraction. We then multiply the resultant continued fraction by $x$. This is marginally more stable than Vuillemin's formulation. > expQ :: QRational -> ContinuedFraction > expQ x = aaQ hom ts2 > where (ts1,ts2) = splitAt 2 cf > hom = absorbQ startH ts1 > (startH,cf) = if abs x < 1 then > (([1,0],[0,1]), oddQ x exp1CF) else > (([qDenominator x,0],[0,qNumerator x]), > evenQ (1/x) exp1CF) > exp1 :: ContinuedFraction > exp1 = expQ (1%%1) We use an exactly similar process for the case of continued fraction arguments. > expR :: ContinuedFraction -> ContinuedFraction > expR x = if mightBeZero x > then processQS x (splitAt 6 (oddR exp1CF)) > else quadraticAlgorithm ([0,1,1,0],[0,1,0,0]) x cf2 > where cf2 = processQS x (splitAt 6 (oddR (tail exp1CF))) \subsection{Logarithm} Vuillemin's continued fraction is too unstable for values of $x-1$ which aren't near $0$. So instead we use Gauss' formula: \[\log(\frac{1+x}{1-x}) = 2xF(\frac{1}{2},1,\frac{3}{2}; x^2)\] It works because the transformation eliminates those values of $x$ in the above expansion that would cause problems, {\it e.g.} $|x| \geq 0$. The continued fraction for $F(\frac{1}{2},1,\frac{3}{2}; x^2)$ is closely related to that for $\arctan(x)$, because: \[\tanh^{-1}(x) = \frac{1}{2} \log(\frac{1+x}{1-x})\] and \[\imath \tanh(x) = \tan(\imath x).\] > logQ :: QRational -> ContinuedFraction > logQ x = aaQ startH ts2 > where (ts1,ts2) = splitAt 2 (oddQ (-t*t) atan1CF) > t = (x-1)/(x+1) > tn = qNumerator t > td = qDenominator t > startH = absorbQ ([-2*td,0],[0,tn]) ts1 > log10 :: ContinuedFraction > log10 = logQ (10%%1) The nice part is that by requiring the multiplier @-t*t@ to be less than @1@, we place no constraints on @x@ provided that it is already in the range $(0,\infty )$. > logR :: ContinuedFraction -> ContinuedFraction > logR x = quadraticAlgorithm ([2,0,0,0],[0,0,0,1]) t cf1 > where t = algebraicAlgorithm ([1,(-1)],[1,1]) x > t2 = quadraticAlgorithm ([-1,0,0,0],[0,0,0,1]) t t > cf = qsSetupAlt (drop 1 (oddR atan1CF)) t2 > cf1 = quadraticAlgorithm ([0,1,0,0],[0,1,1,0]) t2 cf \subsection{Tangent} For this function we use Lambert's Continued Fraction \[\tan(x) = [0,~\frac{1}{x},~\frac{-3}{x},~\frac{5}{x},~\frac{-7}{x},~\ldots]\] It is much more convenient if we can obtain a fraction with some constant terms in it so we observe the following transformation is possible: \[\tan(x) = \frac{1}{x}[0,~\frac{1}{x^2},~-3,~\frac{5}{x^2},~-7,~\ldots]\] provided that $x$ is non-zero. > tan1CF :: [QRational] > tan1CF = 0: f 0 where f n = (4*n+1) : (-(4*n+3)) : f (n+1) > tanQ :: QRational -> ContinuedFraction > tanQ x = if x /= 0 then aaQ startH ts2 else error "tanQ: 0 argument" > where n = qRound (abs (x/2)) + 1 > (ts1,ts2) = splitAt (fromIntegral n) (bothQ x tan1CF) > startH = absorbQ ([1,0],[0,1]) ts1 > tanR :: ContinuedFraction -> ContinuedFraction > tanR x = quadraticAlgorithm ([0,1,0,0],[0,0,1,0]) cfd cfn > where cf = qsSetupAlt (drop 1 (oddR tan1CF)) x2 > x2 = quadraticAlgorithm ([1,0,0,0],[0,0,0,1]) x x > cfn = quadraticAlgorithm ([1,0,0,0],[0,0,0,1]) x cf > cfd = quadraticAlgorithm ([0,1,1,0],[0,0,0,1]) x2 cf > cotR :: ContinuedFraction -> ContinuedFraction > cotR x = quadraticAlgorithm ([0,1,0,0],[0,0,1,0]) cfn cfd > where cf = qsSetupAlt (drop 1 (oddR tan1CF)) x2 > x2 = quadraticAlgorithm ([1,0,0,0],[0,0,0,1]) x x > cfn = quadraticAlgorithm ([1,0,0,0],[0,0,0,1]) x cf > cfd = quadraticAlgorithm ([0,1,1,0],[0,0,0,1]) x2 cf \subsection{Arctangent} Recall, from the discussion of the continued fraction for $\log$, that the $\arctan(x)$ is $xF(\frac{1}{2},1,\frac{3}{2};-x^2)$ The terms in this continued fraction are tending towards $\pi$ and $1$. > atan1CF :: [QRational] > atan1CF = 0: 1: 3: 5/4: f (8/9) 0 > where f :: QRational -> QRational -> [QRational] > f p n = (7/2 + 2*n)*p: (9/2 + 2*n)*p': f p'' (n+1) > where p' = 1/(p*(n+2)*(n+2)) > p'' = 1/(p'*(n+5/2)*(n+5/2)) The @atanQ@ function is valid for $x \in (-\infty,\,\infty)$. We note the following identities: \[\mbox{for $x>0$ } \arctan(x) = \frac{\pi}{2} -\arctan(\frac{1}{x})\] and \[\mbox{for $x<0$ } \arctan(x) = -\frac{\pi}{2} -\arctan(\frac{1}{x})\] > atanQ :: QRational -> ContinuedFraction > atanQ x > = if x > 1 then > quadraticAlgorithm ([0,-1,2,0] ,[0,0,0,1]) atan1 (cf (1/x)) else > if x < -1 then > quadraticAlgorithm ([0,-1,-2,0],[0,0,0,1]) atan1 (cf (1/x)) else > cf x > where > cf x = aaQ startH ts2 > where > (ts1,ts2) = splitAt 4 (oddQ (x*x) atan1CF) > startH = absorbQ ([qDenominator x,0],[0,qNumerator x]) ts1 Because we will be using it to help improve the stability of the $arctan$ function, we pre-define the constant continued fraction $\pi/4$. > atan1 :: ContinuedFraction > atan1 = atanQ (1%%1) > atanR :: ContinuedFraction -> ContinuedFraction > atanR [] = algebraicAlgorithm ([2,0],[0,1]) atan1 > atanR xs@(x:xs') > = if x > 1 then > quadraticAlgorithm ([0,-1,2,0] ,[0,0,0,1]) atan1 ys else > if x < -1 then > quadraticAlgorithm ([0,-1,-2,0],[0,0,0,1]) atan1 ys else > atanRdirect xs > where ys = atanRdirect (algebraicAlgorithm ([0,1],[1,0]) xs) > atanRdirect :: ContinuedFraction -> ContinuedFraction > atanRdirect x > = quadraticAlgorithm ([0,0,1,0],[0,1,0,0]) cfn cfd > where x2 = quadraticAlgorithm ([1,0,0,0],[0,0,0,1]) x x > cf = qsSetupAlt (drop 1 (oddR atan1CF)) x2 > cfn = quadraticAlgorithm ([1,0,0,0],[0,0,0,1]) x cf > cfd = quadraticAlgorithm ([0,1,1,0],[0,0,0,1]) x2 cf \subsection{Tests on CFs} We use @mightBeZero@ to test whether a continued fraction can be zero. > mightBeZero :: ContinuedFraction -> Bool > mightBeZero [] = False > mightBeZero (x:xs) = x == 0 \section{Processing Rational Transcendentals} In this section we deal with the case of transcendental functions of rational arguments. We first define a variant of Gosper's Algenraic Algorithm that takes a homography and a continued fraction with rational terms, and returns an equivalent continued fraction with integer terms. > aaQ :: Homography -> [QRational] -> ContinuedFraction > aaQ homography@([n0,n1],[d0,d1]) xs > = if n0*d1 /= n1*d0 then aaQNoCheck homography xs > else if d1 /= 0 then rat2cf (n1%%d1) > else if d0 /= 0 then rat2cf (n0%%d0) > else error "aaQ: undefined homography" As we did with the original formulation, we check the determinant of the homography only once. > aaQNoCheck homography@([n0,n1],[d0,d1]) [] > = rat2cf (n0%%d0) > aaQNoCheck homography@([n0,n1],[d0,d1]) xs'@(x:xs) > = case algebraicOutput homography x of > Just o -> o : aaQNoCheck ([d0,d1], [n0-d0*o,n1-d1*o]) xs' > Nothing -> aaQNoCheck ([n0*n+n1*d,n0*d], [d0*n+d1*d,d0*d]) xs > where (n,d) = (qNumerator x, qDenominator x) To absorb a finite sequence of terms from a rational continued fraction we use @absorbQ@. > absorbQ :: Homography -> [QRational] -> Homography > absorbQ homography [] = homography > absorbQ ([n0,n1],[d0,d1]) (q:qs) = absorbQ ([n0*n+n1*d,n0*d], > [d0*n+d1*d,d0*d]) qs > where n = qNumerator q > d = qDenominator q To set up the continued fraction with the argument we use @bothQ@, @oddQ@ and @evenQ@. > bothQ :: QRational -> [QRational] -> [QRational] > bothQ x (x1:xs) = x1/x: bothQ x xs > evenQ :: QRational -> [QRational] -> [QRational] > evenQ x (x1:x2:xs) = x1/x: x2: evenQ x xs > oddQ :: QRational -> [QRational] -> [QRational] > oddQ x (x1:x2:xs) = x1: x2/x: oddQ x xs \section{Processing Irrational Transcendentals} To produce continued fractions for the transcendental functions with continued fraction arguments we use an ``infinite state machine''. This infinite state is an infinite list of sub-states, each consisting of a rational, and a homography for the Quadratic Algorithm. > type State = (QRational, Homography) To set up the initial continued fraction for the ``infinite state machine'' from the initial continued fraction we use @oddR@. If the initial continued fraction is \[[\frac{a_0}{b_0},~\frac{a_1}{b_1},~\frac{a_2}{b_2},~\frac{a_3}{b_3},~\ldots]\] then the ``infinite state'' will be: \[[\left(\frac{a_0}{b_0}, \left(\begin{array}[c]{cccc}0&a_1&b_1&0\\b_1&0&0&0\end{array}\right),~ \right) \left(\frac{a_2}{b_2}, \left(\begin{array}[c]{cccc}0&a_3&b_3&0\\b_3&0&0&0\end{array}\right) \right),~ \ldots]\] > oddR :: [QRational] -> [State] > oddR (x1:x2:xs) = (x1,([0,xn,xd,0],[xd,0,0,0])): oddR xs > where (xn,xd) = (qNumerator x2, qDenominator x2) The processing of the infinite state and the continued fraction is initiated by @processQS@. > processQS :: ContinuedFraction -> ([State],[State]) -> ContinuedFraction > processQS x (hs1,hs2) = preProcess x hs1 (qsSetupAlt hs2 x) This in turn makes use of @preProcess@ which is used to absorb some of the leading terms (@hs1@) and @qsSetupAlt@ which is used to process the remainder (@hs2@). > preProcess :: ContinuedFraction -> -- the argument CF > [State] -> -- leading parts of infinite state > ContinuedFraction -> -- accumulating CF > ContinuedFraction -- the resulting CF > preProcess _ [] c = c > preProcess x ((i,h):hs) c = algebraicAlgorithm ([ni,nd],[nd,0]) > (quadraticAlgorithm h x (preProcess x hs c)) > where ni = qNumerator i > nd = qDenominator i To process the remainder of the infinite state, we use @qsSetupAlt@. > qsSetupAlt :: [State] -> ContinuedFraction -> ContinuedFraction > qsSetupAlt hs x = aaQ ([1,0],[0,1]) (qsProcess (hs, x)) \subsection{Quadratic Process} In essence the state for the algorithm holds: \begin{enumerate} \item the infinite list of @Rational@/@Homography@ pairs; and \item the infinite continued fraction of the argument. \end{enumerate} We advance by absorbing one term from the continued fraction or by considering one more term of the infinite state. > qsProcess :: ([State], ContinuedFraction) -> [QRational] > qsProcess (ihs, []) = asProcess ihs > qsProcess st@(ihs, (_:_)) = qsNext 1 st The decision about whether sufficient accuracy has been achieved is decided by @qsNext@. To do this a call to @qsTerm@ is made. This returns a list of output values and a modified state (@ihs'@). If the output list is sufficiently long we can output them as part of the resulting continued fraction. Alternatively we can absorb another term of the argument continued fraction. In either case we consider one more term of the infinite state. > qsNext :: Int -> ([State], ContinuedFraction) -> [QRational] > qsNext n (ihs, []) = asProcess (qsEnd ihs) > qsNext n st@(((i,h):ihs), xs'@(x:xs)) > = if length outs > 1 > then init outs ++ qsNext (n+1) (ihs',xs') > else qsNext (n+1) (qsIn x ((i,h): ihs), xs) > where (outs, (ihs',_)) = qsTerm n st The @qsTerm@ function considers the initial @n@ terms of the infinite state, and the first term of the continued fraction for the argument. From this it calculates a modified infinite state, in which, what is effectively carry propogation has occurred. > qsTerm :: Int -> ([State], ContinuedFraction) > -> ([QRational],([State], ContinuedFraction)) > qsTerm n (ihs,xs) > = (outs, (ihs',xs)) > where (front,back@((i,_):_)) = splitAt n ihs > (outs,ihs') = foldr (qsTerm' (head xs)) ([i],back) front To deal with each individual term of the infinite state we use @qsTerm'@. > qsTerm' :: Integer -> State -> ([QRational], [State]) -> > ([QRational], [State]) > qsTerm' x ih@(i,h) (is,ihs) > = (is',((last is',h''):ihs)) > where h' = qsAbsorb (init is) h > i' = last is > (is',h'') = qsEmit x i' ([i],h') This in turn uses @qsAbsorb@ and @qsEmit@ to propogate the carry and emit output terms, respectively. > qsAbsorb :: [QRational] -> Homography -> Homography > qsAbsorb [] h = h > qsAbsorb (i:is) h@([n0,n1,n2,n3],[d0,d1,d2,d3]) > = qsAbsorb is ([n0*n+n2*d, n1*n+n3*d, n0*d, n1*d], > [d0*n+d2*d, d1*n+d3*d, d0*d, d1*d]) > where n = qNumerator i > d = qDenominator i @qsEmit@ is essentially a variant of the Quadratic Algorithm, capable of dealing with the output of more than $1$ term at once. > qsEmit :: Integer -> QRational -> ([QRational], Homography) -> > ([QRational], Homography) > qsEmit x i ish@(is,h@([n0,n1,n2,n3],[d0,d1,d2,d3])) > = case quadraticOutput h [x%%1,i] of > Just o -> qsEmit x i (is++[o%%1], > ([d0,d1,d2,d3], > [n0-d0*o, n1-d1*o, n2-d2*o, n3-d3*o])) > Nothing -> ish To absorb the leading term of the argument continued fraction into each Quadratic Homography we use @qsIn@. > qsIn :: Integer -> [State] -> [State] > qsIn = map . input > where input x (i,([n0,n1,n2,n3],[d0,d1,d2,d3])) > = (i, ([n0*x+n1, n0, n2*x+n3, n2], > [d0*x+d1, d0, d2*x+d3, d2])) \subsection{Finite Continued Fractions} It is possible to generate finite continued fractions, so we need a way to deal with @[]@ in the @qsProcess@. This results in a degeneration to the algebraic process. @qsEnd@ turns each Quadratic Homography into the equivalent Algebraic Homography. > qsEnd :: [State] -> [State] > qsEnd = map input > where input (i,([n0,_,n2,_],[d0,_,d2,_])) > = (i,([n0,n2],[d0,d2])) The function @asProcess@ munges these homographies to produce an answer. > asProcess :: [State] -> [QRational] > asProcess ihs = fst (head ihs) : asProcess (asNext ihs) > asNext :: [State] -> [State] > asNext ((i,h@([n0,n1],[d0,d1])):ihs'@((i',_):ihs)) > = case algebraicOutput h i' of > Just o -> (o%%1, ([d0,d1], [n0-d0*o,n1-d1*o])): ihs' > Nothing -> asNext ((i,hIn i' h): asNext ihs') > where hIn x ([n0,n1],[d0,d1]) = ([n0*xn+n1*xd,n0*xd],[d0*xn+d1*xd,d0*xd]) > where xn = qNumerator x > xd = qDenominator x And that completes the implementation of the specified subset of transcendental functions.