對於讀者熟悉術語「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
我們可以從度轉換到弧度剛剛獲得輸入值後x
和n
:
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
被傳遞迴reduce
爲t
新值。
步驟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
我們可以用級數展開來估計角度的sine
0
和90
之間改善的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]
我不明白'高清sin_and_cos(X,N) '部分。你能解釋一下這部分代碼是如何一步一步工作的嗎?我在執行它到自己的代碼時遇到問題。 – Gregy 2014-10-14 14:23:09
Gregy,我提供了你要求的解釋。請讓我知道,如果它仍然不清楚。 – 2014-10-14 22:07:10
謝謝,現在對我來說似乎很清楚,但仍然會從第三和第四季度返回竇或餘弦的奇怪數據。它只發生在低「n」值。在你的例子中,我使用了n = 5,並且每個度數高於210且低於360的角度都失敗了。 – Gregy 2014-10-15 14:46:30