Está en la página 1de 61

Guı́a de Prácticas:

Análisis Numérico
de
Ecuaciones Diferenciales
usando MATLAB
2
Índice general

Notaciones 5

Introducción 6

I Elementos básicos de MATLAB 9

1. Introducción a MATLAB. 11
1.1. Vectores en MATLAB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.2. Matrices en MATLAB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.3. Funciones de vectores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.4. Bucles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
1.4.1. Relaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
1.5. La instrucción if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
1.6. Ficheros ejecutables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
1.7. Subrutinas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
1.8. Cadenas de texto, mensajes de error, entradas . . . . . . . . . . . . . . . . 33
1.9. Comparando la eficiencia de algoritmos: cputime . . . . . . . . . . . . . . . 34
1.10. Formatos de salida . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
1.11. Gráficos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
1.11.1. Representaciones en 2D . . . . . . . . . . . . . . . . . . . . . . . . 35
1.11.2. Representaciones en 3D . . . . . . . . . . . . . . . . . . . . . . . . 37
1.12. Resumen de funciones elementales y matrices especiales . . . . . . . . . . . 39

3
4

II Introducción a la construcción de algoritmos 41


2. Sistemas de numeración; errores y sus fuentes 43
2.1. Sistemas de números y conversiones . . . . . . . . . . . . . . . . . . . . . . 43
2.1.1. Sistemas de numeración en base q . . . . . . . . . . . . . . . . . . . 43
2.1.2. Conversión de base decimal a base q . . . . . . . . . . . . . . . . . 44
2.1.3. Estándar IEEE de representación de números enteros y en coma
flotante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
2.2. Errores y sus causas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
2.2.1. Definiciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
2.2.2. Fuentes de errores . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
2.3. Propagación de errores: condición y estabilidad. . . . . . . . . . . . . . . . 54
2.4. Eficiencia de un algorı́tmo numérico . . . . . . . . . . . . . . . . . . . . . . 56
2.4.1. Ejemplo: Evaluación de polinomios, método de Horner . . . . . . . 57
2.5. EJERCICIOS PRÁCTICOS . . . . . . . . . . . . . . . . . . . . . . . . . . 60
5

NOTACIONES

Notaciones generales

Sı́mbolo Significado
x = (x1 , x2 , ..., xN ) Elemento de IRN
r = |x|= (x21 + x22 + ...+ x2N )1/2 Módulo de x
∂u ∂u ∂u
∇u = ∂x ,
1 ∂x2
, ..., ∂x N
Gradiente de u
Espacios de funciones

Sı́mbolo Significado
C(Ω) Funciones continuas en Ω
C0 (Ω) Funciones continuas en Ω con soporte compacto
C k (Ω) Funciones de clase k en Ω
C0k (Ω) Funciones de C k (Ω) con soporte compacto
C ∞ (Ω) Funciones indefinidamente diferenciables en Ω
C0∞ (Ω) = D(Ω) Funciones de C ∞ (Ω) con soporte compacto
6

Introducción
7
8
Parte I

Elementos básicos de MATLAB

9
Capı́tulo 1

Introducción a MATLAB.

Vamos a optar por MATLAB para programar los algoritmos que aparecen asociados
a los distintos métodos numéricos que estudiaremos a lo largo del curso. La razón de ello
es que, para propósitos docentes, existen ventajas en la utilización de este software de
cálculo matemático respecto a lenguajes de programación como C o Fortran: por un lado,
la sintaxis de programación de MATLAB es bastante similar a la de cualquier lenguaje
de alto nivel, lo que nos permitirá adaptarnos a las peculiaridades de su programación
sin demasiados problemas; por otro lado, podremos utilizar como test de nuestros propios
algoritmos las rutinas especı́ficas que incorpora MATLAB; por último, la integración
de una serie de paquetes gráficos en el entorno de programación de MATLAB facilita
extraordinariamente la tarea de presentación de resultados.
Los ejemplos que se incluyen en las secciones siguientes se han obtenido con la versión
6.0 (R12) de MATLAB. Al tratarse de ejercicios sencillos, no es esperable diferencias
significativas cuando se reproduzcan en versiones posteriores de MATLAB.
La aritmética de trabajo en MATLAB es doble precisión (concepto que aclararemos
posteriormente) aunque la versión 7.0 ha introducido la posibilidad de trabajar con arit-
mética entera y real de precisión simple.
Una vez hemos accedido a MATLAB y estando situados en la ventana de coman-
dos, comenzamos nuestro recorrido introductorio. En el texto que sigue a continuación,
cualquier lı́nea que comienza con dos signos >> se utiliza para denotar una lı́nea de
comando MATLAB.

11
12

1.1. Vectores en MATLAB


Casi todos los comandos básicos en MATLAB implican el uso de vectores. Podemos
definir un vector tecleando cada uno de sus elementos o, alternativamente y cuando es
posible, podemos hacerlo especificando el primer elemento, un incremento y el último
elemento. Por ejemplo, para crear un vector cuyos elementos son 0, 2, 4, 6 y 8, podemos
teclear:

>> 0:2:8

ans =

0 2 4 6 8

MATLAB también guarda el último resultado. En el ejemplo previo, se ha creado


una variable “ans”. Para obtener el vector traspuesto, tecleamos:

>> ans’

ans =

0
2
4
6
8

Para guardar los vectores creados de modo que seamos capaces de trabajar con ellos
posteriormente, debemos darles nombre. Por ejemplo, para crear el vector fila v, tecleamos:

>> v = [0:2:8]

v =

0 2 4 6 8
13

>> v

v =

0 2 4 6 8

>> v;
>> v’

ans =

0
2
4
6
8

Podemos darnos cuenta a partir del ejemplo anterior que si finalizamos una lı́nea con
un punto y coma no se muestra el resultado. MATLAB permite también trabajar con
elementos especı́ficos del vector:

>> v(1:3)

ans =

0 2 4

>> v(1:2:4)

ans =

0 4

>> v(1:2:4)’

ans =
14

0
4

Una vez especificada la notación podemos realizar diversas operaciones. Por ejemplo:

>> v(1:3)-v(2:4)

ans =

-2 -2 -2

1.2. Matrices en MATLAB


Damos a continuación una introducción básica a la definición y manipulación de
matrices. La definición de una matriz es análoga a la definición de un vector. Podemos
considerarla como una columna de vectores fila:

>> A = [ 1 2 3; 3 4 5; 6 7 8]

A =

1 2 3
3 4 5
6 7 8

o como una fila de vectores columna:

>> B = [ [1 2 3]’ [2 4 7]’ [3 5 8]’]

B =

1 2 3
15

2 4 5
3 7 8

A estas alturas de la introducción, si hemos estado reproduciendo los ejemplos anteri-


ores tendremos, muy probablemente, una considerable cantidad de variables definidas en
nuestro espacio de trabajo. Si queremos conocer esta información utilizaremos el comando
whos:

>> whos
Name Size Elements Bytes Density Complex

A 3 by 3 9 72 Full No
B 3 by 3 9 72 Full No
ans 1 by 3 3 24 Full No
v 1 by 5 5 40 Full No

Volviendo a las matrices y a la hora de realizar operaciones básicas con las mismas, la
notación que utiliza MATLAB es la usual en álgebra lineal. Obviamente, las operaciones
matriciales deben ser legales si no queremos tener problemas:

>> v = [0:2:8]

v =

0 2 4 6 8

>> A*v(1:3)
??? Error using ==> * Inner matrix dimensions must agree.

>> A*v(1:3)’

ans =

16
16

28
46

Podemos trabajar con diferentes partes de una matriz, al igual que vimos que se podı́a
hacer con vectores. De nuevo, debemos tener cuidado en hacer operaciones permitidas:

>> A(1:2,3:4)
??? Index exceeds matrix dimensions.

>> A(1:2,2:3)

ans =

2 3
4 5

>> A(1:2,2:3)’

ans =

2 4
3 5

Aparte de las operaciones elementales (suma, producto), podemos hacer otras de


interés como por ejemplo, obtener la inversa de una matriz (cuando esta operación tenga
sentido, si no obtendremos un error). Sin embargo, debemos tener cuidado puesto que las
operaciones que se realizan pueden presentar errores de redondeo. En el ejemplo, la matriz
A no es una matriz invertible, pero MATLAB devuelve una matriz como resultado.

>> inv(A)

Warning: Matrix is close to singular or badly scaled.


Results may be inaccurate. RCOND = 4.565062e-18

ans =
17

1.0e+15 *

-2.7022 4.5036 -1.8014


5.4043 -9.0072 3.6029
-2.7022 4.5036 -1.8014

Conviene hacer notar, en este punto, que MATLAB distingue entre mayúsculas y
minúsculas. Esta es otra potencial fuente de problemas cuando trabajamos con algoritmos
complicados:

>> inv(a)
??? Undefined function or variable a.

Otra posible operación es, por ejemplo, la obtención de los valores propios aproxima-
dos de una matriz. Hay dos versiones de esta rutina: una encuentra los valores propios y
la otra encuentra los valores y vectores propios. Si no recordamos cuál es cuál, podemos
obtener más información tecleando eig en la lı́nea de comandos.

>> eig(A)

ans =

14.0664
-1.0664
0.0000

>> [v,e] = eig(A)

v =

-0.2656 0.7444 -0.4082


-0.4912 0.1907 0.8165
-0.8295 -0.6399 -0.4082
18

e =

14.0664 0 0
0 -1.0664 0
0 0 0.0000

>> diag(e)

ans =

14.0664
-1.0664
0.0000

Existen también rutinas que permiten encontrar soluciones de ecuaciones. Por ejem-
plo, si Ax = b y queremos encontrar x, un modo “lento” de hacerlo es, simplemente,
invertir A y realizar una multiplicación por la izquierda sobre ambos lados de la ecuación.
Obviamente, hay métodos más eficientes y más estables para hacer esto (descomposiciones
L/U con pivotes, por ejemplo). MATLAB tiene comandos especiales para hacer esto.
MATLAB posee además dos tipos diferentes de operadores / y \. La acción del primer
operador es la siguiente: x = A\v ≡ A−1 v; la acción del segundo operador es: x = v/B ≡
vB −1 . Se dan ejemplos de su uso a continuación:

>> v = [1 3 5]’

v =

1
3
5

>> x = A\v

Warning: Matrix is close to singular or badly scaled.


19

Results may be inaccurate. RCOND = 4.565062e-18

x =

1.0e+15 *

1.8014
-3.6029
1.8014

>> x = B\v

x =

2
1
-1

>> B*x

ans =

1
3
5

>> x1 = v’/B

x1 =

4.0000 -3.0000 1.0000

>> x1*B
20

ans =

1.0000 3.0000 5.0000

Finalmente, si queremos borrar todos los datos del sistema y comenzar de nuevo uti-
lizaremos el comando clear. Conviene utilizar este comando con cuidado porque MATLAB
no pide una segunda opinión ...

>> clear

>> whos

1.3. Funciones de vectores


Es indudable que la gran ventaja de trabajar con MATLAB es la facilidad de ma-
nipulación de vectores y matrices. En este apartado comenzaremos con manipulaciones
simples (suma, resta, multiplicación). A continuación mostramos cómo se pueden definir
operaciones relativamente complejas con un pequeño esfuerzo.
Comenzamos con la suma y resta de vectores. Definiremos dos vectores y a conti-
nuación los sumaremos y restaremos:

>> v = [1 2 3]’

v =

1
2
3

>> b = [2 4 6]’

b =

2
21

4
6

>> v+b

ans =

3
6
9

>> v-b

ans =

-1
-2
-3

La multiplicación de vectores y matrices sigue, lógicamente y como comentamos an-


teriormente, reglas estrictas, ası́ como la suma. En el ejemplo anterior los vectores son
ambos vectores columna con tres componentes:

>> v*b
Error using ==> * Inner matrix dimensions must agree.

>> v*b’

ans =

2 4 6
4 8 12
6 12 18

>> v’*b

ans =
22

28

Hay ocasiones en las que queremos realizar una operación sobre cada elemento de un
vector o matriz. MATLAB permite hacer este tipo de operaciones. Por ejemplo, supon-
gamos que queremos multiplicar cada componente de un vector v por la correspondiente
componente del vector b. En otras palabras, Supongamos que queremos hallar v(1)*b(1),
v(2)*b(2) y v(3)*b(3). Estarı́a bien utilizar el sı́mbolo * puesto que estamos haciendo un
tipo de multiplicación. Sin embargo, como este sı́mbolo ha sido definido con otra función,
debemos recurrir a otra cosa. Los programadores ocupados del desarrollo de MATLAB
decidieron entonces utilizar los sı́mbolos .* para hacer esta operación. De hecho, se puede
emplear este sı́mbolo antes de cualquier sı́mbolo matemático para especificar a MATLAB
que la operación en cuestión debe tener lugar sobre cada entrada del vector.

>> v.*b

ans =

2
8
18

>> v./b

ans =

0.5000
0.5000
0.5000

Puesto que hemos comenzado a hablar de operaciones no lineales continuemos con


ellas: si pasamos un vector a una operación matemática predefinida, obtendremos un
vector del mismo tamaño con entradas obtenidas realizando la operación especificada
sobre la correspondiente entrada del vector original:

>> sin(v)
23

ans =

0.8415
0.9093
0.1411

>> log(v)

ans =

0
0.6931
1.0986

La posibilidad de trabajar con estas funciones vectoriales es una de las ventajas de


MATLAB. De este modo, podemos definir operaciones complejas rápida y fácilmente. En
el siguiente ejemplo trabajamos con un vector con muchas componentes:

>> x = [0:0.1:100]

x =

Columns 1 through 7

0 0.1000 0.2000 0.3000 0.4000 0.5000 0.6000

(stuff deleted)

Columns 995 through 1001

99.4000 99.5000 99.6000 99.7000 99.8000 99.9000 100.0000

>> y = sin(x).*x./(1+cos(x));

Además de esta simple manipulación de vectores, MATLAB nos permitirá también


representar gráficamente los resultados que obtengamos. Tecleando,
24

>> plot(x,y)

tendremos una representación gráfica de la función antes considerada. Si tecleamos

>> plot(x,y,’rx’)

obtenemos la misma gráfica pero las lı́neas son reempladas por puntos rojos en forma de
x. Para ver más opciones del commando plot, podemos teclear

>> help plot

El comando help es, sin duda, el camino más corto para estar seguro de la sintaxis de un
determinado comando de MATLAB.
La notación compacta permitirá realizar un gran número de operaciones utilizando
pocos comandos. Veamos el siguiente ejemplo:

>> coef = zeros(1,1001);


>> coef(1) = y(1);
>> y = (y(2:1001)-y(1:1000))./(x(2:1001)-x(1:1000));
>> whos
Name Size Elements Bytes Density Complex

ans 3 by 1 3 24 Full No
b 3 by 1 3 24 Full No
coef 1 by 1001 1001 8008 Full No
v 3 by 1 3 24 Full No
x 1 by 1001 1001 8008 Full No
y 1 by 1000 1000 8000 Full No

Grand total is 3011 elements using 24088 bytes

>> coef(2) = y(1);


>> y(1)

ans =

0.0500
25

>> y = (y(2:1000)-y(1:999))./(x(3:1001)-x(1:999));
>> coef(3) = y(1);
>>
>>

A partir de este algoritmo podemos encontrar el polinomio de Lagrange que interpola


los puntos definidos anteriormente (vector x). Por supuesto, con tantos puntos el proceso
puede resultar algo tedioso. Afortunadamente MATLAB dispone de un modo sencillo de
hacer tareas monótonas, como veremos a continuación.

1.4. Bucles
En esta sección veremos cómo utilizar los bucles for y while. En primer lugar, dis-
cutiremos el uso del bucle for con ejemplos para operaciones fila sobre matrices. A con-
tinuación, mostraremos el uso del bucle while.
El bucle for permite repetir ciertos comandos. Todas las estructuras de bucles en
MATLAB comienzan con una palabra clave (como for o while) y terminan con un end
(parece sencillo, ¿no?). Veamos un ejemplo trivial:

>> for j=1:4,


j
end

j =

j =

j =
26

j =

>>

Otro ejemplo:

>> v = [1:3:10]

v =

1 4 7 10

>> for j=1:4,


v(j) = j;
end
>> v

v =

1 2 3 4

Éste es un ejemplo simple y una demostración bonita de cómo funcionan los bucles
for. Sin embargo, no se debe utilizar en la práctica: la notación utilizada en la primera
lı́nea es mucho más rápida que el bucle. Un mejor ejemplo se presenta a continuación,
donde realizamos operaciones sobre las filas de una matriz. Si queremos comenzar en
la segunda fila de una matriz y restar la fila previa y repetir esta operación sobre las
siguientes filas, un bucle for puede ocuparse de esto:

>> A = [ [1 2 3]’ [3 2 1]’ [2 1 3]’]

A =
27

1 3 2
2 2 1
3 1 3

>> B = A;
>> for j=2:3,
A(j,:) = A(j,:) - A(j-1,:)
end

A =

1 3 2
1 -1 -1
3 1 3

A =

1 3 2
1 -1 -1
2 2 4

Presentamos a continuación un ejemplo más realista (implementación de eliminación


Gaussiana):

>> for j=2:3,


for i=j:3,
B(i,:) = B(i,:) - B(j-1,:)*B(i,j-1)/B(j-1,j-1)
end
end

B =

1 3 2
0 -4 -3
3 1 3
28

B =

1 3 2
0 -4 -3
0 -8 -3

B =

1 3 2
0 -4 -3
0 0 3

La forma general de un bucle while es

>> while (condiciones)


(operaciones)
end

Las operaciones se repetirán mientras que las condiciones sean ciertas. Por ejemplo,
dado un número a, el siguiente bloque permite calcular y mostrar el entero no negativo
más pequeño tal que 2n ≥ a:

>> n=0;
>> while 2^n < a
n=n+1;
end
>> n

1.4.1. Relaciones
Los operadores de relación en MATLAB son:
29

< menor que


> mayor que
<= menor o igual que
>= mayor o igual que
== igual que
∼= distinto a
Démonos cuenta que el sı́mbolo “==” se utiliza en lugar de “=” en una relación.
Podemos conectar varias relaciones utilizando los operadores lógicos:

& y
| o
∼ no

1.5. La instrucción if
La forma general de una instrucción if simple es:
>> if (condiciones)
(operaciones)
end
Las operaciones se realizarán únicamente si se cumplen las condiciones especificadas.
Se admiten las ramificaciones múltiples como puede verse en el siguiente ejemplo:
>> if n <0
a=1;
elseif n<5
a=2;
else
a=3;
end

1.6. Ficheros ejecutables


En esta sección introducimos los conceptos básicos para crear ficheros ejecutables. Los
ficheros ejecutables son ficheros de texto que incluyen una serie de comandos MATLAB.
30

Si una tarea MATLAB la vamos a ejecutar muchas veces, es buena idea escribir un fichero
con estos comandos para poder ejecutarlos tantas veces como queramos.
La edición del fichero ejecutable la realizamos con un editor cualquiera. Si nos resulta
más cómodo, podemos utilizar el editor que incorpora MATLAB y al que invocaremos
desde la lı́nea de comandos como:
>>edit
Los ficheros ejecutables de MATLAB (llamados ficheros M) deben tener como exten-
sión “.m”. En el ejemplo que damos a continuación, crearemos un programa que calcula
el factorial de 6:
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%Este es un programa no muy util,
%que calcula el factorial de 6
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
n=6; fac=1; for i=2:n
fac=fac*i;
end fac
Si guardamos esto en el fichero fac.m en el directorio de trabajo (o cualquier otro
incluido en el “path”) y tecleamos el comando fac, obtenemos
>> fac

fac =

720

Las lineas tras el sı́mbolo “ %” son lı́neas de comentario, que se conviene utilizar como
explicación del programa. El comando help sirve para mostrar esas lı́neas:
>> help fac

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Este es un programa no muy util,
que calcula el factorial de 6
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
31

En efecto, este no es un programa muy útil, en primer lugar porque el propio MAT-
LAB tiene su comando para calcular el factorial de números enteros:

)) factorial(6)

ans =

720

y en segundo lugar porque nuestro programa sólo calcula el factorial de 6. Para poder
calcular el factorial para distintos números deberemos crea una subrutina o función MAT-
LAB.

1.7. Subrutinas
Si ahora escribimos en un fichero con nombre facf.m los siguientes comandos

%Esta es una funcion para calcular


%el factorial de n.
%El valor de entrada es n
function fac=facf(n) fac=1; for i=2:n
fac=fac*i;
end

habremos definido una función que podemos utilizar tal como lo hacemos con los comandos
intrı́nsecos de MATLAB. Por ejemplo, tecleando en la linea de comandos facf(6) tenemos:

>> facf(6)

ans =

720

Las funciones pueden tener varios parámetros de entrada y/o salida. Por ejemplo,
la siguiente es una función que, dados dos vectores con la misma longitud, devuelve dos
valores (es decir, la subrutina implementa una función f : Rn × Rn → R2 ).
32

%Esta es una funcion que, dados


%dos vectores de la misma longitud
%calcula dos valores reales a y b
%a=sin(x*y’), b=a*x*x´
function [a,b]=funci(x,y) a=sin(x*y’); b=a*x*x’;

Guardamos este fichero como funci.m y, como prueba, ejecutamos en la lı́nea de


comandos:

>> x=1:1:5

x =

1 2 3 4 5

>> y=0:0.1:0.4

y =

0 0.1000 0.2000 0.3000 0.4000

>> [f1,f2]=funci(x,y);
>> f1

f1 =

-0.7568

>> f2

f2 =

-41.6241

Por supuesto, si las matrices de entrada x e y no son vectores de la misma longitud,


el programa puede fallar. En el siguiente ejemplo, la primera llamada a funci es correcta,
pero no ası́ la segunda:
33

>> [a,b]=funci(1:1:5,0:1:4);
>> [a,b]=funci(1:1:5,0:1:5);
??? Error using ==> * Inner matrix dimensions must agree.

Error in ==> c:\matlab\temporal\funci.m On line 6 ==>


a=sin(x*y’);

Importante:

– Fijémonos que la sintaxis de las rutinas es:

function [output1,output2,...]=nombre(input1,input2,...)

– Si queremos crear un fichero independiente que contenga a la rutina, el nombre que


asignemos al fichero debe ser el mismo que el de la rutina con extensión “.m”.

Por supuesto, las funciones ası́ definidas pueden ser utilizadas tantas veces como sea
necesario dentro de otras funciones y programas.
Una norma muy conveniente que conviene seguir es será escribir todos los progra-
mas en ficheros de texto (utilizando un editor simple) y procurar que estos sean lo más
estructurados posibles, utilizando subrutinas (funciones) para implementar tareas inde-
pendientes. Se trata de programar de la forma más modular y estructurada posible.

1.8. Cadenas de texto, mensajes de error, entradas


Se pueden introducir cadenas de texto en MATLAB si van entre comillas simples.
Por ejemplo, la instrucción

>> s=’Mi asignatura preferida es Calculo Numerico’

asigna a la variable s la anterior cadena de texto.


Se pueden mostrar cadenas de texto utilizando la función disp. Por ejemplo:

>> disp(’En particular, me encantan las practicas de la asignatura’)

Los mensajes de error pueden (deben) mostrarse utilizando la función error


34

>> error(’Fue un error no acabar las memorias a tiempo’)

puesto que cuando se utiliza en un fichero “.m”, interrumpe la ejecución del mismo.
Podemos, asimismo, en un fichero “.m” pedir que un usuario introduzca datos de
forma interactiva utilizando la función input. Cuando, por ejemplo, durante la ejecución
de un programa aparece la lı́nea

>>ifaltas =input(’Cuantas veces has faltado a practicas?’)

el citado mensaje de texto aparece en pantalla y la ejecución del programa se interrumpe


hasta que el usuario teclea el dato de entrada. Después de presionar la tecla de “return”,el
dato es asignado a la variable “ifaltas” y se reanuda la ejecución del programa.

1.9. Comparando la eficiencia de algoritmos: cputime


Una forma adecuada de determinar la eficiencia de un algoritmo es medir el tiempo
de CPU transcurrido en la ejecución del mismo. Este tiempo puede obtenerse utilizando
el comando cputime.
De hecho, la secuencia

>> cpu1=cputime, (cualquier conjunto de operaciones), cpu2=cputime-cpu1

nos proporcionará el tiempo de cpu invertido en la ejecución del mencionado conjunto de


operaciones.

1.10. Formatos de salida


Mientras que todos los cálculos en MATLAB se realizan en doble precisión, el formato
de los datos de salida puede ser controlado con los siguientes comandos:

f ormat short Punto fijo y 4 decimales (es el que hay por defecto)
f ormat long Punto fijo y 14 decimales
f ormat short e notación cientı́fica con 4 decimales
f ormat long e notación cientı́fica con 14 decimales
35

1.11. Gráficos
Aunque ya hemos mencionado anteriormente la utilización del comando plot, vamos
a dar en esta sección algún detalle adicional sobre las posibilidades gráficas de MATLAB.
MATLAB permite generar representaciones gráficas de curvas en 2D y 3D. Los co-
mandos básicos con los que nos manejaremos serán plot, plot3, mesh y surf.

1.11.1. Representaciones en 2D
El comando plot crea gráficos de curvas en el plano x-y; si x e y son vectores de la
misma longitud, el comando plot(x,y) abre una ventana gráfica y dibuja en el plano x-y
los elementos de x versus los elementos de y. Podemos, por ejemplo, dibujar el gráfico de
la función seno en el intervalo -4 a 4 con los siguientes comandos:

x=-4:.01:4; y=sin(x); plot(x,y)

El vector x es una partición del dominio en intervalos de longitud 0.01; el vector y es


un vector que proporciona los valores del seno en los nodos de la partición.
2
Como segundo ejemplo vamos a dibujar el gráfico de y = e−x en el intervalo -1.5 a
1.5:

x=-1.5:.01:1.5; y=exp(-x.^2); plot(x,y)

Démonos cuenta de que la operación de elevar al cuadrado está precedida por un


punto para que opere sobre cada una de las componentes del vector x.
MATLAB posee además la utilidad fplot para representar de forma eficiente y sim-
ple el gráfico de una función: para representar el ejemplo anterior podemos, de forma
alternativa, definir la función en un fichero M (que llamaremos, por ejemplo, expcu.m):

function y=expcu(x) y=exp(-x.^2)

Entonces el comando

fplot(’expcu’,[-1.5,1.5])

nos proporcionará el gráfico en cuestión.


Podemos generar también gráficos de curvas definidas paramétricamente. Por ejem-
plo,
36

t=0:.001:2*pi; x=cos(3*t); y=sin(2*t); plot(x,y)

De forma complementaria, podemos asignar a los gráficos: tı́tulos, etiquetas en los


ejes y texto (en la zona del gráfico). Para ello utilizaremos los comandos

title tı́tulo del gráfico


xlabel etiqueta del eje x
ylabel etiqueta del eje y
gtext sitúa texto sobre el gráfico utilizando el ratón
text sitúa texto en las coordenadas especificadas
Ejemplo, el comando:

title(’Hola caracola’)

proporciona al gráfico el tı́tulo en cuestión.


Los ejes del gráfico son, por defecto, autoescalados. Para modificar esto podemos
utilizar el comando axis:

axis([xmin,xmax,ymin,ymax])

El comando axis debe utilizarse después de plot.


Es posible realizar distintas representaciones en un mismo gráfico. Por ejemplo,

x=0:.01:2*pi;y1=sin(x); y2=sin(2*x); y3=sin(4*x);


plot(x,y1,x,y2,x,y3)

Otra posibilidad es utilizar hold. El comando hold on “congela” la terminal gráfica


en la que estamos trabajando, de modo que se pueden superponer diversos gráficos en
ella. Los ejes pueden, sin embargo, ser reescalados. El comando hold off “descongela” la
terminal gráfica.
Dentro de las opciones gráficas podemos elegir el tipo de lı́nea, el tipo de punto y el
color. Por ejemplo,

x=0:.01:2*pi;y1=sin(x); y2=sin(2*x); y3=sin(4*x);


plot(x,y1,’--’,x,y2,’:’,x,y3,’+’)
37

dibuja una lı́nea discontinua y punteada, respectivamente, para los dos primeros gráficos
mientras que el tercer gráfico se muestra con el sı́mbolo “+”. Los tipos de lı́nea y marca
son:
Tipos de lı́nea: sólida (-), discontinua (–), punteada (:), discontinua y punteada (-.)
Tipos de marca: punto (.), mas (+), estrella (*), cı́rculo (o), x (x)
Se pueden especificar colores para los distintos tipos de lı́nea y marca:
Colores: amarillo (y), magenta (m), rojo (r), verde (g), azul (b), blanco (w), negro
(k)
El comando subplot puede utilizarse para hacer una partición de la terminal gráfica,
de modo que pueden situarse varios subgráficos en una misma figura.

1.11.2. Representaciones en 3D
Gráficos de lı́nea
El comando plot3 en 3 dimensiones es el análogo al comando plot en 2 dimensiones:
produce curvas en el espacio tridimensional. Si x, y y z son vectores del mismo tamaño,
entonces el comando plot3(x,y,z) producirá un gráfico de perspectiva de la curva en el
espacio tridimensional que pasa por los puntos especificados por x, y y z. Estos vectores
suelen estar definidos de forma paramétrica. Por ejemplo,

t=.01:.01:2*pi; x=cos(t); y=sin(t); z=t.^3; plot3(x,y,z)

proporciona una hélice que está comprimida cerca del plano x-y.

Gráficos de malla y de superficie


Pueden obtenerse gráficos tridimensionales mallados de superficies utilizando el co-
mando mesh. Si tecleamos mesh(z) obtenemos un gráfico de perspectiva tridimensional
de los elementos de la matriz z. La superficie representada está definida por las coorde-
nadas z de los puntos sobre un retı́culo rectangular en el plano x-y. El siguiente ejemplo
muestra un gráfico de estas caracterı́sticas de los elementos de la matriz identidad 10 × 10
(comando eye(10)):

mesh(eye(10))

Análogamente pueden obtener gráficos tridimensionales “compactos” de superficies


utilizando el comando surf:
38

surf(eye(10))

Para dibujar el gráfico de una función z = f (x, y) sobre un rectángulo debemos,


en primer lugar, definir vectores xx e yy que proporcionan particiones sobre los lados
del rectángulo. Con la función meshgrid creamos una matriz x, cada fila de la misma
contiene las componentes del vector xx y cuyo número de columnas es igual a la longitud
del vector yy. De manera análoga creamos la matriz y, cuyas columnas contienen las
componentes del vector yy. Esto lo conseguimos con la instrucción:

[x,y]=meshgrid(xx,yy);

Una vez hecho esto, obtenemos la matriz z haciendo actuar f sobre las matrices x
e y. La representación de la matriz z se puede hacer acudiendo a los comandos mesh y
surf.
Veamos un ejemplo:
2 2
Vamos a dibujar el gráfico de z = e−x −y sobre el cuadrado [−2, 2] × [−2, 2] del
siguiente modo:

xx=-2:.2:2; yy=xx; [x,y]=meshgrid(xx,yy); z=exp(-x.^2-y.^2);


mesh(z)

Las caracterı́sticas del comando axis introducido previamente son aplicables también
a los gráficos tridimensionales, ası́ como lo son las de los comandos para tı́tulos, etiquetado
de ejes y el comando hold.
El color de las superficies se ajusta utilizando el comando shading. Hay 3 opciones
para este comando: faceted (el que está por defecto), interpolated y flat. Se accede a
estas opciones tecleando:

shading faceted, shading interp, shading flat

El comando shading debe introducirse después del comando surf. La utilización de


shading interp y shading flat causa la desparición del mallado en la superficie.
El perfil de colores de una superficie se controla mediante el comando colormap.
Mapas de colores predefinidos son:

hsv (por defecto), hot, cool, jet, pink, copper, flag, gray, bone

Por ejemplo, el comando colormap(cool) proporciona un determinado perfil de col-


ores en la figura en cuestión.
39

1.12. Resumen de funciones elementales y matrices


especiales
En la tabla que se muestra a continuación se presentan algunas funciones de MATLAB
que nos pueden ser de utilidad:

abs valor absoluto o módulo


sqrt raı́z cuadrada
real parte real
imag parte imaginaria
conj complejo conjugado
exp exponencial
log logaritmo natural
log10 logaritmo en base 10
sin, asin, sinh, asinh seno, arcseno, seno hiperb., arcseno hiperb.
cos, acos, cosh, acosh idem para el coseno
tan, atan, tanh, atanh idem para la tangente
cot, acot, coth, acoth idem para la cotangente
sec, asec, sech, asech idem para la secante
csc, acsc, csch, acsch idem para la cosecante

Finalmente, algunas matrices especiales de MATLAB:

zeros matriz de ceros


ones matriz de unos
eye identidad
diag diagonal
rand números aleatorios distribuidos unif.
40
Parte II

Introducción a la construcción de
algoritmos

41
Capı́tulo 2

Sistemas de numeración; errores y


sus fuentes

En este tema se introducen los distintos sistemas de numeración y se describe cómo


realizar la conversión entre distintos sistemas, ası́ como el estándar IEEE de representación
de números en el sistema binario. A continuación, se introducen las nociones de error abso-
luto y relativo y se analizan las causas más frecuentes de error en un cálculo computacional,
ilustrándolo con ejemplos. Finalmente, se introducen los conceptos de (in)estabilidad y
condición.

2.1. Sistemas de números y conversiones


Es necesario conocer como se representan los números enteros y decimales en forma-
to digital para entender las limitaciones intrı́nsecas que nos encontraremos al programar
un algoritmo numérico, tanto en cuanto a la mayor precisión alcanzable como en cuanto
al rango de valores admisibles. Veremos además cómo un mı́nimo conocimiento del sis-
tema de representación estándar sirve para evitar la aparición de desastrosos errores de
programación (como bucles infinitos).

2.1.1. Sistemas de numeración en base q


El sistema de numeración decimal es el sistema posicional de numeración más uti-
lizado, que utiliza como base de numeración el 10 (dı́gitos 0...9). Como es bien sabido, se
puede utilizar cualquier número natural mayor que 1 como base de numeración.

43
44

Un número en base q se denota como (an an−1 ...a1 a0 .b1 b2 ...bk ...)q donde ai y bj pertenecen
al conjunto de los q dı́gitos elementales 1 . Estos q dı́gitos representarán valores desde 0
hasta q − 1. La conversión a decimales es, por definición:

(an an−1 ...a1 a0 .b1 b2 ...bk ...)q = an q n + an−1 q n−1 + ... + a1 q + a0 q 0


(2.1)
+b1 q −1 + b2 q −2 + ... + bk q −k + ...
El sistema natural de numeración digital es el binario (base 2), utilizando sólo los
dı́gitos 0 y 1.

Ejemplo 2.1.1. Decimal: (123.25)10 = 1 × 102 + 2 × 101 + 3 × 100 + 2 × 10−1 + 5 × 10−2 .

Binario (base 2) con 2 dı́gitos 0 y 1: 0 + 1 = 1, 1 + 1 = 10.

(1011.01)2 = 1 × 23 + 0 × 22 + 1 × 21 + 1 × 20 + 0 × 2−1 + 1 × 2−2 = 11.25.

Hexadecimal (base 16) tiene 16 dı́gitos: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E y F ,


que representan los valores desde 0 hasta 15 respectivamente. Por tanto:

(7B.4)15 = 7 × 161 + B × 160 + 4 × 16−1 = 123.25.

Los sistemas de numeración octal y hexadecimal, también son muy utilizados, en


parte debido a que es muy sencilla su conversión a binario con la ventaja de que un
mismo número se representa con menos cifras cuanto mayor es la base.

2.1.2. Conversión de base decimal a base q


Ya sabemos como convertir un número en base q a base decimal (2.1). La conversión
de base decimal a base q se base en el hecho de que, acudiendo a la definición (2.1)
observamos que (eliminamos el subı́ndice 10 para números en base decimal):

(an an−1 ...a1 a0 .b1 b2 ...bk ...)q × q = (an an−1 ...a1 a0 b1 .b2 b3 ...bk ...)q
(2.2)
(an an−1 ...a1 a0 .b1 b2 ...bk ...)q × q −1 = (an an−1 ...a1 .a0 b1 b2 ...bk ...)q
1
Vamos a utilizar el punto decimal (.) en lugar de la coma decimal (′ ) para ser consistentes con los
ejemplos MATLAB.
45

Utilizaremos esto para obtener la conversión de un número en base 10 a base 2. Para


esto, conviene separar la conversión de la parte entera (antes del punto decimal) de la
fraccionaria (después del punto); observemos que un número entero es necesariamente
entero en cualquier base y que un número fraccionario lo es en cualquier base.

Conversión entera
De (2.2) deducimos que:

(an ...a0 )q −1 = (an ...a1 )q + (.a0 )q


es decir, que

(an ...a0 )q = (an ...a1 )q × q + (.a0 )q × q = (an ...a1 )q × q + (a0 )q (2.3)

que es una identidad entre números, que podemos evaluar en cualquier base, y en par-
ticular en base 10 (obsérvese que de hecho mezclamos base números en distintas bases).
Vemos pues que al dividir un número entero entre q el resto es el dı́gito menos significativo
del número en base q. Dividiendo sucesivamente (hasta llegar al cociente 0) obtenemos
los sucesivos dı́gitos del número en base q.

Conversión fraccionaria
A partir de (2.2) vemos que:

(.b1 b2 ...bk )q × q = (b1 )q + (.b2 ...bk )q


Vemos pues que la parte entera del resultado de multiplicar nuestro número por q
es el primer dı́gito tras el punto decimal. Reteniendo la parte fraccionaria que resulta en
cada paso y repitiendo sucesivamente el proceso, vamos obteniendo el resto de cifras tras
el punto.

Ejemplo 2.1.2. Escribamos (26.1)10 en base 2.

1. Parte entera. Dividiendo sucesivamente, tenemos que:

26 = 2 × 13 + 0 ; 13 = 2 × 6 + 1 ; 6 = 2 × 3 + 0 ; 3 = 2 × 1 + 1 ; 1 = 2 × 0 + 1

Leyendo de izquierda a derecha los números subrayados: (26)10 = (11010)2


46

2. Parte fraccionaria. Multiplicando sucesivamente por dos y separando la parte frac-


cionaria:

0.1×2 = 0.2 ; 0.2×2 = 0.4 ; 0.4×2 = 0.8 ; 0.8×0.2 = 1.6 ; 0.6×2 = 1.2 ; 0.2×2 = ...

Leyendo de izquierda a derecha tenemos los dı́gitos de la parte fraccionaria (sub-


rayados) luego:
(0.1)10 = (0.00011)2
donde las cifras subrayadas son las cifras periódicas.
Obsérvese que el número 0.1 tiene infinitas cifras distintas de cero cuando se escribe
en base 2 (se repiten indefinidamente desde la segunda a la quinta cifra fraccionaria).

Sumando los resultados de la parte entera y la fraccionaria tenemos que

(26.1)10 = (11010.00011)2

En el anterior ejemplo hemos aprendido que un número fraccionario con un número


finito de dı́gitos en cierta base no tiene por qué tener un número finito en otra base. En
efecto, (0.1)10 es un número periódico con infinitas cifras cuando se escribe en base 2. Esto
es importante puesto que en un procesador digital los números se almacenar en binario
utilizando un número finito de cifras. Esto quiere decir que el número 0.1 no se representa
exactamente en un ordenador. Esto hay que tenerlo en cuenta para evitar errores que
conduzcan a bucles que se repiten indefinidamente. El siguiente algoritmo en Matlab es
un ejemplo de bucle infinito,

Algoritmo 2.1. Bucle infinito en precisión finita binaria:


x=0;
while x∼=10 (Nota: en Matlab esto significa x 6= 0)
x=x+0.1;
end

Esto nos deberı́a prevenir de utilizar bucles que terminan cuando se alcanza cierto
valor concreto de una variable que involucra números no enteros.

Ejercicio 2.2. Si en la tercera lı́nea del anterior algoritmo cambiamos 0.1 por 0.125,
¿seguirı́amos teniendo un bucle infinito?.
47

2.1.3. Estándar IEEE de representación de números enteros y


en coma flotante
Números enteros
Asumamos, como es común en la mayor parte de los procesadores, que cada palabra
tiene una longitud de 32 bits, es decir, que cada número vendrá representado por 32
dı́gitos binarios. La forma más sencilla de representar un número entero es asignando uno
de los 32 bits para designar el signo (0 para +, 1 para menos) y utilizando las 31 restantes
posiciones para representar los dı́gitos del módulo del número entero. En consecuencia, el
mayor número entero que se puede representar es 231 − 1.
Éste es el estándar IEEE salvo porque, por lo general, no se suele reservar un bit para
el signo, sino que se utiliza una ordenación distinta para guardar los enteros negativos.
Sin embargo, en la práctica una y otra prescripción son prácticamente equivalentes.
También pueden utilizarse enteros en longitud doble, utilizando dos palabras de 32
bits, con lo que se aumenta el rango de enteros admisibles.

Números no enteros
Para representar números con parte fraccionaria se utiliza el formato de punto (o
coma) flotante. Esta representación es la correspondiente versión binaria de la conocida
notación cientı́fica o exponencial para los números decimales:

±M × 10E , 1 ≤ M < 10
pero utilizando base 2:
x = ±M × 2E , 1 ≤ M < 2
donde M es la mantisa y E el exponente. Inevitablemente el número de dı́gitos que se
pueden almacenar de la mantisa y exponente es limitado. Hay dos tipos de precisión, la
simple y la doble, que difieren en el número de bits de los que se dispone para almacenar
las cifras; la distribución estándar es:

1. Precisión simple: 32 bits, de los cuales:

a) uno corresponde al signo,


b) 8 al exponente, luego −128 ≤ E ≤ 127 (2 × 128 = 28 )
c) 23 a la mantisa
48

2. Precisión doble

a) uno para el signo


b) 11 al exponente (−1022 ≤ E ≤ 1023)
c) 52 para la mantisa

Ejemplo 2.1.3. Veamos de forma simplificada como se guardarı́a el número 5.5 en pre-
cisión simple. Primero lo pasamos a binario: 5.5 = (101.1)2 y normalizamos para que la
mantisa 1 < M ≤ 2, de forma que 5.5 = (1.011)2 × 22 , que tiene exponente E = 2 (que
se guardará en binario), signo + (bit 0) y mantisa 1.011. Normalmente, salvo para el
caso del número cero, que se almacenan de distinta forma, el número delante del punto
será siempre 1, por lo que no se suele almacenar.

Ejemplo 2.1.4. El anterior es un ejemplo de un número decimal que se puede guardar


de forma exacta en una palabra de 32 bits. Un ejemplo en el que esto no serı́a ası́ es
el ya conocido caso de 0.1 = (0.00011)2 = (1.1001)2 × 2−4 , que necesariamente ha de
redondearse antes de almacenarse; esquemáticamente (sin entrar en detalles de como se
almacena el exponente), tendrı́amos, en precisión simple, la representación (recordemos
que el 1 antes del punto decimal no se suele almacenar)

0|E = −4|10011001100110011001101

donde la 23a cifra tras el punto se ha redondeado a 1 porque la siguiente era 1. De esta
forma, lo que realmente se almacena es:

(1.10011001100110011001101)2 × 24 = 0.10000000149011611938

Esto muestra explı́citamente que, efectivamente, el algoritmo 2.1 es un bucle infinito.

Debemos resaltar que la especificación del número de bits que se utilizan para al-
macenar mantisa y exponente es lo único que necesitamos para determinar cuales son
los mayores y menores números que se pueden almacenar en coma flotante ası́ como la
máxima precisión que se puede obtener. Veamos como obtener estos parámetros en el caso
de doble precisión (que es la precisión utilizada por Matlab).

Ejemplo 2.1.5. Obtener los números de “overflow” y “underflow” ası́ como la precisión
máquina en doble precisión, es decir:
49

1. Obtener el mayor número entero positivo representable en formato de coma flotante


en el caso de doble precisión (lı́mite de “overflow”)
Puesto que el mayor exponente es 1023, el mayor número representable es (1.1....1)2 ×
21023 ≃ 1.8 × 10308 (hay 52 unos detrás del punto “decimal”).

2. Obtener el menor número entero positivo representable en formato de coma flotante


en el caso de doble precisión (lı́mite de “overflow”). El menor decimal representable
con 52 dı́gitos significativos binarios es (1.00...01)2 ×10−1022 ≃ 2.23×10−308 . Sin em-
bargo, el estándar IEEE admites números más pequeños, aunque tengan menos cifras
significativas (es lo que llaman números sub-normales); estrictamente el número
más pequeño representable en coma flotante es: (0.00..01)2 × 2−1022 = 2−1074 ≃
4.94 × 10−324 (52 dı́gitos tras la coma decimal). Para no perder dı́gitos significativos
es mejor moverse en el rango (1/Nov , Nov ), donde Nov es el número de overflow
Nov ≃ 2.23 × 10−308 .

3. Obtener la diferencia entre 1 y el menor número positivo mayor que 1 en coma


flotante (épsilon-máquina).
El número que se nos pide es (1.0...01)2 − (1.0...0)2 = (0.0...01)2 = 2−52 ≃ 2.2 ×
10−16 . Este número representa el mejor error relativo que se puede manejar en
doble precisión (definimos este conocido concepto a continuación). Este valor de
épsilon-maquina significa que todos los números con 15 cifras significativas se pueden
guardar de forma exacta en doble precisión y que ocurre lo mismo para la mayor
parte de los números de 16 cifras.

Nota 2.3. Es importante tener en cuenta que MATLAB trabaja en doble precisión2
aunque por defecto sólo muestre 5 cifras significativas. Para que el sistema muestre más
cifras se puede utilizar el comando “format long”.

Además de los detalles señalados hay otras caracterı́sticas fundamentales del estándar
IEEE que no describiremos como son:

1. Redondeo correcto de la aritmética (volveremos más adelante sobre este punto)3 .


2
La versión 7.0 ya permite trabajar en precisión simple y con aritmética entera, aunque doble precisión
sigue siendo la disponible por defecto.
3
Un error en el “hardware” de punto flotante de los Intel Pentium (1994) le dio una considerable mala
publicidad a la compañı́a. El problema fue solventado reemplazando los procesadores defectuosos
50

2. Tratamiento de excepciones. Es decir, que un operación del tipo log(0.0) no da error


e interrumpe la ejecución del programa, sino que se asigna un valor especial para
representar esta excepción (para significar −∞).

3. Compatibilidad entre procesadores. Esta es por supuesto, una de las grandes ven-
tajas de los estándares.

2.2. Errores y sus causas


El análisis numérico trata de la construcción de métodos discretos para la resolución
de problemas continuos. Esta discretización, que se presenta tanto en la representación
numérica en un ordenador como en los propios algoritmos de cálculo, necesariamente
implica la aparición de errores cuyo origen y propagación debemos estudiar 4 .

2.2.1. Definiciones
Si xA es una aproximación del verdadero valor xT , definimos entonces:

Error absoluto: Eabs = |xT − xA |

xA
Error relativo: Erel = |1 − |, si xT 6= 0.
xT
También se pueden utilizar estas definiciones con signo (es decir, sin el valor absoluto).
El error relativo en ocasiones se expresa también en tanto por ciento.

2.2.2. Fuentes de errores


Las fuentes de error en un cálculo numérico pueden ser de variada naturaleza, algunas
de ellas independientes del método numérico empleado.

1. Un métodos numérico para resolver determinado problema cientı́fico puede dar lugar
a resultados erróneos si el modelo no es una fiel descripción de la realidad.
4
No por ello se debe adoptar la visión reduccionista consistente en definir el análisis numérico como
el área de la matemática que analiza los errores de cálculo
51

2. Un algoritmo numérico puede depender de ciertos datos de entrada (por ejemplo,


datos fı́sicos) afectados de cierto error. Se debe vigilar que el método numérico no
sea crucialmente dependiente de la exactitud de tales valores de entrada y que la
precisión de estos valores sea suficiente para nuestros propósitos. En el caso en que
el propio modelo fı́sico/matemático sea muy sensible a estos parámetros, tendremos
un problema mal condicionado e imposible de resolver numéricamente de forma
estable.
3. Los errores de programación son prácticamente inevitables durante la construcción
de un método numérico. Una vez construido un algoritmo numérico que en aparien-
cia funciona correctamente es necesario certificar su funcionamiento mediante todos
los tests que estén a nuestro alcance.
4. Errores en la aritmética de punto flotante: errores de redondeo, pérdida de cifras
significativas por cancelaciones, problemas de “underflow/overflow”. Estos son los
problemas derivados de la representación en punto flotante, en la cual se dispone de
un número finito de bits para representar la mantisa y el exponente. Como ya vimos,
esto limita tanto la mejor precisión relativa alcanzable (15-16 dı́gitos decimales en
doble precisión) como el rango de valores disponible.
Aunque la segunda de las limitaciones no suele presentar graves problemas, es nece-
sario evitar la evaluación de cantidades demasiado grandes o pequeñas.
La limitación de la precisión tiene por lo general mayores consecuencias. Por ejem-
plo, cuando se sustraen cantidades muy parecidas, el error relativo empeora drásti-
camente por pérdida de cifras significativas.
5. Errores de truncamiento o de discretización, inherentes al hecho de aproximar un
problema continuo mediante una aproximación discreta. Por ejemplo, veremos que
la regla trapezoidal sirve para aproximar integrales mediante:

b N −1
h b−a
Z X
f (x)dx ≈ (f (a)+f (b))+h f (a+kh) ≡ S(h) , donde h = , h = (b−1)/N
a 2 k=1
N

El significado de “≈” es “aproximadamente”, lo cual significa que


Z b
f (x)dx = S(h) + ǫ(h)
a
52

donde ǫ(h) es el error de truncamiento, que es de esperar que sea menor cuanto menor
sea h (N ∈ N lo mayor posible). Un cálculo explı́cito de los errores de truncamiento
es al menos igual de difı́cil que el cálculo del problema original. Nos conformaremos
con un conocimiento cualitativo de estos errores y con buscar acotaciones lo más
finas posible.

Trataremos de estimar los errores de truncamiento en cada algoritmo numérico que


se presente.
Discutamos en este punto acerca de los errores debidos a las limitaciones en la repre-
sentación de punto flotante, en particular por lo que respecta a la precisión limitada del
sistema

Redondeo y pérdida de cifras significativas


Puesto que la representación en coma flotante es limitada en cuanto al número de
cifras significativas que se pueden almacenar, los números reales se redondearán a un de-
terminado número de cifras (siempre utilizando el sistema de numeración binario) cuando
el valor verdadero tenga más cifras de las que se pueden almacenar.
Asimismo, tras cada operación aritmética se vuelve a redondear el resultado. En el
estándar IEEE esto se hace de la mejor manera posible en el sentido de que el resultado de
la operación aritmética está redondeado de forma que se almacena el valor más próximo
al resultado exacto. Ası́, por ejemplo, supongamos que, para simplificar, la precisión fuese
de tres dı́gitos binarios tras el punto (y no consideremos limitación en el exponente),
si se suman los números en punto flotante x = (1.010)2 , y = (1.001)2 × 2−4 tenemos
que el resultado exacto es (1.0101001)2 que no serı́a un número en punto flotante (sólo
disponemos de 3 posiciones tras el punto). El resultado IEEE serı́a redondear la última
cifra, aumentándola si la siguiente fuese 1 y dejándola como está en otro caso. Ası́, x + y
se almacenarı́a como (1.011)2 .
Otra posibilidad (menos recomendable) es, sin más, eliminar las cifras que no quepan
(truncamiento). Esta desafortunada prescripción se utilizaba en los supercomputadores
Cray, pero está en total deshuso.
Aún cuando en el estándar IEEE se redondea al número en coma flotante más cercano,
que es la opción más conveniente, es necesario estudiar como pueden afectar sucesivos
redondeos al resultado de un algoritmo numérico.
Por otra parte, la limitación en el número de bits para la mantisa tiene como conse-
cuencia la posible pérdida de cifras significativas en ciertos cálculos donde dos cantidades
53

tienden a cancelarse entre sı́ 5 . Para evitar este tipo de errores, es recomendable:

a) O bien reescribir la fórmula en cuestión de modo que se eviten las restas de cantidades
de la misma magnitud.
b) O bien utilizar (cuando sea posible) un desarrollo de Taylor para aproximar la fórmula
hasta la precisión requerida.

Veamos algunos ejemplos en los que se da pérdida de cifras significativas


Ejemplo 2.2.1. Obtener las raı́ces de x2 − 106 x + 1.
En una
√ modesta calculadora con 10 decimales y aplicando la archiconocida fórmula
x = −b ± 2ab2 − 4ac , vamos obteniendo los resultados:

106 ± 1012 − 4 106 ± 106
x= ≃
2 2
que da x = 106 para la mayor raı́z y x = 0 para la menor. Es evidente que hemos perdido
todas las cifras significativas para la menor raı́z. De hecho, la famosa fórmula deberı́a ser
desterrada de cualquier método numérico. Es mucho mejor reescribir la solución de la
ecuación de segundo grado como:

x1 = L/2a , x2 = 2c/L , L = −b − signo(b) b2 − 4ac
De esta forma la mayor raı́z es como antes y en la calculadora la menor saldrı́a x = 10−6 ,
cuyo error relativo respecto a la solución verdadera es ∼ 10−12 .
√ √
Ejemplo 2.2.2. Consideremos por ejemplo f (x) = ( x + 1 − x) para x > 0. Cuando √ x
es√grande, se producen errores de redondeo importantes puesto que los valores de x + 1
y x se aproximan considerablemente. ¿Cómo evitarı́amos esta fuente de error?. Lo que
podemos hacer es reescribir f (x) del siguiente modo:
√ √
√ √ x+1+ x 1
f (x) = ( x + 1 − x) √ √ =√ √ .
x+1+ x x+1+ x
En la expresión resultante podemos apreciar que no se producen cancelaciones de
cantidades de la misma magnitud.
5
Un famoso y lamentable error de éste tipo fue el que motivó le fallo de los misiles Patriot para derribar
los misiles Skud iraquı́es durante la guerra del golfo: se medı́an intervalos de tiempo restando tiempos
desde el reinicio del sistema con la consiguiente pérdida progresiva de precisión
54

1 − cos(x)
Ejemplo 2.2.3. Consideremos g(x) = . Cuando se evalúa g(x) para |x| << 1
x2
se produce una pérdida considerable de cifras significativas. En este caso, para remediar
el problema consideramos el desarrollo de Taylor de cos(x) entorno a 0. De este modo:

1 − [1 − x2 /2! + x4 /4! + ...] 1 x2 x4


g(x) = ≈ − + .
x2 2 4! 6!
Esta expresión es apropiada entonces para evaluar g(x) cuando x << 1.

Errores de overflow /underflow


Aunque los lı́mites de overflow/underflow son considerablemente generosos, conviene
escribir las expresiones que se utilicen de manera que se minimice esta posibilidad. Por
ejemplo,
p es preferible calcular el módulo de un número complejo, z = x + iy, |z| =
x2 + y 2 , como
p
|z| = |x| 1 + (y/x)2
De esta forma, no hay que elevar al cuadrado números que pueden ser muy grandes. De
la misma forma, hay que tomar precauciones para calcular la argumento de un número
complejo de manera que no se produzcan “overflows/underflows” en el cálculo de y/x.

Ejercicio 2.4. Escribir un programa en Matlab que obtenga la representación polar de


un número complejo dado z = reiθ , 0 ≤ θ < 2π, eliminando el riesgo de problemas de
overflow-underflow.

2.3. Propagación de errores: condición y estabilidad.


Un error en un cálculo numérico contamina las sucesivas evaluaciones. Esta propa-
gación del error puede describirse en términos de dos conceptos relacionados, los de
(in)estabilidad y condición.
La condición de una función f (x) mide la sensibilidad de los valores de f (x) a
pequeños cambios en x y se define como

Erel (f (x))
C =
Erel (x)
donde Erel (f (x)) es el error relativo de f (x) para un error relativo Erel (x) en x.
55

6
Entonces, como

f ′ (xT )
f (xT ) − f (xA ) ≃ f ′ (xt )(xT − xA ) → Erel (f (x)) ≃ (xT − xA )
f (xT )
luego
f ′ (xT )
C ≃ xT

f (xT )
Utilizaremos esta última expresión como definición de condición para funciones f (x)
de una variable real. Definimos entonces los números de condición como

f (x)
C(x) = x
f (x)
Cuando para un x dado 0 < C(x) < 1 para ese x se dirá que el problema (cálculo
de f) está bien condicionado (y cuanto menor sea C mejor condicionado), mientras que si
C(x) > 1 el problema estará mal condicionado. Si C(X) = 1, el error relativo se mantiene.

Ejemplo 2.3.1. La función f (x) = x está bien condicionada, pues C(x) = 1/2, luego
el error relativo se reduce.
2

En cambio f (x) = x2 − 1 está mal condicionada para x ≃ 1 pues C(x) = 22x

x −1
El concepto de condicionamiento se puede extender a situaciones más generales que la
de una función de una variable continua. Por ejemplo, un problema clásico que involucra
funciones de una variable discreta, es el estudio del condicionamiento de relaciones de
recurrencia:

Ejemplo 2.3.2. Las funciones de Bessel Jn (x) satisfacen la relación de recurrencia:


Jn+1 (x) = −Jn−1 (x) + 2n x Jn (x), pero como las funciones de Bessel de segunda especia
cumplen la misma relación y lı́mn→∞ Jn (x)/Yn (x) = 0, el cálculo de las funciones Jn a
partir de J0 y J1 está mal condicionado, pues una pequeña perturbación en los datos ini-
ciales J0 y J1 contamina nuestra secuencia de funciones {Jn } con la secuencia {Yn }, que
crece más rápido con n.
Ası́, por ejemplo, empezando con los valores en precisión simple J0 (2) = 0.22389078
y J1 (2) = 0.57672481, y aplicando la recurrencia obtenemos J8 (2) = 4.00543213 × 10−5
que no tiene bien ni siquiera un cifra significativa: J8 (2) = 2.2180 × 10−5 .
6
Recordemos el teorema del valor medio: Si g(x) continua en [a, b] y derivable en (a, b) entonces
∃c ∈ (a, b) : f (b) − f (a) = f ′ (c)(b − a)
56

Un concepto relacionado, que no equivalente, es el de (in)estabilidad de un algoritmo,


que describe la sensibilidad de un método numérico especı́fico respecto a los inevitables
errores de redondeo cometidos durante su ejecución en aritmética de precisión finita.
Observemos que la condición no depende de errores de redondeo pero que la estabilidad
de un algoritmo sı́ depende del condicionamiento de la función que queramos evaluar. Un
ejemplo puede servir para distinguir estos dos conceptos relacionados:
√ √
Ejemplo 2.3.3. Dada la función f (x) = x + 1 − x, su número de condición es:

f (x) x
C(x) = x = √ √
f (x) 2 x x + 1

y vemos que C(x) < 1/2 para x > 0, luego la función está bien condicionada (su error
relativo es menor que el error relativo en x).
Sin embargo, el algorı́tmo
√ para
√ calcular x consistente en ir realizando las operaciones
implicadas en f (x) = x + 1 − x, a saber:

1. Input: x

2. y = x + 1

3. f1 = x + 1

4. f2 = x

5. f = f1 − f2

es inestable para x grande por culpa del paso 5 (hay cancelaciones entre números simi-
lares). Como sabemos, un algoritmo estable lo proporciona la siguiente reescritura de la
función:
1
f (x) = √ √
x+1+ x

2.4. Eficiencia de un algorı́tmo numérico


Por supuesto, cualquier algoritmo numérico sensato debe evitar ser inestable. Por
otra parte, si existieran varios métodos para evaluar una misma función, entonces conven-
drá adoptar aquel método que sea más eficiente, es decir, más rápido. Con la mejora en
proporción geométrica de la velocidad de los procesadores, podrı́amos estar tentados en
57

despreocuparnos por la rapidez de cálculo. Esta es, sin embargo, una pésima filosofı́a: se
trata de aprovechar los recursos para poder resolver problemas más complejos y no para
resolver peor problemas simples. La eficiencia es y siempre será de importancia capital en
el desarrollo de buenos métodos numéricos.
La diferencia en tiempos de ejecución pueden llegar a ser muy considerables si ciertas
operaciones elementales, que se pueden repetir miles y miles de veces, no se realizan con
cuidado. Por ejemplo, para calcular x4 para cierto valor de x, es muy mala idea calcular
x4.0 (exponente en coma flotante); hay que tener cuidado en utilizar un exponente entero
ya que los métododos de exponenciación en coma flotante son distintos que los de enteros
y mucho más lentos; aún es mejor idea considerar el cálculo en dos pasos: x2 = x ∗ x,
x4 = x2 ∗ x2 , con lo que se economizar un producto frente a x4 = x ∗ x ∗ x ∗ x.

2.4.1. Ejemplo: Evaluación de polinomios, método de Horner


Otro ejemplo notable (y por desgracia no suficientemente) conocido es la evaluación
de polinomios. Por ejemplo, supongamos que nos planteamos evaluar el polinomio
P (x) = 2 + 4x − 5x2 + 2x3 − 6x4 + 8x5 + 10x6
Contando con que cada potencia de exponente k entero cuente como k − 1 productos,
tendrı́amos que el número total de productos para evaluar el polinomio de forma directa
es:

1 + 2 + 3 + 4 + 5 + 6 = 21
y el número de sumas es 6.
Una forma mejor de proceder es ir calculando primero las potencias de forma sucesiva:
x2 = x x , x 3 = x x 2 , x4 = x x 3 , x5 = x x 4 , x6 = x x 5
con lo que sólo se añade una nueva multiplicación por potencia, par un total de
1 + 2 + 2 + 2 + 2 + 2 = 11
Pero aún se puede hacer mejor reescribiendo

P (x) = 2 + x(4 + x(−5 + x(2 + x(−6 + x(8 + x10)))))


con lo que sólo necesitamos 6 multiplicaciones (y el número de sumas no cambia). Vemos
pues que para evaluar un polinomio de grado n en el que ninguno de los coeficientes
58

es cero, se necesitan n(n + 1)/2 multiplicaciones por el primer método, 2n − 1 por el


segundo y n para el tercero. De forma que se debe procurar utilizar el último método,
particularmente cuando n es grande.
Este método es además sencillo de programa, en efecto:

Algoritmo 2.5 (Algoritmo de Horner o de división sintética).


Algoritmo de Horner
Dado el polinomio

P (x) = a0 + a1 x + ... + an xn , an 6= 0

la evaluación de P (x) para cierto valor x = z se puede realizar en n


pasos mediante

(1) bn = an

(2) bn−1 = an−1 + z ∗ bn

(3) bn−2 = an−2 + z ∗ bn−1


...

(n) b0 = a0 + z ∗ b1

donde P (z) = b0 .

Es fácil programa este algoritmo en forma de bucle, sobretodo si no nos interesan los
cálculos intermedios. Ası́, se puede escribir:

(1) b = an

(2) Repetir mientras n > 0

(3) n = n − 1

(4) b = an + z ∗ b

(5) Volver a (2)

(6) p(z) = b.
59

Esta forma de evaluar polinomios es mucho mejor que el método directo, especial-
mente para órdenes grandes. Dependiendo del órden de un polinomio y las veces que se
repita el cálculo, será importante aplicar el método de Horner.
60

2.5. EJERCICIOS PRÁCTICOS


Introducción a la construcción de algoritmos

El objetivo de estos ejercicios es el de poner en práctica algunos conceptos introducidos


en este tema desarrollando algunos ejemplos con MATLAB.
Ejercicios:
Se propone realizar los siguientes ejercicios y responder a las cuestiones que se plantean:

1. Comprobar que el epsilon-máquina es 2−52 = 2.2204 10−16 , tecleando en la lı́nea de


comandos:

>> a=1+2^(-53);b=a-1

y comparando con

>> a=1+2^(-52);b=a-1

2. Escribir la secuencia de comandos:

x=0; while x~=10


x=x+0.1
end

en un fichero (con extensión .m) y ejecutarlo en MATLAB. Para interrumpir la


ejecución, pulsar CTRL+C. ¿Qué ocurre si en lugar de incrementarse la variable en
0.1 lo hace en 0.125?. ¿Por qué?.

3. Obtener el mayor y el menor número positivo en punto flotante (números de over-


flow y underflow). Para obtener el número de overflow escribir un bucle que vaya
calculando las sucesivas potencias de 2 y que finalice cuando se produce overflow. Se
recomienda utilizar el comando isinf para detectar cuando se produce el overflow
(teclear help isinf) para obtener información sobre este comando. Otra instrucción
que puede resultar útil es break para interrumpir el bucle cuando se produce el over-
flow. El número de underflow se puede obtener calculando las sucesivas potencias
negativas de 2 hasta obtener un número indistinguible del cero en punto flotante.
61

4. Escribir dos funciones MATLAB para la resolución de ecuaciones de segundo grado


ax2 + bx + c = 0, una de ellas (que llamaremos mala.m) implementando la fórmula:

−b ± b2 − 4ac
x=
2a
y otra (buena.m) utilizando el método alternativo:

x1 = L/2a , x2 = 2c/L

donde L = −b − signo(b) b2 − 4ac.
La sintaxis de la llamada a la funciones ha de ser

>> [x1,x2]=buena(a,b,c);

y similarmente para mala.m.


Discutir la ventaja de buena.m sobre mala.m escribiendo un fichero con un ejemplo
ilustrativo.

5. Escribir una rutina que implemente el algoritmo de Horner (horn.m) para la eval-
uación de polinomios. La sintaxis de llamada a la rutina habrá de ser:

>> p=horn(coefs,x);

donde p es el valor del polinomio, coefs es un vector con los coeficientes del poli-
nomio, de mayor a menor grado y x es el valor de la variable independiente. Es decir
que si, por ejemplo, hacemos:

>> p=horn([1 -5 6 2],2);

entonces en la variable p se almacenará el valor P (2) donde P (x) = x3 −5x3 +6x+2.

También podría gustarte