I'm trying to make a while loop that approximates the value of cos(x) so that its to within - or + 1-e10

171 views Asked by At

We must use a while loop to solve this problem (approximating the value of cos to within - or + 1-e10 ) and I believe I have all the right setup but I keep getting the error "missing value where TRUE/FALSE needed"

The question stating

A Taylor expansion of a function is an expression of the function as an infinite sum of terms. We can approximate the function by taking the first several terms of the infinite sum. The more terms we include, the better will be our approximation.

The Taylor expansion of the cosine function $cos(x)$ is: 1-((x^2)/(2!))+(x^4)/(4!)...

x = pi/2
n = 0
approximation = 1
limit = 1e-10
while(approximation < (limit*(-1)) || approximation > limit){
  (term = c(((-1)^n)*((x)^(2*n)))/factorial(2*n))
  (n  = n + 1)
  (approximation = approximation + term)
}
approximation

This is the code that I attempted but like I said it keeps giving me the error stated above.

3

There are 3 answers

0
Greg On BEST ANSWER

Solution

To avoid this error, simply initialize n <- 1, which aligns your terms with the Taylor series.

Diagnosis

When you initialize n <- 0, the series starts with 2 rather than 1, unlike the Taylor series. This causes approximation to converge wrongly on 1, by the 11th iteration.

x = pi/2
n = 0
approximation = 1
limit = 1e-10
while(approximation < (limit*(-1)) || approximation > limit){
  (term = c(((-1)^n)*((x)^(2*n)))/factorial(2*n))
  (n  = n + 1)
  (approximation = approximation + term)
  
  # Debugging.
  cat(sep = "\n",
    sprintf("n = %3s; term = %22s; approximation = %17s", n, term, approximation)
  )
}
approximation

As discussed below, this loops out of control until throwing an error.

n =   1; term =                      1; approximation =                     2
n =   2; term =      -1.23370055013617; approximation =      0.76629944986383
n =   3; term =      0.253669507901048; approximation =      1.01996895776488
n =   4; term =     -0.020863480763353; approximation =     0.999105477001525
n =   5; term =   0.000919260274839426; approximation =      1.00002473727636
n =   6; term =  -2.52020423730606e-05; approximation =     0.999999535233992
n =   7; term =   4.71087477881817e-07; approximation =      1.00000000632147
n =   8; term =  -6.38660308379185e-09; approximation =     0.999999999934866
n =   9; term =   6.56596311497947e-11; approximation =      1.00000000000053
n =  10; term =  -5.29440020073462e-13; approximation =     0.999999999999996
n =  11; term =    3.4377391790986e-15; approximation =                     1
n =  12; term =  -1.83599165215524e-17; approximation =                     1

# ...

n =  85; term =  3.51311990046563e-270; approximation =                     1
n =  86; term = -3.01715137758328e-274; approximation =                     1
n =  87; term =                      0; approximation =                     1
n =  88; term =                      0; approximation =                     1

# ...

n = 785; term =                      0; approximation =                     1
n = 786; term =                      0; approximation =                     1
n = 787; term =                    NaN; approximation =                   NaN
Error in while (approximation < (limit * (-1)) || approximation > limit) { : 
  missing value where TRUE/FALSE needed

But with n <- 1 we properly obtain an approximation of -6.51335680512735e-11, which terminates on the 7th iteration:

n =   2; term =      -1.23370055013617; approximation =     -0.23370055013617
n =   3; term =      0.253669507901048; approximation =    0.0199689577648782
n =   4; term =     -0.020863480763353; approximation = -0.000894522998474732
n =   5; term =   0.000919260274839426; approximation =  2.47372763646945e-05
n =   6; term =  -2.52020423730606e-05; approximation = -4.64766008366076e-07
n =   7; term =   4.71087477881817e-07; approximation =  6.32146951574058e-09
n =   8; term =  -6.38660308379185e-09; approximation = -6.51335680512735e-11

Error

As shown above, approximation converges on 1, and always remains greater than the limit of 1e-10. So the condition is always TRUE, and the while loop continues indefinitely.

#                                     |------ TRUE -------|
while(approximation < (limit*(-1)) || approximation > limit){
  # ...
}

But when n reaches 786, your term's numerator ((-1)^n)*((x)^(2*n)) maxes out at Infinity.

n <- 785
((-1)^n)*((x)^(2*n))
#> [1] -8.094815e+307

n <- 786
((-1)^n)*((x)^(2*n))
#> Inf

Now your denominator factorial(2*n) has been Inf for some time, ever since n reached 86.

n <- 85
factorial(2*n)
#> [1] 7.257416e+306

n <- 86
factorial(2*n)
#> [1] Inf

So until now, the overall quotient has been 0: a finite number divided by Infinity. But when n reaches 786, your term becomes Inf / Inf, which is NaN: "Not a Number".

n <- 785
c(((-1)^n)*((x)^(2*n)))/factorial(2*n)
#> [1] 0
-8.094815e+307 / Inf
#> [1] 0

n <- 786
c(((-1)^n)*((x)^(2*n)))/factorial(2*n)
#> [1] NaN
Inf / Inf
#> [1] NaN

When approximation is incremented by NaN, the result is still NaN.

1 + NaN
#> [1] NaN

With an approximation of NaN, your while conditions evaluate to NA: "Not Available".

limit <- 1e-10

NaN < (limit*(-1))
#> [1] NA
NaN > limit
#> [1] NA

NA || NA
#> [1] NA

In your while loop, this NA condition throws the error you encountered.

while(NA){
  # ...
}
#> Error in while (NA) { : missing value where TRUE/FALSE needed
4
Rui Barradas On

Compute the factorial and the powers of x using logarithms, then invert the logs.

In the question's code, if n starts at zero then approximation is off by 1. The cosinus series expansion 1st term is 1 because (-1)^0 == 1.
So I start with n <- 1 and approximation <- 1. This avoids having to treat the first case as a special case. I also change the stop criterion to one based on differences of two consecutive values. This allows for the computation of cos(x) to be more general and not depend on the input x. In question's algorithm, only values with an approximate final value equal to zero were allowed.

As for the use of log factorials, the value of (2n)! grows rapidly and with a small enough allowed absolute error it would become Inf, and all next term's would be zero, resulting in an infinite loop. lfactorial keeps the numbers amenable and the intermediate calculations finite.

Speed is not important so I keep the repeated calculation of log(x) in the loop, to have the formula used as close to the Taylor series expansion formula as possible. This calculation can (and should) be made before the loop. And multiplied by 2, since we are at it.

taylor_cos <- function(x, limit = 1e-10) {
  n <- 1
  approximation <- 1
  err <- Inf
  while(err > limit){
    lterm <- 2*n*log(x) - lfactorial(2*n)
    term <- (-1)^n * exp(lterm)
    previous <- approximation
    approximation <- previous + term
    err <- abs(previous - approximation)
    n <- n + 1L
  }
  approximation
}

taylor_cos(pi/2)
#> [1] 5.260102e-13
taylor_cos(pi/3)
#> [1] 0.5
taylor_cos(pi/6)
#> [1] 0.8660254
taylor_cos(pi/4)
#> [1] 0.7071068

Created on 2024-01-31 with reprex v2.0.2

0
ThomasIsCoding On

Two points to address your issues:

  1. Should initialize n with 1 instead of 0,
  2. Should be aware of precision loss in factorial(2*n) when n grows huge. (you can refer to @Rui Barradas's solution, where lfactorial is used)

One workaround works like below

x <- pi / 2
n <- 1 # initialize by `1`
approximation <- 1
limit <- 1e-10
while (abs(approximation) > limit) {
  term <- (-1)^n * prod(x / seq_len(2 * n)) # refactor `x^(2*n)/factorial(2*n)` by `prod(x / seq_len(2 * n))` to keep the precision
  (n <- n + 1)
  (approximation <- approximation + term)
}

and you will obtain

> approximation
[1] -6.513356e-11