sin(x)和cos(x)的Maclaurin系列超过最大值

时间:2014-10-11 14:27:44

标签: ruby math

我编写的程序对输入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窦和余弦超过最大值。
我认为该程序应该以某种方式切入一个角度,但我不知道如何编写它。

1 个答案:

答案 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);
  • 您对factorial的计算效率低下,因为只需更新每个递增值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} }。

这就是:

第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

我们可以通过使用系列展开来估算nn = 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