Scheme 余弦函数计算格式

Scheme 余弦函数计算格式,scheme,Scheme,我正在制作一个计算 cos(x)=1-(x^2/2!)+(x^4/4!)-(x^6/6!) 完成这个程序最有效的方法是什么?你会怎么做交替加减法,这就是我用模来做的,但不适用于0和1(前两项)。x是x的初始值,num是项数 (define cosine-taylor (lambda (x num) (do ((i 0 (+ i 1))) ((= i num)) (if(= 0 (modulo i 2)) (+ x (/ (pow-tr2 x (

我正在制作一个计算 cos(x)=1-(x^2/2!)+(x^4/4!)-(x^6/6!)

完成这个程序最有效的方法是什么?你会怎么做交替加减法,这就是我用模来做的,但不适用于0和1(前两项)。x是x的初始值,num是项数

(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))
一些建议:

  • 减少输入模2pi-大多数多项式展开式收敛速度非常慢,数值很大
  • 跟踪你的阶乘,而不是每次从头开始计算(一旦你有了4,你就可以通过乘以5得到5,以此类推)
  • 同样,你所有的幂都是x^2的幂。只计算x^2一次,然后将“迄今为止的x次方”乘以这个数字(x2),而不是将x取n次方
  • 下面是一些实现这一点的python代码—它收敛于很少的术语(并且您可以使用
    while(abs(delta)>precision):
    语句控制精度)

    除此之外,我帮不了你多少忙,因为我不熟悉scheme…

    你的问题:

  • 完成课程最有效的方法是什么?假设您想要使用泰勒级数展开式,并简单地将项
    n
    相加几次,那么您的迭代方法就可以了。我在下面对它进行了改进;但是你的算法很好。其他人则指出了可能出现的精度损失问题;我的方法见下文

  • 你会怎么做交替加减法?使用另一个
    odd?
    布尔值的“参数/局部变量”,并使用
    not
    进行替换。当
    奇数?
    奇数?
    相加时进行减法

  • 不错吧

    以上是执行“do”循环的方法。您应该能够很容易地看到
    do
    i
    result
    odd?
    的三个局部变量的对应关系

    关于数字精度的损失-如果您真的想解决精度问题,请将
    x
    转换为“精确”数字,并使用精确数字进行所有计算。通过这样做,您可以得到一个自然的、具有“完美”精度的Scheme-ly算法

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

    我们应以迭代方式计算这些项,以防止因分割非常大的数字而导致精度损失:

    (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))))))
    
    然后,应该很容易构建一个函数来生成第n个项,或者将这些项相加,直到一个项小于预设的精度值:

    (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
    

    为了让您总体享受,下面是一个流实现。流根据提供的
    func
    返回泰勒项的无限序列。使用当前索引调用
    func

    (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
    

    这是我能想到的最精简的函数

    它利用了这样一个事实,即每个项乘以(-x^2)并除以(i+1)*(i+2)得到文本项

    它还利用了我们正在计算2,4,6的阶乘这一事实。以此类推,它将位置计数器增加2,并将其与2*N进行比较,以停止迭代

    (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)))
    
    在进行测试

    以下是一些答案:

    (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 (余弦泰勒1.02) => 0.5416666666666666 (余弦泰勒1.04) => 0.5403025793650793 (余弦泰勒1.06) => 0.5403023058795627 (余弦泰勒1.08) => 0.5403023058681398 (余弦泰勒1.0 10) => 0.5403023058681397 (余弦泰勒1.0 20) => 0.5403023058681397
    泰勒太慢了,无法收敛。看看切比雪夫多项式。或者牛顿-拉斐逊方案,你可以把算符作为参数传递。我正在看我的小Schemer(伟大的书)的副本,因为我认为它们在后面的章节中有这样一个函数。代码不可能编译(因为
    let
    用法),或者肯定会失败。你试了多努力?是的,没错,那句话是我试过的一次错误的;此外,在除法之前,计算幂和阶乘将很快失去控制,产生非常大的结果数,并随之失去精度(翻译:错误的结果)。只有这样,迭代计算才是可行的,当我们将中间值乘以
    x^2
    除以
    (k+1)*(k+2)
    的结果时。实际上,如果你想最小化精度损失,最好从最小的数开始——这需要计算一组值,然后“按相反顺序”求和。但这确实是一个调整。当你将数据降到-pi/2-pi/2域时,你就赢得了99%的战斗胜利。当然,将一个值降到域中应该是一个单独函数的任务,这就是为什么我没有在我的答案中包括它。余弦的域很幸运,但例如菲涅耳积分-没有那么多。:)@我不同意,如果你想计算余弦,你不需要调用两个函数。现在,你的余弦函数可能会调用另一个函数来缩小域,但我认为它的值有限。我的意思是,范围缩小与余弦计算是分开的,就像泰勒级数一样。它本身不是一个单独的布尔值和一个
    1/-1
    select,为什么不在每次迭代时用一个符号变量乘以-1…谢谢。用不同的方法更新。根据
    奇数?
    +1选择
    +
    -
    ,以显示精确/不精确。我猜在-pi/2…pi/2的范围内,精度问题无论如何都是没有意义的,尽管从概念上来说,我认为术语的计算应该是迭代的(而且我认为
    do
    很好)。如果你能说明如何限制精度(比如说,1/10^20),那就太好了(用某种方法将精确值近似为1/10^n的精确倍数)。感谢你的详细回答,我需要阅读更多内容才能完全理解你的代码
    (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)))
    
    (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