2014-10-11 125 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