I’ve been dabbling with haskell in the recent days. My first semi-serious attempt to some code was inspired by a prompt at programming praxis.
What I concocted should ashame any veteran haskell programmer (if you are and have stumbled on this by accident feel free to point me in the right direction;-)):
import System.Environment
taylor :: Double -> Double -> Double
taylor x tol = reduce1 (x - fromIntegral(truncate(x / (2*pi)))*(2*pi)) tol
reduce1 :: Double-> Double -> Double
reduce1 x tol
| x < 0 = (-reduce2 (-x) tol)
| otherwise = reduce2 x tol
reduce2 :: Double -> Double -> Double
reduce2 x tol
| x <= pi/2 = sumTaylor x tol
| x <= pi = sumTaylor (pi - x) tol
| x <= 3*pi/2 = (-sumTaylor (x - pi) tol)
| otherwise = (-sumTaylor (2*pi - x) tol)
sumTaylor :: Double -> Double -> Double
sumTaylor x tol = checkdelta (scanl sumOdd (0,1,1,-1) [1..]) tol
where sumOdd (p,n,d,s) el
| even el = (p-s*n/d,n*x,d*fromIntegral el,-s)
| otherwise = (p,n*x,d*fromIntegral el,s)
checkdelta :: [(Double,Double,Double,Double)] -> Double -> Double
checkdelta (a:b:c:xs) tol = if abs ( first4 a - first4 c ) < tol then first4 c else checkdelta (c:xs) tol
checkdelta [] _ = 0
first4 :: (Double,Double,Double,Double) -> Double
first4 (a,b,c,d) = a
main :: IO ()
main = do
(x:tol:[]) <- getArgs
print $ taylor (read x) (read tol)
I was curious to see how this would fare against a C equivalent, so I quickly typed the following:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define max_iter 100
/*
* Compute the taylor sum x-x**3/3!+x**5/5!+ ...
* Stop if the difference between 2 consecutive terms is minor than a given
* tolerance or if it doesn't converge after 100 iterations.
*
*/
double sum(const double x, const double tol) {
double y, old_y, num, den, sign;
int i;
y = 0.0;
num = 1.0;
den = 1.0;
sign = 1.0;
old_y = 100.0;
for ( i=1; i<max_iter; i++ ) {
if (!(i & 1)) {
y += sign*num/den;
sign = -sign;
if(fabs(y-old_y)<tol) return y;
old_y = y;
}
num *= x;
den *= (double)i;
}
return NAN;
}
int main(int argc, char **argv) {
double x, tol, pi, y;
int i, sign = 0;
if (argc<2) {
printf("Requires at least a numeric argument\n");
return -1;
}
pi = atan(1.0)*4.0;
x = strtod(argv[1], NULL);
if (argc==3) {
tol = strtod(argv[2], NULL);
} else {
tol = 1.0e-9;
}
//Only compute for positive x (since sin(x)=-sin(-x))
if ( x < 0 ) {
x = -x;
sign = 1;
}
//Limit x to the 0-2pi range (sine is periodic with period=2pi
x -= floor(x/(2*pi)) * (2*pi);
//Only compute in the first quadrant and use symmetries
if ( x <= pi/2 ) {
y = sum(x, tol);
} else if ( x <= pi) {
y = sum(pi-x, tol);
} else if ( x <= 3*pi/2 ) {
y = -sum(x-pi, tol);
} else {
y = -sum(2*pi-x, tol);
}
if (sign) y = -y;
printf("%.16f\n", y);
return 0;
}
Code size wise the haskell one is indeed more compact (and I think there is still room for improvement), however the size of the executable is amazingly different. With default compiler settings for gcc and gch the C program is 8.2 kB and the haskell one 1007 KB!? I wonder what is all that overhead …
Also, performance wise the C code is about 100% faster than the haskell one (but that I expected).
Well, its not really a fair comparison, the problem is indeed more tailored to imperative programming.
#1 by maxauthority on January 19, 2010 - 15:50
Quote
You probably mean 50% faster (twice as fast). If it is 100% faster, then it would execute in 0ms while the other could take a few minutes.
#2 by bmboy on February 4, 2010 - 20:50
Quote
Thank you! I added this page to bookmark)) I think would be useful …
#3 by belavina on February 14, 2010 - 23:22
Quote
Nice article. Would be grateful for any other information concerning this topic. Thanks!