2014-10-11 156 views
0

我已經編寫了用於計算輸入x和n的sin(x)和cos(x)的程序。 下面是一個代碼:用於sin(x)和cos(x)的Maclaurin系列超過最大值

def factorial(z) 
    if z <= 0 
    1 
    else 
    z * factorial(z-1) 
    end 
end 

def radian(y) 
    y%360 * Math::PI/180 
end 

puts "Enter x" 
x = gets.chomp.to_i 
puts "Enter n" 
n = gets.chomp.to_i 

sin = 0 
(0..n).each do |n| 
    k = ((-1)**(n))*(radian(x)**((2*n)+1))/factorial((2*n)+1) 
    sin = sin+k 
end 
puts "Sinus: #{sin}" 

cos = 0 
(0..n).each do |n| 
    l = ((-1)**(n))*(radian(x)**(2*n))/factorial(2*n) 
    cos = cos+l 
end 
puts "Cosinus: #{cos}" 

我不能找出什麼不對第三和第四季度
例如,X = 237用於程序如果用戶輸入低的「n」(步驟值)中,n = 3竇和餘弦超過最大值。
我認爲程序應該以某種方式切入一個角度,但我不知道如何編寫代碼。

回答

1

對於讀者熟悉術語「Maclauren系列」,它僅僅是一個泰勒級數集中在零:

f(x) ≈ f(0) + f'(0)/1! + f''(0)/2! + f'''(0)/3! +... 

其中f'f''f'''表示功能f的第一,第二和第三衍生物。

問題

讓我們來看看你的sin計算。看來你只是求和奇數項,這很好,因爲偶數項全部爲零。你的問題是Maclauran系列的分子,在你的表達中,它應該是零的餘弦(相當於1),或者帶有符號+-。你的符號是正確的,但不是cos(0) #=> 1,你已經計算出弧度爲2*n+1的弧度角。

另類視角

考慮以下幾點:

  • 因爲sin(x)**2 + cos(x)**2 = 1,你只需要計算sin(x)cos(x)與Maclauren系列。下面我假設我們估計sin(x)(在這種情況下cos(x) = 1-sin(x)**2)**0.5);
  • 由於sin(x)衍生物是cos(x)cos(x)衍生物是-sin(x)sin(0)是零,我們只需要計算系列,其分子爲可替代地cos(0)-cos(x)的奇數項(1和-1);
  • 您的階乘計算效率很低,因爲只需要更新每個遞增值n的階乘值;
  • 由於您只能使用弧度,所以您可以在獲得度數的輸入值後立即將度數轉換爲弧度;和
  • 由於您限制輸入整數值的度數,因此您可以編寫gets.to_i而不是gets.chomp.to_i(該選擇僅僅是文體)。

首先讓我們階乘的計算效率更高,當他們只需要計算爲n奇數值:

def factorial(n) 
    if n==1 
    @fac = 1 
    else 
    @fac *= n*(n-1) 
    end 
end 

檢查:

factorial(1) #=> 1 
factorial(3) #=> 6 
factorial(5) #=> 120 
factorial(7) #=> 5040 

我們可以從度轉換到弧度剛剛獲得輸入值後xn

x = radian(x) 

在查看我的意見的上方,用於近似的值的sin降低到n/2術語表達:

(x**1)/1! - (x**3)/3! + (x**5)/5! - (x**7)/7! +... 

,我們可以作爲寫如下:

sign = -1 
(1..n).step(2).reduce(0) { |t,i| t + (sign *= -1)*(x**i)/factorial(i) } 

代碼

把它放在一起:

def sin_and_cos(x,n) 
    sign = -1 
    sin = (1..n).step(2).reduce(0) { |t,i| t + (sign *= -1)*(x**i)/factorial(i) } 
    cos = (1-sin**2)**0.5 
    cos = -cos if (x > 0.5 * Math::PI && x < 1.5 * Math::PI) 
    [sin, cos] 
end 

def factorial(n) 
    if n==1 
    @fac = 1 
    else 
    @fac *= n*(n-1) 
    end 
end 

def radian(y) 
    y%360 * Math::PI/180 
end 

Let's try it: 

x = 30 
x = radian(30) 
    #=> 0.5235987755982988 

sin, cos = sin_and_cos(x,5) 
    #=> [0.5000021325887924, 0.8660241725302242] 
sin, cos = sin_and_cos(x,9) 
    #=> [0.5000000000202799, 0.8660254037727301] 
sin, cos = sin_and_cos(x,13) 
    #=> [0.5, 0.8660254037844386] 

說明

讓我們來看看sin_and_cos(x,n)計算時:

x = 30 
x = radian(30) 
    #=> 0.523598 
n = 5 

我們首先執行

sign = -1 

接下來,我們創建一個枚舉:

enum0 = (1..5).step(2) 
    #=> (1..5).step(2) 
    #=> #<Enumerator: 1..5:step(2)> 

我們可以通過將其轉換爲一個數組看到枚舉的元素:

enum0.to_a 
    #=> [1, 3, 5] 

接着,Enumerable#reduce(也稱爲inject)將其塊變量t初始化爲0,然後將枚舉數enum的每個值傳遞到塊中,並將其分配給塊變量i。每當你要計算從一個數組,散列或其他類型的集合值的總和或產品,你應該想到使用reduce的,之後可能進行的每個值(e..g的改造,[1,2,3].reduce(0) { |t,i| t + i*i } #=> 14

這是發生的事情:

第1步:通過i => 1成塊:

i = 1 
t = 0 
sign *= -1 
    #=> sign = sign * -1 => -1 * -1 = 1 
x**i 
    #=> 0.523598**1 => 0.523598 
factorial(i) 
    #=> factorial(1) => 1 
t 
    #=> 0 + 1 * 0.523598/1 
    #=> 0.523598 

0.523598被傳遞迴reducet新值。

步驟2:通過i => 3成塊:

i = 3 
t = 0.523598 
sign *= -1 
    #=> sign = sign * -1 => 1 * -1 = -1 
x**i 
    #=> 0.523598**3 => 0.1435469 
factorial(i) 
    #=> factorial(3) => 6 
t 
    #=> 0.523598 + -1 * 0.1435469/6 
    #=> 0.499673 

0.499673被傳遞迴reduce作爲t新值。

步驟3:通過i => 5成塊:

i = 5 
t = 0.499673 
sign *= -1 
    #=> sign = sign * -1 => -1 * -1 = 1 
x**i 
    #=> 0.523598**5 => 0.039354 
factorial(i) 
    #=> factorial(5) => 120 
t 
    #=> 0.499673 + 1 * 0.039354/120 
    #=> 0.50000095 

0.50000095被傳遞迴reduce作爲t新的值,但作爲枚舉的所有元素現在已經處理,該值是返回爲sin(0.523598)的近似值。

最後我們計算餘弦的近似值:

cos = (1-sin**2)**0.5 
    = (1-0.523598**2)**0.5 
    #=> 0.851965 

,並返回

[0.523598, 0.851965] 

注餘弦值是在第一和第四象限正,故

cos = -cos if (x > 0.5 * Math::PI && x < 1.5 * Math::PI) 

不會改變標誌。

如您所見,sign *= -1只是在1-1之間翻轉sign的值。您也可以使用(-1)**(n/2)

精度

回想一下,這種近似是泰勒級數集中於零。對於接近於零的度數,僅使用sin的一階和二階導數就可以提供很好的近似值。對於遠離零的度數,需要高階導數來得到相當好的近似值。

考慮例如30,150,210和330度的sin。前兩個等於0.5,後兩個是-0.5。讓我們看看n的不同值的近似值與其中每個值的接近程度。我們會發現度數從零開始越遠,更大的n必須產生一個近似值。

首先,讓我們創建一個方法,返回給定度數(非弧度)和n的近似值sin,四捨五入爲七位十進制數字。

def sin_approx(degrees, n) 
    x = radian(degrees) 
    sin_and_cos(x, n).first.round(7) 
end 

現在,讓我們看到的n不同的價值觀會發生什麼:

n = 5 
sin_approx(30, n) #=> 0.5000021 
sin_approx(150, n) #=> 0.6522731 
sin_approx(210, n) #=> 0.9709643 
sin_approx(330, n) #=> 26.7331389 

n = 7 
sin_approx(30, n) #=> 0.5 
sin_approx(150, n) #=> 0.4850294 
sin_approx(210, n) #=> -0.7920105 
sin_approx(330, n) #=> -14.9834333 

n = 10 
sin_approx(30, n) #=> 0.5 
sin_approx(150, n) #=> 0.5009498 
sin_approx(210, n) #=> -0.4630779 
sin_approx(330, n) #=> 4.2368035 

n = 14 
sin_approx(30, n) #=> 0.5 
sin_approx(150, n) #=> 0.5000014 
sin_approx(210, n) #=> -0.4997892 
sin_approx(330, n) #=> -0.3269112 

n = 20 
sin_approx(30, n) #=> 0.5 
sin_approx(150, n) #=> 0.5 
sin_approx(210, n) #=> -0.5 
sin_approx(330, n) #=> -0.5001706 

我們可以用級數展開來估計角度的sine090之間改善的n給定值的精度並且推導出大於90的度數的正弦:

R0_90 = (0.0..0.5*Math::PI) 
R90_180 = (0.5*Math::PI...Math::PI) 
R180_270 = (Math::PI...1.5*Math::PI) 

def sin_and_cos(x,n) 
    s = sin(x,n) 
    cos = (1-s**2)**0.5 
    cos = -cos if (R90_180.cover?(x) || R180_270.cover?(x)) 
    [s, cos] 
end 

def sin(x,n) 
    sign = [1, -1].cycle 
    sin = 
    case(x) 
    when R0_90 
     (1..n).step(2).reduce(0) { |t,i| t + (sign.next)*(x**i)/factorial(i) } 
    when R90_180 
     sin(Math::PI-x,n) 
    else 
     -sin(x-Math::PI,n)   
    end 
end 

sin_approx(30, n) #=> 0.5000021 
sin_approx(150, n) #=> 0.5000021 
sin_approx(210, n) #=> -0.5000021 
sin_approx(330, n) #=> -0.5000021 

總結

將上面的改進,代碼如下:

R0_90 = (0.0..0.5*Math::PI) 
R90_180 = (0.5*Math::PI...Math::PI) 
R180_270 = (Math::PI...1.5*Math::PI) 

def factorial(n) 
    if n==1 
    @fac = 1 
    else 
    @fac *= n*(n-1) 
    end 
end 

def radian(y) 
    y%360 * Math::PI/180 
end 

def sin(x,n) 
    sign = [1, -1].cycle 
    sin = 
    case(x) 
    when R0_90 
     (1..n).step(2).reduce(0) { |t,i| t + (sign.next)*(x**i)/factorial(i) } 
    when R90_180 
     sin(Math::PI-x,n) 
    else 
     -sin(x-Math::PI,n)   
    end 
end 

def sin_and_cos(x,n) 
    s = sin(x,n) 
    cos = (1-s**2)**0.5 
    cos = -cos if (R90_180.cover?(x) || R180_270.cover?(x)) 
    [s, cos] 
end 

def sin_and_cos_approx(degrees, n) 
    x = radian(degrees) 
    s, c = sin_and_cos(x, n) 
    [s.round(7), c.round(7)] 
end 

讓我們試一下:

n = 5 
sin_and_cos_approx(30, n) #=> [ 0.5000021, 0.8660242] 
sin_and_cos_approx(150, n) #=> [ 0.5000021, -0.8660242] 
sin_and_cos_approx(210, n) #=> [-0.5000021, -0.8660242] 
sin_and_cos_approx(330, n) #=> [-0.5000021, 0.8660242] 

n = 7 
sin_and_cos_approx(30, n) #=> [ 0.5, 0.8660254] 
sin_and_cos_approx(150, n) #=> [ 0.5, -0.8660254] 
sin_and_cos_approx(210, n) #=> [-0.5, -0.8660254] 
sin_and_cos_approx(330, n) #=> [-0.5, 0.8660254] 
+0

我不明白'高清sin_and_cos(X,N) '部分。你能解釋一下這部分代碼是如何一步一步工作的嗎?我在執行它到自己的代碼時遇到問題。 – Gregy 2014-10-14 14:23:09

+0

Gregy,我提供了你要求的解釋。請讓我知道,如果它仍然不清楚。 – 2014-10-14 22:07:10

+0

謝謝,現在對我來說似乎很清楚,但仍然會從第三和第四季度返回竇或餘弦的奇怪數據。它只發生在低「n」值。在你的例子中,我使用了n = 5,並且每個度數高於210且低於360的角度都失敗了。 – Gregy 2014-10-15 14:46:30