Upload
others
View
0
Download
0
Embed Size (px)
Citation preview
Comunicaciones en Socioeconomía, Estadística e Informática 2002, Vol. 6 Núm. 1, pp 63-91
63
MEJORANDO LA PRECISION NUMERICA DE LOS RESULTADOS DE SIMULACIONES DIGITALES
CONTINUAS USANDO ARITMETICA DE PRECISION ARBITRARIA
Graciela Bueno1, Enrique Arjona1, Andrés Arjona2
RESUMEN
En corridas de simulación y particularmente cuando se utilizan
modelos digitales continuos, la propagación del error numérico
resultante de la ejecución de procesos iterativos puede afectar
considerablemente la exactitud de los resultados de la simulación.
Algunos lenguajes de programación, como C y Pascal, atenúan la
propagación del error a través de permitir el uso de un número fijo
relativamente grande de dígitos significativos. Sin embargo, investigar si
un número de dígitos significativos es adecuado para un modelo
particular no es una tarea fácil para el usuario promedio. Además, si el
número máximo de dígitos significativos permitidos por el lenguaje no
es satisfactorio, no existe una solución directa para este problema. En
este artículo, para atenuar la propagación del error se propone un
conjunto de rutinas sencillas para aritmética de precisión arbitraria. La
implementación de estas rutinas en programas existentes es directa y una
1 Especialidad de Estadística, Colegio de Postgraduados, Km. 36.5 Carr. México Texcoco, Montecillo 56230 Edo. de México 2 Universidad de las Américas A.C., Puebla Col. Roma, México D.F.
Graciela Bueno, Enrique Arjona y Andrés Arjona
64
vez que la implementación se concluye, probar que un número de dígitos
significativos particular es adecuado para el modelo es muy fácil.
Palabras clave: Simulación digital continua, Error de redondeo,
Aritmética de precisión arbitraria
INTRODUCCION
En corridas de simulación, y particularmente al usar modelos
digitales continuos, el cálculo de variables endógenas y de salida
requiere de la retroalimentación de valores intermedios dentro de
procesos iterativos que son ejecutados muchas veces. La propagación
del error numérico resultante de dichos procesos puede afectar
considerablemente la exactitud de los resultados de la simulación.
Los errores numéricos pueden estar inherentes en los parámetros y
en las relaciones aproximadas usadas en las ecuaciones de los modelos
para representar sistemas reales. Estos errores son independientes de la
precisión del lenguaje o la computadora en la que los modelos son
implementados y la única manera de evitarlos es utilizando parámetros y
ecuaciones más precisas.
Además de los errores numéricos inherentes existen dos tipos de
errores que son más bien independientes de la precisión de las
ecuaciones usadas en los modelos.
Comunicaciones en Socioeconomía, Estadística e Informática 2002, Vol. 6 Núm. 1, pp 63-91
65
El primer tipo de error es producido por la naturaleza aproximada
de las rutinas de integración numéricas usadas para extrapolar valores
simulados. Este tipo de error puede ser evitado o atenuado usando
integración simbólica siempre que sea posible, o rutinas de integración
elaboradas y tamaños de paso pequeños (Maron y López 1991).
El segundo tipo de error, que es en el que estamos interesados, es
producido por la precisión numérica fija usada para la representación
interna de los números en una computadora digital. Este tipo de error es
conocido como error de redondeo y se puede propagar. Algunos
lenguajes de programación, como C y Pascal, atenúan la propagación del
error a través de permitir el uso de un número fijo, relativamente largo,
de dígitos significativos. Sin embargo, investigar si un número de dígitos
significativos es adecuado para un modelo particular no es tarea fácil
para el usuario promedio. Además, si el número máximo de dígitos
significativos permitidos por el lenguaje no es satisfactorio, no existe
una solución directa para este problema.
La propagación del error debida a la precisión numérica fija no es
exclusiva de los modelos continuos digitales y ocurre en muchos
algoritmos matemáticos. Con el paso de los años se han propuesto
diversas soluciones a este problema. Las propuestas incluyen el uso de
aritmética entera de precisión ilimitada (Collins, 1966), aritmética
Graciela Bueno, Enrique Arjona y Andrés Arjona
66
racional (Knuth 1969) y aritmética entera de precisión arbitraria (Knuth
1969). Las maneras en las que estos sistemas aritméticos pueden ser
implementados en una computadora, son usualmente considerados
complejos para el usuario promedio, por lo que no se incluyen en libros
de simulación, estadística u optimización. Por consiguiente, el uso de
sistemas aritméticos especializados ha sido el dominio de paquetes muy
especializados de programación. Otro hecho que posiblemente ha
retrasado la generalización del uso de rutinas de atenuación del error de
redondeo, es que los algoritmos modificados con esas rutinas consumen
más tiempo que los algoritmos originales. El tiempo de cómputo era un
recurso caro hasta la llegada de computadoras personales poderosas lo
cual ocurrió hace apenas algunos años.
Los sistemas computacionales algebraicos como Mathematica
(Wolfram 1991), que permiten el uso de aritmética racional y de
precisión arbitraria, parecen ser una solución a mano para evitar la
propagación del error. Sin embargo, el uso de dichos sistemas para la
implementación de modelos de simulación continuos no ha sido
generalizado aún. Los usuarios usan mayormente otras herramientas
como los lenguajes C, CSL, y SIMULINK (Bennet 1995).
El uso de rutinas de precisión arbitraria no es necesariamente una
tarea compleja. Su complejidad está relacionada íntimamente al tipo de
Comunicaciones en Socioeconomía, Estadística e Informática 2002, Vol. 6 Núm. 1, pp 63-91
67
rutinas utilizadas y la manera en que son implementadas y sus
implementaciones no siempre requieren de la ayuda de un especialista en
ciencias de la computación.
En este artículo, un conjunto de rutinas aritméticas de precisión
arbitraria es propuesto. Estas rutinas pueden proveer una solución a la
propagación del error porque el número de dígitos significativos usados
para la representación numérica en un modelo puede incrementarse tanto
como sea necesario. La implementación de estas rutinas en programas
existentes es directa y requiere básicamente del cambio de expresiones
aritméticas de notación infija a postfija. Una vez que la implementación
de las rutinas en un modelo ha sido realizada, probar que un número
particular de dígitos es adecuado para un modelo es muy fácil y consiste
en comparar los resultados usando diferentes precisiones que difieren en
un pequeño número de dígitos.
El artículo está organizado de la siguiente manera: Primero, se
presenta un ejemplo de propagación del error y se dan generalidades
acerca de la aritmética racional y de precisión arbitraria. Después, las
rutinas de precisión arbitraria propuestas son descritas acompañadas
junto con el proceso para su implementación en programas existentes.
Finalmente una discusión es dada.
Graciela Bueno, Enrique Arjona y Andrés Arjona
68
PROPAGACION DEL ERROR DE REDONDEO, ARITMETICA
RACIONAL Y PRECISION ARBITRARIA
Como se ha mencionado, el error de redondeo es producido por la
precisión numérica fija usada para la representación interna de números
en una computadora digital. Este ocurre cuando el número de dígitos
significativos disponible para la representación interna de números en
una computadora, es menor que el número de dígitos significativos
obtenidos del resultado de alguna operación aritmética. Una vez que ha
ocurrido un error de redondeo, puede propagarse en operaciones
aritméticas subsecuentes.
Por ejemplo, si tenemos una computadora que internamente
permite sólo ocho dígitos significativos y realizamos la siguiente suma:
1234567 + .1234567 = 1234567.1234567 el resultado contiene catorce dígitos significativos, aún cuando cada uno
de los números sumados contienen sólo siete dígitos significativos. En la
computadora, el resultado será almacenado como 1234567.1 . Eso es, se
han perdido seis dígitos significativos en esta operación.
Para ver como este error de redondeo puede propagarse a más
dígitos significativos, realizaremos dos operaciones más. Primero, con el
resultado obtenido arriba realizamos una simple resta:
Comunicaciones en Socioeconomía, Estadística e Informática 2002, Vol. 6 Núm. 1, pp 63-91
69
1234567.1 - 1234565.1 = 2
y finalmente, con este nuevo resultado, realizamos la siguiente
multiplicación:
2 x 7000 = 14000
El resultado exacto que podríamos haber obtenido al realizar las
operaciones descritas, con un lápiz y papel es 14164.1969. Lo que
significa, que en las operaciones arriba mostradas, a pesar de que se
habían perdido inicialmente sólo seis dígitos, el resultado final es
truncado en siete dígitos significativos debido a la propagación del error.
El ejemplo anterior involucra sólo números con un número finito
de dígitos significativos. Sin embargo, en aritmética real (aritmética con
números reales), se pueden obtener resultados con un número infinito de
dígitos significativos. Por ejemplo:
10 : 3 = 3.333333...
En estos casos, obviamente, el número de dígitos significativos
perdidos en cualquier representación decimal interna es también infinita.
Graciela Bueno, Enrique Arjona y Andrés Arjona
70
Los números que tienen una representación infinita en un sistema
numérico posicional como el decimal, pueden tener una representación
finita en otro sistema numérico posicional y viceversa. Por ejemplo, si
usamos el sistema ternario (base 3), el número mencionado tendrá la
representación finita 10.1 porque en el sistema ternario:
10.1 = 1 x 3**1 + 0 x 3**0 + 1 x 3**(-1) = 3.33... en base 10
y el número 10.111... , que tiene una representación infinita en el sistema
ternario, es equivalente al número 3.5 en el sistema decimal.
En cualquier sistema numérico posicional, habrá un conjunto
infinito de números que para su representación requieren de un número
infinito de dígitos significativos. Por lo que el cambio de base no es una
solución al problema de error por redondeo.
Para superar el problema de propagación del error de redondeo se
ha propuesto el uso de aritmética racional y de precisión arbitraria.
El uso de aritmética racional en una computadora es soportada por
el hecho de que todos los números decimales finitos y periódicos como
3.3333... y 23.67813813813... , son números racionales y tienen una
representación finita como el cociente de dos enteros. La aritmética
Comunicaciones en Socioeconomía, Estadística e Informática 2002, Vol. 6 Núm. 1, pp 63-91
71
racional siempre producirá números racionales y por lo tanto,
susceptibles a representación finita. Por consiguiente, en el sistema de
números racional, los únicos números reales que no tienen una
representación finita y que son susceptibles al error de redondeo son los
números irracionales como π, e, √2, etc.
Las reglas para transformar números decimales finitos y periódicos
a sus representaciones racionales equivalentes son muy sencillas. Un
número decimal finito puede ser expresado como un número racional
cuyo denominador es una potencia de 10, por ejemplo:
14164.1969 = 141641969/10000
Un número decimal periódico puede ser expresado como la suma
infinita de números racionales, que en turno puede ser expresada como
la suma de dos números racionales y por consiguiente como un número
racional único, por ejemplo:
23.67813813... = 23.67 + .00813 + .00000813 + ...
= 2367/100 + 813/10000 + 813/100000000 + ...
= 2367/100 + 813/99900
= 2365446/99900
Graciela Bueno, Enrique Arjona y Andrés Arjona
72
Mas aún, los números racionales pueden ser reducidos a números
racionales equivalentes usando el algoritmo de Euclídes para calcular
máximos comunes divisores. Los números reducidos pueden contener
menos dígitos que los números originales. Por ejemplo:
2365446/99900 = 394241/16650
A pesar de su simplicidad, la aritmética racional no es fácil de
implementar en una computadora. Las representaciones de números
racionales como cociente de dos enteros pueden involucrar números
muy largos, por lo que realizar operaciones con ellos puede requerir
precisión infinita de aritmética de enteros. Además, las rutinas para la
reducción de números racionales calculados deben de ser muy eficientes
para minimizar el tiempo de ejecución.
La aritmética de precisión arbitraria permite elegir un número
arbitrario fijo máximo de dígitos significativos para la representación
interna de números en una computadora. Usualmente, el número de
dígitos para la precisión elegida depende de la exactitud requerida en los
resultados de una aplicación en particular. A mayor precisión
seleccionada, mayor será el tiempo que las aplicaciones requerirán para
su ejecución.
Comunicaciones en Socioeconomía, Estadística e Informática 2002, Vol. 6 Núm. 1, pp 63-91
73
La precisión arbitraria es más fácil de implementar que la
aritmética racional. Sin embargo, su utilidad para atenuar la propagación
del error de redondeo es más débil que la utilidad de la aritmética
racional. Con precisión arbitraria, todos los números decimales infinitos
almacenados en una computadora, racionales e irracionales, tendrán
error de redondeo y podrían causar la propagación del error. Más aún,
números decimales finitos pueden también tener error de redondeo
dependiendo de su número de dígitos significativos. Sin embargo, en
comparación con la máxima precisión permitida en muchos lenguajes de
programación, la cual es del orden de 20 dígitos significativos, el uso de
precisión arbitraria de 50 o más dígitos, puede llegar a ser muy
satisfactoria.
La implementación de precisión arbitraria en una computadora
puede realizarse de diversas formas. Desde unas muy eficientes, usando
lenguaje máquina y optimizando la ejecución y la cantidad de memoria
de la computadora. A unos muy simples usando un lenguaje de alto nivel
y un byte para almacenar cada dígito decimal. Las implementaciones
simples son más fáciles de rastrear y requieren de menos programación,
pero usualmente son lentas. Implementaciones completas requieren de
una librería extensa de funciones matemáticas. Las rutinas presentadas
en la siguiente sección contienen características eficientes, pero pueden
considerarse simples.
Graciela Bueno, Enrique Arjona y Andrés Arjona
74
UN CONJUNTO DE RUTINAS DE PRECISION ARBITRARIA
Las rutinas aquí presentadas han sido implementadas como
procedimientos en Pascal, pero pueden fácilmente ser cambiadas a
funciones o subrutinas en otro lenguaje de alto nivel como C o
FORTRAN. El almacenamiento interno de números y operaciones
aritméticas es realizado en base 256, por lo que cada dígito requiere un
byte. En los procedimientos, AP significa precisión arbitraria; EP
precisión externa; e IP precisión interna. AP (un tipo); EP (una
constante); e IP (también una constante) son declaradas globales en el
programa principal.
Las rutinas han sido clasificadas en cuatro grupos. El primer grupo
consiste de los procedimientos READLONG y WRITELONG usadas
para entrada y salida. El segundo grupo está integrado por las funciones
GREATERLONG, LESSLONG, y EQULONG usadas para pruebas de
relación. El tercer grupo se refiere a los procedimientos CONSTLONG,
ADDLONG, SUBLONG, MULLONG, y DIVLONG usadas para
operaciones aritméticas. El último grupo consiste de los procedimientos
de soporte INITLONG, CONVLONG, NORMLONG y ROUNDLONG.
Las rutinas en este último grupo no son llamadas por el usuario. A
continuación se presentan las rutinas.
Comunicaciones en Socioeconomía, Estadística e Informática 2002, Vol. 6 Núm. 1, pp 63-91
75
Grupo 1 READLONG PROCEDURE READLONG(VAR APVAR:AP); { Este procedimiento lee un número decimal de punto fijo de precisión arbitraria desde el } {teclado y almacena su representación de precisión arbitraria de base BASE en APVAR } var inputstr:string[EP+2]; begin readln(inputstr); CONVLONG(inputstr, APVAR); end; WRITELONG PROCEDURE WRITELONG(APVAR:AP); { Este procedimiento despliega en la pantalla la representación decimal de punto fijo de} {número de precisión arbitraria arbitraria en base BASE almacenada en APVAR } var appart,digit,ten,aptemp:AP; ndigits,i,exp:integer; outputstr:string[EP+2]; decdigit:string[1]; begin INITLONG(10,ten); outputstr:=''; exp:=APVAR[IP+1]-127; ndigits:=0; if APVAR[1]=0 then begin outputstr:='0'; writeln(outputstr); exit; end; if exp>0 then begin appart:=APVAR; appart[0]:=1; for i:=exp+1 to IP do appart[i]:=0; repeat DIVLONG(appart,ten,aptemp); for i:=aptemp[IP+1]-127+1 to IP do aptemp[i]:=0; MULLONG (aptemp,ten,aptemp); SUBLONG(appart,aptemp,digit); if digit[IP+1]>127 then begin str(digit[1]:1,decdigit); outputstr:=decdigit+outputstr; end
Graciela Bueno, Enrique Arjona y Andrés Arjona
76
else outputstr:='0'+outputstr; ndigits:=ndigits+1; DIVLONG(aptemp,ten,aptemp);appart:=aptemp; until (appart[IP+1]=127); exp:=APVAR[IP+1]-127; i:=exp+1; while (APVAR[i]=0) and (i<=IP) do i:=i+1; if i=IP+1 then begin if APVAR[0]=0 then outputstr:='-'+outputstr; writeln(outputstr); exit; end; end; outputstr:=outputstr+'.'; appart:=APVAR; exp:=APVAR[IP+1]-127; if exp>0 then begin i:=exp+1; while (i<=IP) and (appart[i]=0) do begin exp:=exp+1; i:=i+1; end; for i:=exp+1 to IP do appart[i-exp]:=appart[i]; for i:=IP-exp to IP do appart[i]:=0; appart[ip+1]:=appart[IP+1]-exp; end; appart[0]:=1; repeat MULLONG(appart,ten,digit); if digit[IP+1]=128 then begin str(digit[1]:1,decdigit); outputstr:=outputstr+decdigit; appart:=digit; for i:=2 to IP do digit[i]:=0; SUBLONG(appart,digit,appart); end else begin outputstr:=outputstr+'0'; MULLONG(appart,ten,appart); end; ndigits:=ndigits+1; until ((ndigits=EP) or (appart[1]=0)); if apvar[0]=0 then outputstr:='-'+outputstr; writeln(outputstr); end;
Comunicaciones en Socioeconomía, Estadística e Informática 2002, Vol. 6 Núm. 1, pp 63-91
77
Grupo 2 GREATERLONG FUNCTION GREATERLONG(OP1,OP2:AP) : BOOLEAN; { Esta función compara variables de precisión arbitraria OP1 y OP2, si OP1>OP2 regresa} {un valor verdadero(true) } var pos:byte; begin GREATERLONG:=true; if OP1[1]=0 then if (OP2[0]=1) or (OP2[1]=0) then begin GREATERLONG:=false; exit; end else exit; if OP2[1]=0 then if OP1[0]=0 then begin GREATERLONG:=false; exit; end else exit; if (OP1[0]=0) and (OP2[0]=1) then begin GREATERLONG:=false; exit; end; if (OP1[0]=1) and (OP2[0]=0) then exit; if OP1[0]=1 then begin if OP1[IP+1]<OP2[IP+1] then begin GREATERLONG:=false; exit;end else if OP1[IP+1]>OP2[IP+1] then exit; end; if OP1[0]=0 then begin if OP1[IP+1]>OP2[IP+1] then begin GREATERLONG:=false; exit; end else if OP1[IP+1]<OP2[IP+1] then exit; end; pos:=1; while (OP1[pos]=OP2[pos]) and (pos<IP) do pos:=pos+1; if OP1[0]=1 then GREATERLONG:=OP1[pos]>OP2[pos] else GREATERLONG:=OP1[pos]<OP2[pos]; end;
Graciela Bueno, Enrique Arjona y Andrés Arjona
78
LESSLONG FUNCTION LESSLONG(OP1,OP2:AP) : BOOLEAN; { Esta función compara variables de precisión arbitraria OP1 y OP2, } { si OP1<OP2 regresa un valor verdadero(true) } begin LESSLONG:=GREATERLONG(OP2,OP1); end; EQULONG FUNCTION EQULONG(OP1,OP2:AP) : BOOLEAN; { Esta función compara variables de precisión arbitraria OP1 y OP2, } { si OP1=OP2 regresa un valor verdadero(true) } begin EQULONG:=not GREATERLONG(OP1,OP2) and not GREATERLONG(OP2,OP1); end; Grupo 3 CONSTLONG PROCEDURE CONSTLONG(REALCONST:REAL; APVAR:AP); { Este procedimiento almacena el valor de la constante real REALCONST } { en la variable de precisión arbitraria APVAR } var apstring:string[EP+2]; begin str(REALCONST,apstring); CONVLONG(apstring, APVAR); end; ADDLONG PROCEDURE ADDLONG (OP1,OP2:AP; VAR RES:AP); { Este procedimiento suma los dos números de precisión } { arbitraria OP1 y OP2 y almacena el resultado en RES } var exp1,exp2,difexp,i,carry,shift:integer; aptemp:AP;
Comunicaciones en Socioeconomía, Estadística e Informática 2002, Vol. 6 Núm. 1, pp 63-91
79
acum: array[0..2*IP+1] of longint; begin INITLONG(0,aptemp); for i:=1 to 2*IP+1 do acum[i]:=0; exp1:=OP1[IP+1]-127; exp2:=OP2[IP+1]-127; if OP1[1]=0 then begin RES:=OP2; exit; end else if OP2[1]=0 then begin RES:=OP1;exit; end; if exp2<=exp1-IP then begin RES:=OP1; exit; end else if exp1<=exp2-IP then begin RES:=OP2; exit; end; if OP1[0]<>OP2[0] then begin if OP2[0]=0 then OP2[0]:=1 else OP2[0]:=0; SUBLONG(OP1,OP2,RES); exit; end; if exp1<exp2 then begin aptemp:=OP1; OP1:=OP2; OP2:=aptemp; exp1:=OP1[IP+1]; exp2:=OP2[IP+1]; end; acum[0]:=OP1[0]; acum[2*IP+1]:=IP+OP2[IP+1]; difexp:=exp1-exp2; shift:=2*IP-difexp+1; if difexp<>0 then acum[shift]:=OP2[IP-difexp+1]; carry:=0; for i:=difexp to IP+difexp-1 do begin shift:=shift-1; if i<IP then acum[shift]:=OP2[IP-i]+carry+OP1[IP-i+difexp] else acum[shift]:=carry+OP1[IP-i+difexp]; if acum[shift]>=BASE then begin carry:=1; acum[shift]:=acum[shift]-BASE; end else carry:=0; end; acum[shift-1]:=carry; NORMLONG(acum); TRUNCLONG(acum,RES); end; SUBLONG PROCEDURE SUBLONG (OP1,OP2:AP; VAR RES:AP); { Este procedimiento resta los dos números de precisión } { arbitraria OP1 y OP2 y almacena el resultado en RES } var exp1,exp2,difexp,i,carry,shift,sop1,sop2:integer; aptemp:AP;
Graciela Bueno, Enrique Arjona y Andrés Arjona
80
acum: array[0..2*IP+1] of longint; begin if OP2[0]=1 then OP2[0]:=0 else OP2[0]:=1; INITLONG(0,aptemp); for i:=1 to 2*IP+1 do acum[i]:=0; exp1:=OP1[IP+1]-127; exp2:=OP2[IP+1]-127; if OP1[1]=0 then begin RES:=OP2; if RES[1]=0 then RES[0]:=1; exit; end else if OP2[1]=0 then begin RES:=OP1; exit; end; if exp2<=exp1-IP then begin RES:=OP1; exit; end else if exp1<=exp2-IP then begin RES:=OP2; exit; end; if OP1[0]=OP2[0] then begin ADDLONG(OP1,OP2,RES); exit; end; sop1:=OP1[0]; sop2:=OP2[0]; OP1[0]:=1; OP2[0]:=1; if LESSLONG(OP1,OP2) then begin OP1[0]:=sop1; OP2[0]:=sop2; aptemp:=OP1; OP1:=OP2; OP2:=aptemp; exp1:=OP1[IP+1]; exp2:=OP2[IP+1]; end else begin OP1[0]:=sop1; OP2[0]:=sop2; end; acum[0]:=OP1[0]; acum[2*IP+1]:=IP+OP2[IP+1]; difexp:=exp1-exp2; shift:=2*IP-difexp+1; if difexp<>0 then acum[shift]:=OP2[IP-difexp+1]; carry:=0; for i:=difexp to IP+difexp-1 do begin shift:=shift-1; sop1:=OP1[IP-i+difexp]; sop1:=sop1+carry; if i<IP then sop2:=OP2[IP-i] else sop2:=0; if sop1<sop2 then begin carry:=-1; sop1:=sop1+BASE; end else carry:=0; if i<IP then acum[shift]:=sop1-sop2 else acum[shift]:=sop1; end; acum[shift-1]:=carry; NORMLONG(acum); TRUNCLONG(acum,RES); end;
Comunicaciones en Socioeconomía, Estadística e Informática 2002, Vol. 6 Núm. 1, pp 63-91
81
MULLONG PROCEDURE MULLONG (OP1,OP2:AP; VAR RES:AP); { Este procedimiento multiplica los dos números de precisión } { arbitraria OP1 y OP2 y almacena el resultado en RES } var i,j,twoip,p1,p2,shift:integer; carry,op1long:longint; acum:array[0..2*IP+1] of longint; begin if (OP1[1]=0) or (OP2[1]=0) then begin INITLONG(0,RES); exit; end; if OP1[0]=OP2[0] then acum[0]:=1 else acum[0]:=0; carry:=0; twoip:=2*IP; for i:=1 to twoip+1 do acum[i]:=0; p1:=IP; while (p1>1) and (OP1[p1]=0) do p1:=p1-1; p2:=IP; while (p2>1) and (OP2[p2]=0) do p2:=p2-1; shift:=twoip-p1-p2; for i:=twoip downto shift+1 do begin acum[i-shift]:=carry; for j:=0 to twoip-i do if (p2-j>0) and (p1-twoip+i+j>0) then begin op1long:=OP1[p1-twoip+i+j]; acum[i-shift]:=acum[i-shift]+OP2[p2-j]*op1long;end; carry:=acum[i-shift] div BASE; acum[i-shift]:=acum[i-shift] mod BASE; end; acum[twoip+1]:=OP1[IP+1]-127+OP2[IP+1]; NORMLONG(acum); TRUNCLONG(acum,RES); end; DIVLONG PROCEDURE DIVLONG (OP1,OP2:AP; VAR RES:AP); { Este procedimiento divide el número de precisión arbitraria OP1 } { por el número de precisión arbitraria OP2 y almacena el resultado en RES } var sign:byte; pos,i,reduce,expres:integer; aptemp:AP; acum:array[0..2*IP+1] of longint; dividp,divisp,quotp,lbase:longint; begin
Graciela Bueno, Enrique Arjona y Andrés Arjona
82
if OP1[0]=OP2[0] then sign:=1 else sign:=0; OP1[0]:=1;OP2[0]:=1; if OP2[1]=0 then begin writeln('arbitrary precision divide by cero'); halt; end; INITLONG(1,aptemp); for i:=1 to 2*IP+1 do acum[i]:=0; if EQULONG(OP2,aptemp) then begin RES:= OP1; RES[0]:=sign; exit;end; if OP1[1]=0 then begin INITLONG(0,RES); exit; end; acum[2*IP+1]:=OP1[IP+1]+1-OP2[IP+1]+127; reduce:=2+127-OP2[IP+1]; expres:=acum[2*IP+1]; if (expres<0) or (expres>255) or (expres>127+IP) then begin writeln('arbitrary division invalid'); halt; end; OP2[IP+1]:=OP2[IP+1]+reduce; OP1[IP+1]:=2+127; pos:=0; lbase:=BASE; divisp:=OP2[1]*lbase+OP2[2]; while (OP1[1]>0) and (pos<=IP+1) do begin dividp:=OP1[1]; i:=128; while OP1[IP+1]>i do begin i:=i+1; dividp:=dividp*lbase+OP1[i-127]; end; if dividp >= divisp then begin quotp:=dividp div divisp; while quotp >= BASE do begin quotp:=quotp div BASE; OP1[IP+1]:=OP1[IP+1]-1; end; INITLONG(quotp,aptemp); MULLONG(aptemp,OP2,aptemp); while GREATERLONG(aptemp,OP1) do begin quotp:=quotp-1; SUBLONG(aptemp,OP2,aptemp); end; SUBLONG(OP1,aptemp,OP1); OP1[IP+1]:=OP1[IP+1]+1; pos:=pos+1; acum[pos]:=quotp; end else begin pos:=pos+1; acum[pos]:=0; OP1[IP+1]:=OP1[IP+1]+1; end; end; acum[0]:=sign; NORMLONG(acum); TRUNCLONG(acum,RES); end;
Comunicaciones en Socioeconomía, Estadística e Informática 2002, Vol. 6 Núm. 1, pp 63-91
83
Grupo 4 INITLONG PROCEDURE INITLONG(NUMBER:longint; VAR APVAR:AP); { Este procedimiento inicializa la variable de precisión arbitraria APVAR } { con un dígito no negativo < BASE*BASE-1 dado en NUMBER } var pos:byte; begin for pos:=1 to IP do APVAR[pos]:=0; APVAR[IP+1]:=127;APVAR[0]:=1; if NUMBER>0 then begin if number>=BASE then begin APVAR[1]:=NUMBER div BASE; APVAR[2]:=number mod BASE; APVAR[IP+1]:=129; end else begin APVAR[1]:=NUMBER; APVAR[IP+1]:=128; end; end; end; CONVLONG PROCEDURE CONVLONG(VAR APSTRING; VAR APVAR:AP); { Este procedimiento convierte un número decimal de punto fijo de precisión arbitraria } { en la cadena APSTRING a su representación de precisión arbitraria en base BASE y } { la almacena en APVAR } var inputstr:string[EP+2] absolute APSTRING; ten,digit,powers,left,right:AP; sign:byte; pos,point:integer; begin INITLONG(10,ten); INITLONG(1,powers); INITLONG(0,left); INITLONG(0,right); INITLONG(0,APVAR); pos:=0; repeat pos:=pos+1; until (inputstr[pos]<>' '); inputstr:=copy(inputstr,pos,length(inputstr)-pos+1); sign:=1; if inputstr[1]='-' then sign:=0; if (inputstr[1]='+') or (inputstr[1]='-') then inputstr:=copy(inputstr,2,length(inputstr)-1);
Graciela Bueno, Enrique Arjona y Andrés Arjona
84
point:=0; for pos:=1 to length(inputstr) do if inputstr[pos]='.' then point:=pos; if point<>1 then begin if point=0 then point:=length(inputstr)+1; for pos:=point-1 downto 1 do if (inputstr[pos] in ['0'..'9']) then begin INITLONG(0,digit); digit[IP+1]:=128; digit[1]:=ord(inputstr[pos])-48; MULLONG(powers,digit,digit); ADDLONG(left,digit,left); MULLONG(powers,ten,powers);end else begin writeln('arbitrary precision invalid input data'); halt; end; APVAR:=left; APVAR[0]:=sign; end; if point >= length(inputstr) then exit; inputstr:=copy(inputstr,point+1,length(inputstr)-point); INITLONG(1,powers); DIVLONG(powers,ten,powers); for pos:=1 to length(inputstr) do if (inputstr[pos] in ['0'..'9']) then begin INITLONG(0,digit); digit[IP+1]:=128; digit[1]:=ord(inputstr[pos])-48; MULLONG(powers,digit,digit); ADDLONG(right,digit,right); DIVLONG(powers,ten,powers);end else begin writeln('arbitrary precision invalid input data'); halt; end; ADDLONG(LEFT,RIGHT,APVAR); APVAR[0]:=sign; end; NORMLONG PROCEDURE NORMLONG(VAR UNNORMED); { Este procedimiento normaliza un número doble de precisión arbitraria APVAR } var acum:array[0..2*IP+1] of longint absolute UNNORMED; i,j,limit,twoip:integer; begin twoip:=2*IP; limit:=acum[twoip+1]; if twoip<limit then limit:=twoip; i:=0; while (acum[i+1]=0) and (i<=limit) do i:=i+1;
Comunicaciones en Socioeconomía, Estadística e Informática 2002, Vol. 6 Núm. 1, pp 63-91
85
if i>0 then begin for j:=1 to twoip-i do acum[j]:=acum[j+i]; for j:=twoip+1-i to twoip do acum[j]:=0; acum[twoip+1]:=acum[twoip+1]-i; end; if acum[twoip+1]-127>IP then begin writeln('arbitrary precision overflow');halt;end; if acum[1]=0 then begin for i:=2 to twoip do if acum[i]<>0 then begin writeln('arbitrary precision underflow'); halt; end; acum[0]:=1; acum[twoip+1]:=127;end; end; TRUNCLONG PROCEDURE TRUNCLONG(VAR UNROUND; VAR APVAR:AP); { Este procedimiento trunca un número doble de precisión arbitraria UNROUND y } { almacena el resultado en el número de precisión arbitraria APVAR } var i,pos:integer; acum:array[0..2*IP+1] of longint absolute UNROUND; begin APVAR[IP+1]:=acum[2*IP+1]; if ROUND=0 then begin for pos:=0 to IP do APVAR[pos]:=acum[pos]; exit; end else begin APVAR[0]:=acum[0]; if acum[IP+1]>=trunc(BASE/2) then acum[IP]:=acum[IP]+1; for pos:=IP downto 2 do if acum[pos]=BASE then begin APVAR[pos]:=0; acum[pos-1]:=acum[pos-1]+1; end else APVAR[pos]:=acum[pos]; if acum[1]=BASE then begin for pos:=IP downto 3 do APVAR[pos]:=APVAR[pos-1]; APVAR[1]:=1; APVAR[2]:=0; APVAR[IP+1]:=APVAR[IP+1]+1;end else APVAR[1]:=acum[1]; end;
Graciela Bueno, Enrique Arjona y Andrés Arjona
86
for i:=0 to IP*2+1 do acum[i]:=0; end;
IMPLEMENTACION DE PRECISION ARBITRARIA EN
PROGRAMAS EXISTENTES
La implementación de las rutinas mencionadas en programas
existentes es directa. Toda la aritmética real en sus programas es
transformada en aritmética de precisión arbitraria. Las reglas para la
implementación son las siguientes:
1. Incluya en su programa principal (main) la declaración global de
BASE, ROUND, EP, IP y AP. BASE es la base utilizada internamente
por las rutinas y puede ser cualquier número desde 10 a 256. Las bases
que son múltiplos de 10 usualmente se desempeñan más rápidamente
que otras bases. EP es la precisión deseada (máximo número de dígitos
significativos). El máximo valor permitido por EP en las rutinas dadas es
256, sin embargo, cuando los números son introducidos por el teclado, el
número máximo para EP será 130. IP es la precisión interna equivalente
en bytes de EP y depende de la base usada, para base 10 IP=EP; para
base 256 IP=1/2EP. Para otras bases use valores intermedios para IP y
revise la adecuidad. ROUND es utilizado para indicar si se desea
redondeo interno, un valor de 1 significa un redondeo al siguiente dígito
Comunicaciones en Socioeconomía, Estadística e Informática 2002, Vol. 6 Núm. 1, pp 63-91
87
significativo, un valor de 0 significa truncar sin redondear. AP es la
declaración del tipo de precisión arbitraria. Las declaraciones a incluir
en el programa principal, antes de la primera declaración BEGIN son
similares a las siguientes:
CONST EP=100; IP=50; BASE=250; ROUND=1;
TYPE AP=ARRAY[0..IP+1] OF BYTE;
2. Incluya en su programa principal y también antes de la primera
declaración begin, los encabezados de todas las rutinas con la opción
forward, y después del grupo de encabezados, el código de todas las
rutinas. Por ejemplo, el encabezado del procedimiento READLONG es:
PROCEDURE READLONG(VAR APVAR:AP); forward;
3. Incluya en su programa principal la declaración de las variables de
precisión arbitraria que serán utilizadas para almacenar resultados
intermedios de operaciones aritméticas (paso 5). Usualmente, dos
variables serán suficientes, pero use más si así lo desea. Las variables
temporales podrán ser declaradas de la siguiente manera:
VAR TEMP1, TEMP2: AP;
Graciela Bueno, Enrique Arjona y Andrés Arjona
88
si su programa ya usa variables con esos nombres, use otros nombres
para las variables temporales.
4. Cambie todas las variables de tipo real utilizadas en su programa al
tipo AP. Por ejemplo, si en su programa existe una declaración como la
siguiente:
VAR A,B,C: REAL;
cámbiela a: VAR A,B,C: AP;
5. Cambie todas las declaraciones de lectura (escritura) de datos reales
en su programa a llamadas al procedimiento READLONG
(WRITELONG). Estos procedimientos sólo leen (escriben) una variable
a la vez, por lo que una declaración puede requerir más de una llamada a
READLONG (WRITELONG). Por ejemplo, si en su programa hay una
declaración como la siguiente (A y B son variables reales):
READLN (A,B);
cámbiela a: READLONG(A); READLONG(B);
6. Cambie todas las expresiones aritméticas que involucren datos reales
en su programa a llamadas a los procedimientos aritméticos
Comunicaciones en Socioeconomía, Estadística e Informática 2002, Vol. 6 Núm. 1, pp 63-91
89
ADDLONG, SUBLONG, MULLONG y DIVLONG. Note que esos
procedimientos realizan operaciones sólo entre dos números, por lo que
puede ser necesario reorganizar una expresión en su programa en varias
llamadas a los procedimientos mencionados. Los resultados intermedios
son almacenados en variables temporales de precisión arbitraria (véase
paso 2). Si una expresión contiene constantes reales, éstas deberán ser
transformadas a precisión arbitraria utilizando el procedimiento
CONSTLONG. Por ejemplo, si su programa contiene una expresión
como la siguiente (A, B, C y D son variables reales):
A:= (B+2.0)/(C*D)
cámbiela a:
CONSTLONG(2.0,TEMP1); ADDLONG(B,TEMP1,TEMP1);
MULTLONG(C,D,TEMP2); DIVLONG(TEMP1,TEMP2,A)
7. Cambie todas las expresiones relacionales que involucren datos reales
en su programa, a llamadas a los procedimientos GREATERLONG,
LESSLONG, y EQULONG. Por ejemplo, si su programa contiene la
siguiente declaración (A, B y C son variables reales):
IF A<B THEN C=A+B
cámbiela a: IF LESSLONG(A,B) THEN ADDLONG(A,B,C);
Graciela Bueno, Enrique Arjona y Andrés Arjona
90
DISCUSION
Es bien conocido el hecho que los algoritmos iterativos de
integración numérica pueden desempeñarse de manera pobre en muchas
circunstancias. Esto es especialmente cierto cuando se trabaja con
modelos que contienen ecuaciones diferenciales parciales cuya
resolución numérica requiere la solución de grandes sistemas de
ecuaciones simultáneas. El análisis de regresión es otra área en la que
podemos terminar con resultados no deseables. Una matriz de
covarianza inestable puede producir una inversa sin sentido. La
precisión arbitraria puede ser muy valiosa en dichos casos. Las rutinas
presentadas aquí, en una versión ligeramente distinta, han probado ser de
gran ayuda en estudios de caso de finanzas y ciencias de la vida en los
que los procedimientos usuales fallaron.
La precisión arbitraria puede también ser de gran ayuda para
implementar algoritmos que son fácilmente entendibles pero
numéricamente inestables. En estos casos, la precisión arbitraria puede
hacer innecesario el conocimiento de técnicas sofisticadas de análisis
numérico. De hecho, las rutinas presentadas, implementadas en una
computadora 486-DX4 corriendo a 100 MHz con 8 megabytes de RAM,
han sido probadas con algoritmos ineficientes en problemas numéricos
clásicos y se han desempeñado muy bien, tanto en términos de tiempo de
ejecución, como de exactitud.
Comunicaciones en Socioeconomía, Estadística e Informática 2002, Vol. 6 Núm. 1, pp 63-91
91
Finalmente, a pesar de que las rutinas dadas fueron desarrolladas
para aritmética real de precisión arbitraria, las rutinas pueden utilizarse
también para aritmética entera de precisión arbitraria implícita. En este
caso, las variables enteras del programa del usuario que requieran de
precisión arbitraria deben ser convertidas primero a reales, y luego a
precisión arbitraria. Una rutina adicional para pruebas de derrame
(overflow) podría ser muy conveniente.
Agradecimientos: Este artículo fue publicado con apoyo del
CONACYT a través del Proyecto 31984-B
REFERENCIAS
Bennett, B.S. 1995. Simulation Fundamentals. Prentice Hall
International, U.K. Collins, G.E. 1966. "PM-A System for Polynomial Manipulation."
Communications of the ACM, Vol. 9, no. 8: 578-579. Conte, S.D. and C. de Boor 1980. Elementary Numerical Analysis. An
Algorithmic Approach. McGraw-Hill International Book Company, Tokyo, Japan.
Knuth, D.E. 1969. The Art of Computer Programming, Vol II : Seminumerical Algorithms. Addison-Wesley, Reading, MA.
Maron, M.J. and R.J. Lopez. 1991. Numerical Analysis: A practical Approach. Wasdworth Publishing Companing,
Wolfran, S. 1991. Mathematica: A System for Doing Mathematics by Computer. Addison-Wesley Publishing Company, Menlo Park, CA.