用于sin(x)和cos(x)的Maclaurin系列超过最大值
我已经编写了用于计算输入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窦和余弦超过最大值。
我认为程序应该以某种方式切入一个角度,但我不知道如何编写代码。
对于读者熟悉术语“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