简体   繁体   中英

cosine function calculating scheme

Im making a scheme program that calculates cos(x) = 1-(x^2/2!)+(x^4/4!)-(x^6/6!).......

whats the most efficient way to finish the program and how would you do the alternating addition and subtraction, thats what I used the modulo for but doesnt work for 0 and 1 (first 2 terms). x is the intial value of x and num is the number of terms

(define cosine-taylor
 (lambda (x num)
   (do ((i 0 (+ i 1)))
      ((= i num))
      (if(= 0 (modulo i 2))
          (+ x (/ (pow-tr2 x (* i 2)) (factorial (* 2 i))))
          (- x (/ (pow-tr2 x (* i 2)) (factorial (* 2 i))))
      ))
   x))

Your questions:

  1. whats the most efficient way to finish the program? Assuming you want use the Taylor series expansion and simply sum up the terms n times, then your iterative approach is fine. I've refined it below; but your algorithm is fine. Others have pointed out possible loss of precision issues; see below for my approach.

  2. how would you do the alternating addition and subtraction? Use another 'argument/local-variable' of odd? , a boolean, and have it alternate by using not . When odd? subtract when not odd? add.

 (define (cosine-taylor xn) (let computing ((result 1) (i 1) (odd? #t)) (if (> in) result (computing ((if odd? - +) result (/ (expt x (* 2 i)) (factorial (* 2 i)))) (+ i 1) (not odd?))))) > (cos 1) 0.5403023058681398 > (cosine-taylor 1.0 100) 0.5403023058681397 

Not bad?

The above is the Scheme-ish way of performing a 'do' loop. You should easily be able to see the correspondence to a do with three locals for i , result and odd? .

Regarding loss of numeric precision - if you really want to solve the precision problem, then convert x to an 'exact' number and do all computation using exact numbers. By doing that, you get a natural, Scheme-ly algorithm with 'perfect' precision.

> (cosine-taylor (exact 1.0) 100)
3982370694189213112257449588574354368421083585745317294214591570720658797345712348245607951726273112140707569917666955767676493702079041143086577901788489963764057368985531760218072253884896510810027045608931163026924711871107650567429563045077012372870953594171353825520131544591426035218450395194640007965562952702049286379961461862576998942257714483441812954797016455243/7370634274437294425723020690955000582197532501749282834530304049012705139844891055329946579551258167328758991952519989067828437291987262664130155373390933935639839787577227263900906438728247155340669759254710591512748889975965372460537609742126858908788049134631584753833888148637105832358427110829870831048811117978541096960000000000000000000000000000000000000000000000000
> (inexact (cosine-taylor (exact 1.0)  100))
0.5403023058681398

we should calculate the terms in iterative fashion to prevent the loss of precision from dividing very large numbers:

(define (cosine-taylor-term x)
  (let ((t 1.0) (k 0))
    (lambda (msg)
      (case msg
        ((peek) t)
        ((pull) 
          (let ((p t))
             (set! k (+ k 2))
             (set! t (* (- t) (/ x (- k 1)) (/ x k)))
             p))))))

Then it should be easy to build a function to produce an n -th term, or to sum the terms up until a term is smaller than a pre-set precision value:

(define t (cosine-taylor-term (atan 1)))
;Value: t

(reduce + 0 (map (lambda(x)(t 'pull)) '(1 2 3 4 5)))
;Value: .7071068056832942

(cos (atan 1))
;Value: .7071067811865476

(t 'peek)
;Value: -2.4611369504941985e-8

A few suggestions:

  1. reduce your input modulo 2pi - most polynomial expansions converge very slowly with large numbers
  2. Keep track of your factorials rather than computing them from scratch each time (once you have 4!, you get 5! by multiplying by 5, etc)
  3. Similarly, all your powers are powers of x^2. Compute x^2 just once, then multiply the "x power so far" by this number (x2), rather than taking x to the n'th power

Here is some python code that implements this - it converges with very few terms (and you can control the precision with the while(abs(delta)>precision): statement)

from math import *

def myCos(x):
  precision = 1e-5 # pick whatever you need
  xr = (x+pi/2) % (2*pi)
  if xr > pi:
    sign = -1
  else:
    sign = 1
  xr = (xr % pi) - pi/2
  x2 = xr * xr
  xp = 1
  f = 1
  c = 0
  ans = 1
  temp = 0
  delta = 1
  while(abs(delta) > precision):
    c += 1
    f *= c
    c += 1
    f *= c
    xp *= x2
    temp = xp / f
    c += 1
    f *= c
    c += 1
    f *= c
    xp *= x2
    delta = xp/f - temp
    ans += delta
  return sign * ans

Other than that I can't help you much as I am not familiar with scheme...

For your general enjoyment, here is a stream implementation. The stream returns an infinite sequence of taylor terms based on the provided func . The func is called with the current index.

(define (stream-taylor func)
  (stream-map func (stream-from 0)))
(define (stream-cosine x)
  (stream-taylor (lambda (n) 
                   (if (zero? n)
                       1
                       (let ((odd? (= 1 (modulo n 2))))
                         ;; Use `exact` if desired...
                         ;; and see @WillNess above; save 'last'; use for next; avoid expt/factorial
                         ((if odd? - +) (/ (expt x (* 2 n)) (factorial (* 2 n)))))))))
> (stream-fold + 0 (stream-take 10 (stream-cosine 1.0)))
0.5403023058681397

Here's the most streamlined function I could come up with.

It takes advantage of the fact that the every term is multiplied by (-x^2) and divided by (i+1)*(i+2) to come up with the text term.

It also takes advantage of the fact that we are computing factorials of 2, 4, 6. etc. So it increments the position counter by 2 and compares it with 2*N to stop iteration.

(define (cosine-taylor x num)

    (let ((mult (* x x -1))
          (twice-num (* 2 num)))

      (define (helper iter prev-term prev-out)
        (if (= iter twice-num)
          (+ prev-term prev-out)
          (helper (+ iter 2)
                  (/ (* prev-term mult) (+ iter 1) (+ iter 2))
                  (+ prev-term prev-out))))

      (helper 0 1 0)))

Tested at repl.it .

Here are some answers:

(cosine-taylor 1.0 2)
=> 0.5416666666666666
   (cosine-taylor 1.0 4)
=> 0.5403025793650793
   (cosine-taylor 1.0 6)
=> 0.5403023058795627
   (cosine-taylor 1.0 8)
=> 0.5403023058681398
   (cosine-taylor 1.0 10)
=> 0.5403023058681397
   (cosine-taylor 1.0 20)
=> 0.5403023058681397

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM