COMPUTING SINES

Problem: Given the magnitude of an angle, expressed in radians, compute the angle's sine.

Ideally, we should be able to figure the sine of x as the limit of the infinite series x/1! - x^3/3! + x^5/5! - x^7/7! + ... . It is convenient to represent a series in Scheme as a procedure that takes any positive integer k as an argument and returns the value of the kth term. If we had a procedure limit-of-series that took take any convergent series as its argument and return the limit of that series, we could simply write

(define sine
  (lambda (x)
    (let ((sine-series
           (lambda (n)
             (let ((index (+ n n -1)))
               ((if (odd? n) + -) (/ (expt x index) (factorial index)))))))
      (limit-of-series sine-series))))
as a direct translation of the mathematical definition.

Unfortunately, computing the limit of a series arithmetically requires an infinite amount of time. In many cases, however, one can halt either when a specified number of terms has been computed or when sucessive partial sums differ by a sufficiently small amount. In either case, the limit will be computed only approximately; but in general the arguments to the sine function will be inexact numbers anyway.

Here is a version of limit-of-series that computes the value of the series at the twelfth term:

(define limit-of-series
  (let ((number-of-terms 12))
    (lambda (series)
      (let loop ((total 0)
                 (k number-of-terms))
        (if (zero? k)
            total
            (loop (+ total (series k)) (- k 1)))))))
In this case the computation has been arranged so that the terms that are likely to have the least absolute values are summed first, since this will postpone the loss of precision.

Here is a version of limit-of-series that continues the series until successive partial sums differ by less than 0.000001:

(define limit-of-series
  (let ((epsilon 1e-6))
    (lambda (series)
      (let loop ((total (series 1))
                 (previous-total 0)
                 (k 2))
        (if (< (abs (- total previous-total)) epsilon)
            total
            (loop (+ total (series k)) total (+ k 1)))))))
Either approach gives a sine function that yields plausible values for arguments close to zero, rapidly deteriorating in accuracy once |x| exceeds 1. (The difficulty when |x| > 1 is that the computation involves adding inexact values of large, nearly equal magnitude and opposite sign, so that many or all of the significant digits are cancelled out.)


This document is available on the World Wide Web as

http://www.math.grin.edu/~stone/events/scheme-workshop/sines.html


created July 9, 1995
last revised June 24, 1996

John David Stone (stone@math.grin.edu)