La cosa va de que alguien preguntó en comp.sys.sinclair si había alguna forma de mostrar por pantalla números almacenados en memoria, de N bytes (pero no BCD, sino en binario natural), con N<256. Es decir, una rutina que fuera capaz de pasar a ASCII números de hasta 2040 bits.
Esta fue mi propuesta de código (he traducido el texto que originalmente puse en inglés)
Aquí la tenéis. No está optimizada para ir rápida. He podido probarla con números desde 8 bytes hasta 255 bytes. Para un número que conste de 255 bytes, todos ellos con el valor 255 (esto es, 2^(255*8)-1, el cálculo llevó 3213,82 segundos (usando Spectaculator con el multiplicador de velocidad al máximo, para no tener que esperar todos esos segundos en tiempo real). Se puede comprobar la exactitud del resultado usando la interfaz web para probar la librería GMP en http://www.swox.com/gmp/#TRY
Este es el resultado tras el cálculo mencionado:
La rutina está pensada para ser usada desde el BASIC, ya que retorna en BC la dirección de memoria del primer dígito ASCII del número convertido. Cuidado! ya que el algoritmo que he escrito extrae dígitos desde el menos hasta el más significativo, de tal forma que el número convertido se almacena en memoria de esa forma. Por tanto a la hora de leer dígitos habrá que hacerlo recorriendo la memoria hacia direcciones bajas, a partir de la dirección suministrada como resultado de USR, escribiendo los dígitos leídos, hasta que se encuentre un byte a NULL.
Creo que el pequeño programa BASIC que incluyo aclarará un poco lo que acabo de decir. La rutina comienza en 32768.
El algoritmo:
Para que esto funcione, tenemos que ser capaces e dividir un número arbitrariamente grande por 10, y obtener tanto el cociente como el resto. El resto es un valor pequeño, entre 0 y 9, que podremos guardar perfectamente en un registro. El cociente se convierte en el dividendo de una nueva división, así que podemos guardarlo en el mismo sitio donde estaba el dividendo original (la rutina de división usada precisamente tiene esta propiedad).
Seguimos dividiendo por 10, extrayendo restos, y guardándolos en memoria, hasta que el dividendo sea 0. En ese momento, la conversión ha finalizado.
Para la división me he basado en código que se muestra en la página de Milos "baze" Bazelides: "Z80 bits" : http://map.tni.nl/sources/external/z80bits.html . Más concretamente, el algoritmo que muestra para la división sin signo entre un valor de 16 bits entre un valor de 8 bits..
Lo que en el algoritmo original es un simple ADD HL,HL (HL contiene el dividendo, que después se convierte en cociente), se convierte aquí a una rutina separada que se encarga de la misma operación (esencialmente, multiplicar por 2) el número completo almacenado en memoria.
Lo que en el algoritmo es un INC L, en esta versión es un INC (HL). El número por cierto, está guardado (little endian) a partir de la dirección apuntada por HL.
El bucle principal simplemente llama a la rutina de división para obtener un nuevo resto, lo convierte a un dígito ASCII y lo almacena en memoria apuntada por DE. Si el número no es 0, el bucle vuelve a empezar. Al final, el valor de DE se ajusta restandole 1 y se copia a BC para devolverlo al BASIC, que se encarga de leer la memoria hacia atrás, escribiendo los dígitos leídos hasta encontrar el NULL.
BASIC loader
Código: Seleccionar todo
10 CLEAR 32767
20 LOAD ""CODE
30 LET t1=PEEK 23672+256*PEEK 23673+65536*PEEK 23674
40 LET adr=USR 32768
50 LET t2=PEEK 23672+256*PEEK 23673+65536*PEEK 23674
60 LET digit=PEEK adr: IF digit THEN PRINT CHR$ digit;: LET adr=adr-1: GO TO 60
70 PRINT: PRINT "Conversion took ";(t2-t1)/50;" seconds."
Para usarlo, se debe pokear el valor a convertir a partir de la dirección apuntada por NUMERO. En NBYTES se pokea la cantidad de bytes que ocupa el número a convertir, y finalmente se llama a la rutina con algo como "LET adr=USR 32768" para obtener en "adr" la dirección del primer dígito (el más significativo) del número convertido.
Aseembler source code (compiled with PASMO 0.5.2)
Código: Seleccionar todo
This source code is regulated under the GPL license. Original division routine (c)Milos "baze" Bazelides, ( baze@stonline.sk ) . Rest of code (c)Miguel Angel Rodriguez Jodar ( rodriguj@atc.us.es )
org 32768
Main proc
ld de,CADENA
otro_digito push de
call Obtener_Resto ; Obtiene el siguiente resto (dígito)
pop de
add a,'0' ; Lo convierte a un dígito ASCII
ld (de),a ; y lo almacena
inc de
call Check_Cero ; El nuevo dividendo es 0?
jr nz,otro_digito ; Si no es así, ve a obtener otro dígito
dec de ; Ajusta la dirección final, para apuntar al último dígito generado
ld b,d ; Y la copiamos en BC para devolverla como resultado de USR
ld c,e
ret ; Retorno al BASIC
endp
Obtener_Resto proc
ld a,(NBYTES) ; A = cantidad de bytes que ocupa el número original.
; Como necesitamos en realidad el número de bits, multiplicamos por 8
ld l,a
ld h,0
add hl,hl
add hl,hl
add hl,hl
ex de,hl ; DE = cantidad de bits que ocupa el número original.
; number
ld c,10 ; C guarda el divisor, que siempre es 10.
ld hl,NUMERO ; HL apunta al dividendo ante de dividir/cociente tras la división
xor a ; Ponemos el resto inicial a 0
buc_div push hl ; Este es, esencialmente, el mismo algoritmo
; de Milos Bazelides, con los cambios descritos anteriormente.
call Mult_2 ; Sustituye a ADD HL,HL en el algoritmo original
pop hl
rla
cp c
jr c,no_incr
sub c
inc (hl) ; Sustituye a INC L en el algoritmo original
no_incr dec de
push af
ld a,d
or e
jr z,fin_div
pop af
jr buc_div
fin_div pop af
ret
endp
Mult_2 proc
ld hl,NBYTES
ld b,(hl)
ld hl,NUMERO
sla (hl)
inc hl
dec b
ret z
buc_desplaza rl (hl)
inc hl
djnz buc_desplaza
ret
endp
Check_Cero proc
ld hl,NUMERO
ld a,(NBYTES)
ld b,a
check_byte ld a,(hl)
or a
jr nz,no_cero
inc hl
djnz check_byte
xor a
no_cero ret
endp
;Variables
NUMERO ds 255,255 ; 255 bytes rellenos con el valor 255. Formato little-endian. Este es el número más
; grande que esta rutina puede manejar.Equivale a aproximadamente 1,262383049660E+614
NBYTES db 255 ; Cuantos bytes ocupa el número
db 0 ; Byte a NULL para marcar que no hay más dígitos en el número convertido.
CADENA equ $ ; A partir de aquí se almacena el número, desde el dígito menos significativo
; hasta el más significativo, lo que significa que para leerlo y mostrarlo, habrá que recorrer
; la memoria desde la posición que nos indique USR, hacia atrás, hasta encontrar el NULL de antes.
Si se quiere que la rutina imprima directamente el número basta con sustituir la rutina Main por ésta:
Código: Seleccionar todo
Main proc
ld a,0feh
call 1601h ;Abrir canal 2
ld de,CADENA
otro_digito push de
call Obtener_Resto
pop de
add a,'0'
ld (de),a
inc de
call Check_Cero
jr nz,otro_digito
dec de
imprime_num ld a,(de)
or a
ret z
rst #10
dec de
jr imprime_num
endp
De esta forma se puede probar un con simple RANDOMIZE USR 32768