我编写的程序对输入x和n的sin(x)和cos(x)进行计数。 这是一个代码:
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}"
如果用户输入第三和第四季度的低“n”(步长值),我无法弄清楚程序有什么问题。例如x = 237,n = 3窦和余弦超过最大值。
我认为该程序应该以某种方式切入一个角度,但我不知道如何编写它。
答案 0 :(得分:0)
对于不熟悉“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
,您只需要使用Maclauren系列计算sin(x)
或cos(x)
。以下我假设我们估计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} }。
这就是:
第1步:将[1,2,3].reduce(0) { |t,i| t + i*i } #=> 14
传递到块中:
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
。
第2步:将t
传递到块中:
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
。
第3步:将t
传递到块中:
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
,但由于现在已经处理了枚举数的所有元素,因此该值将作为{的近似值返回{1}}。
最后我们计算余弦的近似值:
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
之间翻转sign
的值。您也可以使用1
。
<强>精度强>
回想一下,这个近似是一个以零为中心的泰勒级数。对于接近零的度数,仅使用-1
的一阶和二阶导数提供了良好的近似。对于距离零更远的度数,需要更高阶导数才能得到相当好的近似值。
例如,考虑30,150,210和330度的(-1)**(n/2)
。前两个等于0.5,后两个等于-0.5。让我们看看对于sin
的不同值,近似值与每个值的接近程度。我们会发现度数从零开始越远,sin
越大必须产生近似值。
首先,让我们创建一个方法,在给定度数(不是弧度)和n
的情况下返回n
的近似值,舍入到七位小数。
sin
现在让我们看看n
的不同值会发生什么:
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
之间角度的n
来提高给定值sine
的准确度,并导出正弦值度数大于0
:
90
总结
结合上述改进,代码如下:
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