FN: Todo sobre FuNciones (Parte II) en combinación con USR y VAL en el Spectrum. En la primera parte trataba sobre la sentencia DEF FN del Spectrum y la comparaba con su equivalente tanto en otros lenguajes de programación como son el Fortran o el Pascal, además de otros BASICs. Este texto reune en cambio todas mis demás notas sobre DEF FN en combinación con otras instrucciones relacionadas con la misma, en este caso VAL y USR para la definición de nuevas funciones de usuario en el ZX SPECTRUM. INVOCANDO UNA FUNCION FNnombre(arg) Toda función definida por el usuario es invocada mediante el prefijo FN seguido del nombre de la función conformado por una sola letra de la A a la Z. El Basic de Darmouth originalmente solo aceptaba definir funciones matemáticas de un sólo argumento. Es la forma sintáctica de la gran mayoría de BASICs para 8 bits como Applesoft, Commodore o TRS80, los cuales pueden aceptar más de una letra pero solo valen los dos primeros. FNnombre{(arg1,arg2,...args)} Las sigtes. versiones de Basic ya aceptaban más de un argumento o ninguno, pero seguía aceptando solamente funciones matemáticas. FNnombre{$}(arg{1$....args[$]}) Esta es la forma final la cual acepta funciones definidas tanto numéricas como de cadena, donde el nombre puede ser de más de un caracter, como es el caso del Basic GW de Microsoft. El Spectrum acepta en cambio una sola letra en cualquiera de los casos de FN. DEFINIENDO UNA FUNCION EN CODIGO MAQUINA EN COMBINACIÓN CON USR (Extraido del artículo "Circulos redondos" pags. 8-12 de Todospectrum #15 año 2 nov. 1985) "Si cogemos el manual de Programación (nunca nos cansaremos de recomendarlo) y lo abrimos en el capítulo 25, nos encontramos con que existe una variable del sistema conocida como DEFADD, que guarda la dirección del argumento de una función definida por el usuario. Esto nos debería llevar a investigar un poco en este sentido. Las conclusiones sacadas son muy interesantes. Resulta que después de la definición de cada una de las variables de la función hay espacio para almacenar los valores de los parámetros que pasamos a ésta. Es la única vez en la que se trabaja directamente sobre el área de programa, operando sobre ella. Veamos un caso concreto para fijar ideas: DEF FN C (U, V, W)=... Cuando se localiza un FN, se busca la definición, y se procede a transferir los valores de la linea de llamada a la del DEF FN. A continuación, pasaremos a trabajar en esta línea. Cuando aparece una variable, husmearemos primero en nuestro particular área de variables. En caso de no encontrarlo, pasaríamos entonces a buscarlo entre las auténticas variables. Si resulta que la definición de la función es un USR, nuestra rutina recibirá el control del ordenador cuando DEFADD apunta a la dirección del primer parámetro del DEF FN, concretamente al nombre de la variable. Los parámetros deben ser necesariamente de una letra, por lo que sabremos inmediatamente dónde empieza el espacio dedicado al número. Y si el valor asignado es un entero comprendido entre -65535 y +65535 (ver capítulo 24 del Manual), los números se almacenarán en un formato especial (no el de coma flotante), por lo que encontraremos en los bytes tercero y cuarto de los dedicados al número, nuestro valor en el formato que usa el Z-80. Incluso conservaremos el signo. Vamos a expresar gráficamente la asignación de valores en el programa ejemplo. 00 00/FF XX X 00 58 OF ------------------ IX+00IX+04 00 00/FF YY YY 00 2C59 OF ------------------- IX+12 DEFADD 00 00/FF RR RR 00 ...52 OF ------------------- IX+20 El programa permite introducir la rutina en el buffer de impresora. Una última recomendación: recuerde que siempre que hagamos uso del FN deberemos usar números enteros como parámetros. Si no lo hiciéramos así, puede pasar un buen rato antes de que recuperemos el control del aparato." Programa ejemplo: 10 DATA 217,229,243,221,42,11,92,221,78,4,221,70,5,221,94,12 20 DATA 221,86,13,221,110,20,221,102,21,213,17,0,0,213,197,229 30 DATA 221,33,0,0,221,57,229,217,225,41,235,33,3,0,167,237 40 DATA 82,217,167,237,82,250,166,91,62,2,8,209,225,25,235,193 50 DATA 225,9,221,249,205,175,91,209,225,25,235,193,225,167,237,66 60 DATA 221,249,205,175,91,209,225,167,237,82,235,193,225,9,221,249 70 DATA 205,175,91,209,225,167,237,82,235,193,225,167,237,66,221,249 80 DATA 205,175,91,193,225,209,197,229,213,8,61,194,58,91,197,217 90 DATA 235,203,122,40,8,225,41,41,1,6,0,24,12,225,193,11 100 DATA 197,167,237,66,41,41,1,10,0,9,25,217,225,209,209,19 110 DATA 213,221,249,195,50,91,33,8,0,57,249,225,217,251,201,122 120 DATA 167,192,124,167,192,62,175,189,216,69,75,205,170,34,71,4 130 DATA 62,1,15,16,253,182,119,201 200 LET SUM=0: FOR I=0 TO 199: READ A: POKE (23296+1),A: LET SUM=SUM+A: NEXT I 210 IF SUM<>27735 THEN BEEP .2,10: PRINT "Error en el DATA": STOP 300 REM 310 REM Demostración 320 REM 330 DEF FN C(X,Y,R)=USR 23296 340 FOR I=0 TO 180 STEP 5: RANDOMIZE FN C(126,88,I): NEXT I Podemos ver los muchos usos de esta técnica, no solo es posible definir una función en C.M. sino también nuevos comandos a los cuales pasarles datos a travez de n parámetros según la nueva instrucción lo requiera. Nota: el artículo original tal como su nombre indica, estaba dedicado de modo exclusivo y pensando en crear una mejor instrucción CIRCLE, así que solamente tomé la parte que me interesa, la que ve el modo de usar DEF FN en combinación con USR. REDEFINIENDO UNA FUNCION DENTRO DEL PROGRAMA MISMO En otros BASICs (Tomado del artículo "El coseno por correspondencia" pag.85 de El Ordenador Personal #45 de febrero de 1986) "Con frecuencia se puede leer en los menús de los programas matemáticos más o menos gráficos: LA FUNCION F(x) DEBE SER DEFINIDA POR EL USUARIO EN LA LINEA 340 SEGUN EL MODELO: 340 DEF FN(X)=SEN(X) COS(X) ¿Por qué molestarse si la solución está en un programa Basic? Este programa ante todo graba la función matemática; un análisis sintáctico determina el código Basic que traduce la fórmula ma­temática {ejemplo 220 para LOG). Después de la modificación se POKEa en el transcurso y en el corazón del programa. El único imperativo es que la primera lí­nea debe comenzar por DEF F{X)=X:REM seguido por un máximo de "*" (o cualquier otro símbolo) en el límite del intérprete. El subprograma 30000 interviene para definir una nueva función, tras el signo =. Este utilitario se puede adaptar a muchos otros ordenadores si se conoce la dirección de principio de un programa Basic en ROM y los códigos de las palabras clave (ver recuadro). Cuidado con las expresiones lógicas: para Apple X=(A=10) es igual a 1 (o sea cierto) si A=10, e igual a 0 (o falso) si A 10. Para Oric y CBM o Basic Microsoft, X será igual a -1 si A=10, e igual a O si A<>10." Codificación de las palabras clave de las funciones matemáticas para Apple, Oric, MSX y CBM. Apple Oric CBM MSX (Basic4.0) NOT 198 202 168 223 + 200 204 170 241 - 201 205 171 242 * 202 206 172 243 / 203 207 173 244 - 204 208 174 AND 205 209 175 246 OR 206 210 176 247 > 207 211 177 238 = 208 212 178 239 < 209 213 179 240 SGN 210 214 180 255.132 INT 211 215 181 255.133 ABS 212 216 182 255.134 SQR 218 222 186 255.135 RND 219 223 187 255.136 LOG 220 224(LN) 188 255.138 EXP 221 225 189 255.139 COS 222 226 190 255.140 SIN 223 227 191 255.137 TAN 224 228 192 255.141 ATN 225 229 193 255.1f2 LOG10 232 PI 238 El programa: 10 DEF FN F(X)=X*X:REM*********************************************************** **************************************************************** 20 GOSUB 29999 30 INPUT "VALOR DE X ";X$:IF X$="" THEN 70 40 X=VAL(X$) 50 PRINT X,FN F(x) 60 GOTO 30 70 PRINT "OTRA FUNCION (S/N) ?"; 71 R$=INKEY$:IF R$="" THEN 71 72 IF R$="S" THEN 20 80 END 20000 REM CODIGOS DE FUNCIONES MATEMATICAS DE APPLESOFT 29999 A=200:B=201:C=202:D=203:E=204:F=207:G=208:H=209:I=223:J=222:K=224:L=218: M=220:N=221:O=198:P=205:Q=210:R=211:S=212:T=219:U=225:U=206 30000 CLS:INPUT "FUNCION F(X) = ";F$:K=0 30001 FOR Y=1 TO LEN(F$):X=ASC(MID$(F$,Y,1)):MI$=MID$(F$,Y,1):W=W+1 30002 X1=A*(X=43)+B*(X=45)+C*(X=42)+D*(X=47)+E*(X=94)+F*(X=62)+G*(X=61)+H*(X=60) :IF X1>0 THEN X=X1: GOTO 30006 30003 X1=I*(MI$="SIN")+J*(MI$="COS")+K*(MI$="TAN")+L(MI$="SQR)+M*(MI$="LOG")+N *(MI$="EXP")+O*(MI$="NOT")+P*(MI$="AND"):IF X1>0 THEN X=X1:Y=Y+2:GGT0 30006 30004 X1=Q*(MI$="SGN")+R*(MI$="INT")+S*(MI$="ABS")+T*(MI$="RND")+U*(MI$="ATN): IF X1>0 THEN X=X1:Y=Y+2:GOTO 30006 30005 X1=V*(LEFT$(MI$,2)="OR"):IF X1>0 THEN X=X1:Y=Y+1 30006 POKE 2059+W,X:REM DIRECCION DE COMIENZO DE LA RAM APPLESOFT 30007 NEXT Y 30006 POKE 2060+W,58:POKE 2061+W,178 30009 RETURN 40010 REH * (c) EL O.P. y R. JOST * En el Spectrum con VAL Ninguna versión de BASIC puede, tal como se ha visto en el programa ejemplo anterior, redefinir una función una vez añadida al listado del programa. Así que la única solución es POKEar a una línea REM. El Basic Sinclair aunque también puede hacer uso de ese truco, puede en cambio, mediante VAL, hacer precisamente eso, redefinir una función dentro del programa, sin tener que detenerlo para editar y retipear toda la línea del FN. He aquí la conversión del programa anterior portado al Spectrum: 10 DEF FN F(X)=VAL F$ 20 GOSUB 3000 30 INPUT "VALOR DE X ";X$:IF X$="" THEN GOTO 70 40 LET X=VAL X$ 50 PRINT X,FN F(X) 60 GOTO 30 70 PRINT "OTRA FUNCION (S/N) ?" 71 PAUSE 0:LET R$=INKEY$:IF R$="" THEN GOTO 71 72 IF R$="S" THEN GOTO 20 80 STOP 2000 REM 3000 CLS:INPUT "FUNCION F(X) = ";F$ 3002 IF F$="" THEN LET F$="X*X" 3004 PRINT F$ 3006 RETURN La única limitación es que se respeten la cantidad de parámetros dentro de la función. En el ejemplo se usa un solo parámetro, la cadena por lo tanto debe almacenar una expresión matemática de función con un solo argumento, de lo contrario devolverá error. A modo de apendice Mas funciones multilinea ahora con DIM Ejemplo 1 - secuencial: 10 REM Programa Fibonaci 20 LET n=15 30 GOSUB 1000 40 PRINT FN f() 50 STOP 1000 DEF FN f()=FIBONACI 1010 DIM f(n) 1015 LET f(1)=1: LET f(2)=1 1020 FOR m=3 TO n 1030 LET f(m)=f(m-1)+f(m-2) 1040 NEXT m 1050 LET fibonaci=f(m-1) 1060 RETURN Ejemplo 2 - la misma rutina anterior pero ahora recursivo: 1000 DEF FN f()=FIBONACI 1010 DIM f(n+1) 1015 LET f(n+1)=1: LET f(n)=1 1020 IF n=2 THEN RETURN 1030 LET f(n-1)=f(n+1)+f(n) 1040 LET n=n-1 1050 GOSUB 1020: LET fibonaci=f(n) 1060 RETURN Y PARA CERRAR A continuación unas cuantas funciones matematicas inexistentes (publicadas en diversos medios como libros y revistas) en el Basic Sinclair y que pueden ser calculadas y por lo mismo definibles mediante FN: LOG10(X)=LN(X)/2.30258509 - Logaritmo comun en base 10 LOGB(X)=LN(X)/LN(B) - Logaritmo de X en base B MODM(X)=INT((X/M-INT(X/M))*M+.5)*SGN(X/M) - Modulo: el resto tras hacer X/M SEC(X)=1/COS(X) - Secante CSC(X)=1/SIN(X) - Cosecante COT(X)=1/TAN(X) - Cotangente ARCSEC(X)=ATN(X/SQR(X*X-1))+SGN(SGN(X)-1)* PI/2 - Arco Secante ARCCSC(X)=ATN(X/SQR(X*X-1))+SGN(X)-1)* PI/2 - Arco Cosecante ARCCOT(X)=ATN(X)+ PI/2 - Arco Cotangente SINH(X)=(EXP(X)-EXP(-X))/2 - Seno Hiperbolico COSH(X)=(EXP(X)+EXP(-X))/2 - Coseno Hiperbolico TANH(X)=EXP(X)-EXP(-X))/+(EXP(X)+EXP(-X)) -Tangente Hiperbolico SECH(X)=2/(EXP(X)+EXP(-X)) - Secante Hiperbolica CSCH(X)=2/(EXP(X)-EXP(-X)) - Cosecante Hiperbolica COTH(X)=EXP(-X)/(EXP(X)-EXP(-X))*2+1 - Cotangente Hiperbolica ARCSINH(X)=LOG(X/SQR(X*X+1)) - Arco Seno Hiperbolico ARCCOSH(X)=LOG(X+SQR(X*X-1)) - Arco Coseno Hyperbolico ARCTANH(X)=LOG((1+X)/(1-X))/2 - Arco Tangente Hyperbolica ARCCSCH(X)=LOG(SGN(X)*SQR(X*X+1)+1)/X - Arco Cosecante Hyperbolica ARCSECH(X)=LOG(SQR(-X*X+1)+1)/X - Arco Secante Hyperbolica ARCCOTH(X)=LOG((X+1)/(X-1))/2 - Arco Cotangente Hyperbolica GRADOS(X)=X*180/PI - donde X es en radianes RADIANES(X)=X*PI/180 - donde X es en grados El Spectrum ya posee en su BASIC, las funciones ASN, ACS y ATN que son para Arco Seno o inversa del seno, Arco Coseno y Arco Tangente. La gran mayoría de BASICs carece de estas funciones por lo que deben ser definidas con DEF FN. (c)2021 zx_if1@hotmail.com