Proyecto Métodos Numéricos

SEPARACIÓN Y BÚSQUEDA DE RAÍCES E INTERPOLACIÓN
Yensur Camacho Hernández. 2110206
1. Se tiene la función f(x)= ( )
( ), donde p es un parámetro que varía
entre 1.0 y 10.0, en intervalos enteros de uno en uno.
a. Separación de raíces.
Convirtiendo la función en una ecuación, se tiene la igualdad
( ). En una
gráfica de ambas partes de la igualdad como funciones, los puntos en que se intersectan las
curvas, son las raíces de la función. La función de la parte izquierda, es un logaritmo
elevado al cuadrado, de la cual se sabe que predomina el logaritmo sobre el exponente. Por
lo tanto, ésta tiene una concavidad hacia abajo. De la función del lado derecho, se sabe que
es un coseno con amplitud variable, donde la función f(x)=x representa el contorno. Dado
el comportamiento (concavidad) de la primera función, se sabe que en el intervalo (1,20),
siempre es superior la recta.
Para un parámetro p=1.0, las curvas son las siguientes:
Fig. 1. Funciones para un parámetro p=1.
Para un parámetro p=10.0, las funciones son las siguientes:
Fig. 2. Funciones para un parámetro p=10.
(Se utilizó un software para realizar las gráficas. Sin embargo, su uso no es obligatorio,
debido a que se las deducciones sobre las tendencias de las curvas, se realizaron sin
disponer de éste. Estos son resultados puramente analíticos).
Del estudio de las funciones periódicas, se sabe que el parámetro p representa la frecuencia
de la función coseno, y que el producto por la variable x únicamente altera su amplitud
(contorno). El coseno se hace cero, cada vez que el ángulo se hace múltiplo impar de π/2.
Es decir, si empezamos en π/2, cada vez que sumemos π nos encontraremos con una raíz.
Pero dado que el parámetro p altera la frecuencia de la función se tiene que cada π/p se
tiene una raíz. Así, a medida que se incremente p, también aumentará el número de raíces
en un mismo intervalo (“densidad de raíces”). Por otra parte, si se toman unos cuantos
valores de la función logarítmica, y se comparan con los valores de la recta y=x, se observa
que la altura de la recta es considerablemente superior a la de la curva (esto se sabe por la
dominancia del logaritmo sobre la potencia o por su concavidad). Este resultado es
importante, dado que se puede encontrar raíces a una menos distancia que π/p, dependiendo
de la altura a la que se encuentren las dos curvas. Un intervalo de π/2p parecería suficiente
“distancia” para evitar encontrar dos raíces juntas, pero por seguridad se utilizó π/4p como
paso.
b. Cálculo de las raíces.
El programa escrito en el lenguaje C, está diseñado para calcular las raíces de la función
especificada inicialmente, dentro del intervalo (1,20) y para valores del parámetro p entre 1
y 10, aumentando de uno en uno y contemplando una tolerancia de
. Asimismo,
para cada valor de p, el programa indica la cantidad de raíces encontradas dentro del
intervalo.
/* Programa que calcula las raíces de una función específica, dentro de
un intervalo establecido y en función de un parámetro p que varía de
forma controlada y conocida */
#include<stdio.h>
#include<math.h>
#define pi 3.14159265
//Funciones o subrutinas
double fun(double x,double p);
double raiz(double a,double b,double p);
int main(){
int nroot,salto=0;
double a,b,p,paso,i,root=0.00000000;
printf("\nPROGRAMA QUE CALCULA LAS RAICES DE UNA FUNCIÓN DADA, DENTRO
DEL INTERVALO (1,20), Y PARA DIFERENTES PARÁMETROS P \n",p);
for(p=1.00000000;p<=10.00000000;p++){
nroot=0;
paso=pi/(4.00000000*p);
printf("\n\nLas raices para la función con el parámetro p=%lf
son:\n\n",p);
for(i=1.00000000;i<20.00000000;i=i+paso){
a=i;
b=i+paso;
if ((fun(a,p)*fun(b,p))<0.00000000) {
root=raiz(a,b,p);
nroot=nroot+1;
printf("x%d= %.8lf, f(x%d)=%.9lf\t\t",nroot,root,nroot,fun(root,p));
salto++;
//Orden en consola
if(salto==3){
printf("\n");
salto=0;
}
}
}
printf("\n\nPara la función con el parámetro p=%lf, se encontraron %d
raíces, en el intervalo (1,20)\n\n\n",p,nroot);
printf("________________________________________\n");
}
return 0;
}
// Función que calcula la raíz para un intervalo (a,b)
double raiz(double a,double b,double p){
int i=0;
double c,e=0.00000001;
while(fun(a,p)>e || fun(a,p)<-e){
c=(b+a)/2;
if((fun(a,p)*fun(c,p))<0.00000000) b=c;
else if ((fun(c,p)*fun(b,p))<0.00000000)a=c;
}
return a;
}
// Función que evalúa cada valor
double fun(double x,double p){
double f;
f=(log(x))*(log(x)) - x*cos(p*x);
return f;
}
c. Variación de la cantidad de raíces con respecto al parámetro p.
El programa anterior, también está diseñado para indicar la cantidad de raíces halladas para
cada parámetro, donde p varía entre 1 y 10 de uno en uno. A continuación, se muestra un
histograma que presenta la cantidad de raíces halladas dentro del intervalo (1,20) para cada
parámetro diferente:
Cantidad de raices
Número de raices para cada parámetro
70
60
50
40
30
20
10
0
Raices
1
7
2
12
3
18
4
24
5
30
6
37
7
43
8
48
Fig .3. Histograma de la cantidad de raíces para cada parámetro.
9
54
10
61
d. Comparación entre el método de bisección y el método de Newton.
Los siguientes dos programas, permiten calcular la primera raíz de la función dada en el
intervalo (1,20) para los parámetros p=1 y p=10, respectivamente. Asimismo, muestra la
cantidad de iteraciones necesarias para llegar a tal resultado usando cada método.

Método de bisección.
/* PROGRAMA QUE CALCULA LA PRIMERA RAIZ DE UNA FUNCIóN DADA DENTRO DE UN
INTERVALO */
#include<stdio.h>
#include<math.h>
#define pi 3.14159265
//Funciones
double fun(double x,double p);
//Programa principal
int main(){
int iter;
double a,b,c,p,paso;
printf("\n\nPROGRAMA QUE CALCULA LA PRIMERA RAIZ DE UNA FUNCIÓN DADA
DENTRO DE UN INTERVALO USANDO EL MÉTODO DE BISECCIÓN\n\n");
for(p=1.00000000; p<=10.00000000;p=p*10.0){
printf("\n__________________________________\n\nBúsqueda de la primera
raiz para la función dada, en el intervalo (1,20), para un parámetro
p=%.8lf\n\n",p);
paso=pi/(20*p);
a=1.00000000;
b=a+paso;
while((fun(a,p)*fun(b,p))>0.00000000){
a=b;
b=b+paso;
}
iter=0;
while((fun(a,p))>0.00000001 || (fun(a,p))<-0.00000001 ||
(fun(b,p))>0.00000001 || (fun(b,p))<-0.00000001){
c=(b+a)/2;
if((fun(a,p)*fun(c,p))<0.00000000) b=c;
else if ((fun(c,p)*fun(b,p))<0.00000000)a=c;
iter=iter+1;
if((fun(a,p))>0.00000001 || (fun(a,p))<-0.00000001) printf("x%d=%.8lf,
f(x%d)=%.9lf.\n\n",iter,a,iter,fun(a,p),b,fun(b,p));
else printf("x%d=%.8lf, f(x%d)=%.9lf.\n\n",iter,b,iter,fun(b,p));
}
printf("\nNúmero de iteraciones: %d\n\n",iter);
}
return 0;
}
//Función para evaluar la "función"
double fun(double x,double p){
double f;
f=(log(x))*(log(x)) - x*cos(p*x);
return f;
}

Método de Newton.
/* PROGRAMA QUE CALCULA LA PRIMERA RAIZ DE UNA FUNCIÓN DADA DENTRO DE UN
INTERVALO */
#include<stdio.h>
#include<math.h>
#define pi 3.14159265
//Funciones
double fun(double x,double p);
double derivuno(double x,double p);
double derivdos(double x,double p);
//Programa principal
int main(){
int iter;
double a,b,p,paso,x;
printf("\n\nPROGRAMA QUE CALCULA LA PRIMERA RAIZ DE UNA FUNCIÓN DADA
DENTRO DE UN INTERVALO USANDO EL MÉTODO DE NEWTON\n\n");
for(p=1.00000000; p<=10.00000000;p=p*10.0){
printf("\n__________________________________\n\nBúsqueda de la primera
raiz para la función dada, en el intervalo (1,20), para un parámetro
p=%.8lf\n\n",p);
paso=pi/(20*p);
a=1.00000000;
b=a+paso;
while((fun(a,p)*fun(b,p))>0.00000000){
a=b;
b=b+paso;
}
if((fun(a,p)*derivdos(a,p))>0.00000000) x=b;
else if((fun(b,p)*derivdos(b,p))>0.00000000) x=a;
iter=0;
while((fun(x,p))>0.00000001 || (fun(x,p))<-0.00000001){
iter=iter+1;
x= x-(fun(x,p)/derivuno(x,p));
printf("x%d=%.8lf, f(x%d)=%.9lf.\n\n",iter,x,iter,fun(x,p));
}
printf("\nNúmero de iteraciones: %d\n\n",iter);
}
return 0;
}
//Función para evaluar la "función"
double fun(double x,double p){
double f;
f=(log(x))*(log(x)) - x*cos(p*x);
return f;
}
//Función que evalúa la primera derivada
double derivuno(double x,double p){
double g;
if(p==1.00000000) g=(x*sin(x))+((2.00000000*log(x))/x)-cos(x);
else if(p==10.00000000)
g=(2.00000000*log(x)/x)+(10.00000000*x*sin(10.00000000*x))-cos(x);
return g;
}
//Función que evalúa la segunda derivada
double derivdos(double x,double p){
double h;
if(p==1.00000000) h=(2.00000000*sin(x)) + (x*cos(x)) ((2.00000000*log(x))/(x*x)) + (2.00000000/(x*x));
else if(p==10.00000000)
h=(20.00000000*sin(10*x))+(100.00000000*x*cos(10.00000000*x))((2.00000000*log(x))/(x*x))+(2.00000000/(x*x));
return h;
}
El método de bisección requirió de 24 y 25 iteraciones para calcular la primera raíz de los
parámetros p=1 y p=10, respectivamente. Por su parte, el método de Newton necesitó
realizar únicamente 4 y 5 ciclos (en el mismo orden). Esto se debe principalmente a que a
medida que se está más cerca de la raíz, el método de Newton se acerca mucho más al valor
real (porque la pendiente de la recta tangente es más inclinada hacia la raíz), mientras que
el método de bisección realiza particiones uniformes siempre.
2. Se tiene la función ( )
( )
(
) entre a=1 y b=5.
a. Valores de la función.
Se calcularon los valores de la función, utilizando un programa que genera números
( )(
aleatorios entre 0 y 1, donde se tiene
).
Tab. 1. Datos de la función con un error ε= 0.000001.
i
1
2
3
4
5
6
7
8
9
10
11
xi
1.0
1.4
1.8
2.2
2.6
3.0
3.4
3.8
4.2
4.6
5.0
yi
0.259590
0.130271
-0.240861
0.796175
-1.405977
-1.902580
-2.122269
-1.946222
-1.332566
-0.332860
0.9106070
Tab. 2. Datos de la función con un error ε=0.1.
i
1
2
3
4
5
6
7
8
9
10
11
xi
1.0
1.4
1.8
2.2
2.6
3.0
3.4
3.8
4.2
4.6
5.0
yi
0.270045
0.141019
-0.249052
0.833427
-1.488352
-1.955351
-2.161273
-2.102620
-1.347872
-0.335111
0.961361
b. Cálculo de los valores de la función usando el polinomio de Lagrange.
El siguiente programa, permite calcular f(x) para un x que pertenece al intervalo [1,5], por
medio del polinomio de LaGrange, a partir de los datos de (Tab. 1-2):
#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#define e 0.000001 //En esta línea se cambia el
error................................
double fun(double x);
double exacta(double x);
int main(){
int i,j,h=0;
double a=1.000000,b=5.000000,x[11],y[11],P[201],paso,L[11],z;
printf("\n\nPROGRAMA QUE REALIZA INTERPOLACIÓN USANDO LA FÓRMULA DE
LAGRANGE\n\n");
paso=(b-a)/10.000000;
for(z=1.0;z<=5.0;z=z+0.02){
// Lectura de X y Y, e inicialización de L
for(i=0;i<11;i++){
x[i]=a+(i*paso);
y[i]=fun(x[i]);
L[i]=1.0;
}
//Fórmula de Lagrange
for(i=0;i<11;i++){
for(j=0;j<11;j++){
if(i!=j){
L[i]=L[i]*(z-x[j])/(x[i]-x[j]);
}
}
}
// Cada vez que pasa, el valor del polinomio se reinicia
P[h]=0.000000;
for(i=0;i<11;i++){
P[h]=P[h]+ L[i]*y[i];
}
printf("X=%lf , P(%d)=%lf , Error=%lf\t\t",z,h,P[h],exacta(z)-P[h]);
h++;
if(h%3 ==0.0) printf("\n\n");
}
return 0;
}
double fun(double x){
double f,rn;
rn=(drand48() * (0.94-0.1)+0.05);
f=((log(1.0+x)*log(1.0+x))*cos(x)*(1.0+e*rn));
return f;
}
double exacta(double x){
double f,rn;
f=((log(1.0+x)*log(1.0+x))*cos(x));
return f;
}
El programa permite realizar el cálculo tomando en cuenta diferentes errores en la función
(ε=0.1 y ε=0.000001), solo basta con ajustar el valor de ε en cada caso.
Si el programa se ejecuta para un error de ε=0.1 en la función, se puede observar que en
algunos casos el error en la imagen es superior a la unidad, siendo demasiado elevado. Sin
embargo, al cambiar el parámetro e (error) por ε=0.000001, se puede ver que la diferencia
entre el valor exacto y el valor aproximado, calculado por este método, recién aparece a
partir de la quinta cifra decimal (10-5). Razón por la cual, se puede deducir que ha sido una
aproximación aceptable.
c. Interpolación segmentaria línea y cuadrática.
Adicionalmente, tomando las mismas doscientas divisiones uniformes del intervalo [1,5]
como malla, se realizó el cálculo de cada una de las imágenes utilizando interpolación
polinomial segmentaria lineal y parabólica. Asimismo, cada programa registra el error
cometido en la aproximación con respecto al valor exacto.
//Interpolación lineal
#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#define e 0.000001
double fun(double x);
double exacta(double x);
int main(){
int i,j,h=0;
double a=1.0,b=5.0,z,paso,cota,c,d,P;
printf("\n\nPROGRAMA QUE INTERPOLA UNA FUNCIÓN USANDO UN POLINOMIO DE
PRIMER GRADO\n\n\n");
paso=(b-a)/10.0;
//---------------------------------------//Condición fuerte: X debe estar entre a y b
for(z=1.0;z<=5.0;z=z+((b-a)/200.0)){
for(cota=1.0;cota<=5.0;cota=cota+paso){
if(z>=cota && z<(cota+paso)){
c=cota;
d=cota+paso;
}
}
P=0.000000;
P= fun(c) + ((z-c)*(fun(d)-fun(c))/(d-c));
printf("X=%lf , P(%d)=%lf , Error=%lf\t\t",z,h+1,P,exacta(z)-P);
h++;
if(h%3 ==0.0) printf("\n\n");
}
printf("\n");
//----------------------------------------return 0;
}
double fun(double x){
double f,rn;
rn=(drand48() * (0.94-0.1)+0.05);
f=((log(1.0+x)*log(1.0+x))*cos(x)*(1.0+e*rn));
return f;
}
double exacta(double x){
double f,rn;
f=((log(1.0+x)*log(1.0+x))*cos(x));
return f;
}
/* Interpolación parabólica de segundo orden*/
#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#define e 0.000001
double fun(double x);
double exacta(double x);
int main(){
int i,j,h=0;
double a=1.0,b=5.0,z,paso,cota,c,d,P,r;
printf("\n\nPROGRAMA QUE INTERPOLA UNA FUNCIÓN USANDO UN POLINOMIO DE
SEGUNDO GRADO\n\n\n");
paso=(b-a)/10.0;
//---------------------------------------//Condición fuerte: X debe estar entre a y b
for(z=1.0;z<=5.0;z=z+((b-a)/200.0)){
for(cota=1.0;cota<=5.0;cota=cota+paso){
if(z>=cota && z<(cota+(2*paso))){
c=cota;
d=cota+paso;
r=cota+(2*paso);
}
}
P=0.000000;
P= fun(c)+((z-c)*(fun(d)-fun(c))/(d-c)) + ((fun(r)(2*fun(d))+fun(c))*(z-c)*(z-d)/(d-c)*(d-c));
printf("X=%lf , P(%d)=%lf , Error=%lf\t\t",z,h+1,P,exacta(z)-P);
h++;
if(h%3 ==0.0) printf("\n\n");
}
printf("\n");
//----------------------------------------return 0;
}
double fun(double x){
double f,rn;
rn=(drand48() * (0.94-0.1)+0.05);
f=((log(1.0+x)*log(1.0+x))*cos(x)*(1.0+e*rn));
return f;
}
double exacta(double x){
double f,rn;
f=((log(1.0+x)*log(1.0+x))*cos(x));
return f;
}
En términos generales, los errores cometidos por interpolación lineal y parabólica, no son
demasiado superiores con respecto a la interpolación por el polinomio de LaGrange,
cuando el error ε en la función es alto. Pese a esto, se destaca como más preciso el método
de LaGrange, seguido del método parabólico y por último el método lineal (esto se puede
comprobar ejecutando los programas respectivos que están diseñados para mostrar el error
para cada nodo).
Sin embargo, cuando el error ε se hace arbitrariamente más pequeño, el polinomio de
LaGrange aumenta considerablemente su precisión, disminuyendo su error en varios
órdenes de magnitud. Mientras tanto, en los métodos polinomiales no se producen mayores
cambios, dado que su precisión se basa en la unión por medio de curvas suaves entre los
puntos. Por esta razón, a medida que los valores de x se alejan de los nodos, los métodos
lineales pierden precisión.
A continuación, se muestran algunos resultados de la ejecución de cada programa para
demostrar las afirmaciones realizadas, dado que para sacar dichas conclusiones, los
programas deben ser compilados y ejecutados. Se muestran datos únicamente de ε=10-6.

Fórmula de LaGrange.

Forma polinomial lineal.

Forma polinomial parabólicas