Mostrar números muy grandes en la pantalla

Todo sobre la creación, diseño y programación de nuevo software para
nuestro Spectrum

Moderador: Sir Cilve Sinclair

Mostrar números muy grandes en la pantalla

Notapor mcleod_ideafix el Mie Mar 26, 2008 4:11 pm

Este es otro de los posts de comp.sys.sinclair que me he decidido a copiar al foro.

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:
Imagen

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
Web: ZX Projects | Twitter: @zxprojects
Avatar de Usuario
mcleod_ideafix
Johnny Jones
 
Mensajes: 3982
Registrado: Vie Sep 21, 2007 1:26 am
Ubicación: Jerez de la Frontera

Re: Mostrar números muy grandes en la pantalla

Notapor radastan el Mie Mar 26, 2008 4:42 pm

Y ahora la pregunta... ¿cual es el objetivo de todo esto?
De verdad, sin mala intención, simple curiosidad.
_________________________________________
Hay otras páginas.... pero no son Bytemaniacos
http://www.bytemaniacos.com
Orgullo de 8 bits
_________________________________________
Avatar de Usuario
radastan
Phantomas
 
Mensajes: 2179
Registrado: Lun May 07, 2007 5:34 pm

Re: Mostrar números muy grandes en la pantalla

Notapor mcleod_ideafix el Mie Mar 26, 2008 5:49 pm

El motivo del post original fue contestar a una pregunta que se hizo en comp.sys.sinclair donde alguien pidió si sería posible hacer algo como esto.
Me quedó tan completita la explicación :D, y dado que la multiplicación y la división no son los fuertes del Z80, creí que podría ser interesante tener esta rutina en la sección de programación y nuevos desarrollos. Nada más.
Web: ZX Projects | Twitter: @zxprojects
Avatar de Usuario
mcleod_ideafix
Johnny Jones
 
Mensajes: 3982
Registrado: Vie Sep 21, 2007 1:26 am
Ubicación: Jerez de la Frontera

Re: Mostrar números muy grandes en la pantalla

Notapor radastan el Mie Mar 26, 2008 6:19 pm

mcleod_ideafix escribió:El motivo del post original fue contestar a una pregunta que se hizo en comp.sys.sinclair donde alguien pidió si sería posible hacer algo como esto.
Me quedó tan completita la explicación :D, y dado que la multiplicación y la división no son los fuertes del Z80, creí que podría ser interesante tener esta rutina en la sección de programación y nuevos desarrollos. Nada más.


No, si la rutina la veo interesante, pero era más que nada saber el motivo para que alguien pretenda hacer semejante burrada en un ZX Spectrum.

Por cierto, una pregunta casi de novato, veo que usas proc/endp, cuando realmente no es necesario en ensamblador, ¿algún motivo en concreto? ¿aporta alguna ventaja? mi ensamblador es bastante recio, y estoy abierto a nuevos caminos para ganar el legibilidad.
_________________________________________
Hay otras páginas.... pero no son Bytemaniacos
http://www.bytemaniacos.com
Orgullo de 8 bits
_________________________________________
Avatar de Usuario
radastan
Phantomas
 
Mensajes: 2179
Registrado: Lun May 07, 2007 5:34 pm

Re: Mostrar números muy grandes en la pantalla

Notapor mcleod_ideafix el Mie Mar 26, 2008 6:33 pm

radastan escribió:No, si la rutina la veo interesante, pero era más que nada saber el motivo para que alguien pretenda hacer semejante burrada en un ZX Spectrum.

Ah! no lo sé, no le pregunté al autor de la pregunta el para qué lo quería, la verdad...

radastan escribió:veo que usas proc/endp, cuando realmente no es necesario en ensamblador, ¿algún motivo en concreto? ¿aporta alguna ventaja? mi ensamblador es bastante recio, y estoy abierto a nuevos caminos para ganar el legibilidad.

Uso PASMO normalmente para programar en ensamblador, y no sé si en este ensamblador es obligatorio o no. En mi caso, es por costumbre. No lo usaba cuando estaba con GENS/MONS, pero al empezar a programar en ensamblador del 8086, tanto el MASM como el TASM sí que lo usan, así que cuando "volví" con PASMO, he seguido usándolo.
En otro orden de cosas, usar PROC/ENDP permite al ensamblador delimitar dónde están los "trozos" de código, de tal forma que cuando se ensambla para arquitecturas en las que existe algún tipo de paginación, el enlazador puede asignar páginas distintas a distintos trozos de código. Es un poco lo que hace MASM/TASM cuando ensamblas para un 8086 y usas el modelo far o huge de memoria: te permite asignar segmentos distintos a distintas funciones.
En un Spectrum quizás tuviera algún uso en el 128K, si es que el ensamblador está preparado para ello: el enlazador podría automáticamente ubicar rutinas en otras páginas de memoria, y sustituir un CALL a una de estas rutinas por un OUT para "traer" la página donde está la rutina, seguido de un CALL para llamar a dicha rutina. No sé si en CP/M se hace algo parecido a esto.
Web: ZX Projects | Twitter: @zxprojects
Avatar de Usuario
mcleod_ideafix
Johnny Jones
 
Mensajes: 3982
Registrado: Vie Sep 21, 2007 1:26 am
Ubicación: Jerez de la Frontera

Re: Mostrar números muy grandes en la pantalla

Notapor decicoder el Mie Mar 26, 2008 8:20 pm

La pura satisfacción matemática.

Otro caso. En los ochenta empezó la loteria primitiva. En clase de matemáticas vimos la formula para calcular las probabiliadades de acertar 1 entre 13 milones y pico.

Tiempo después cuando conseguí el spectrum y supe un poco de emsablador se me ocurrió poner al Spectrum a construir y contar todas las combinaciones posibles una a una, a ver si era verdad lo de los 13 y pico. Y lo clavó.
xor a
ld R,a
b1 in f,(c)
jp pe , b1
ld a,R
Avatar de Usuario
decicoder
Jack The Nipper
 
Mensajes: 176
Registrado: Jue Jul 19, 2007 10:37 am

Re: Mostrar números muy grandes en la pantalla

Notapor Rafa el Mie Mar 26, 2008 10:09 pm

Hablando de la Primitiva, yo tengo un programita para Spectrum que te calcula de unos números dados, las combinaciones posibles para hacer una reducción al 5. O sea, que si los seis números de la primitiva están entre los números dados, como mínimo saldrá un boleto con 5 aciertos, eso como mínimo, porque pueden salir 5 y el complementario, de 3, de 4 aciertos, y hasta de 6.
Para 12 números, las apuestas son 68, 68 euros de apuesta, y si los números de la combinación ganadora están en los 12 elegidos, pues tienes como mínimo una de 5.
Si aumentas los números, por supuesto suben las apuestas a realizar.
Y funciona, porque sólo a la segunda semana de apostar, cogí 2700 eurazos. La cuestión es que con los 12 números aciertes la combinación ganadora. Con una apuesta sencilla sólo puedes tachar 6.
No puedo saber cómo coger 49 números para lograr una de 5, me sale "Number too big"
RANDOMIZE USR 0
Avatar de Usuario
Rafa
Jack The Nipper
 
Mensajes: 181
Registrado: Lun May 07, 2007 11:59 am

Re: Mostrar números muy grandes en la pantalla

Notapor mcleod_ideafix el Mie Abr 23, 2008 8:57 pm

radastan escribió:Por cierto, una pregunta casi de novato, veo que usas proc/endp, cuando realmente no es necesario en ensamblador, ¿algún motivo en concreto? ¿aporta alguna ventaja? mi ensamblador es bastante recio, y estoy abierto a nuevos caminos para ganar el legibilidad.


Ahora que estoy exprimiendo un poco más al PASMO, te puedo decir que hay una gran ventaja en delimitar las rutinas con PROC/ENDP: las etiquetas locales. PASMO se puede invocar con un parámetro que hace que todas las etiquetas de saltos, etc. que comiencen con un _ las tome como locales, siendo su ámbito el correspondiente al bloque delimitado por PROC/ENDP donde se encuentre dicha etiqueta.

Esto es muy útil cuando estás escribiendo rutinas que hacen cosas parecidas, y te apetece reutilizar nombres de etiquetas, por ejemplo:

SIN PROC/ENDP
Código: Seleccionar todo
ScrollHorDer4
                  di
                  call GrabaPant
                  ld hl,BuffPant
                  ld b,192
ScrollLinDer:     xor a
                  rept 32
                       rrd
                       inc hl
                  endm
                  djnz ScrollLinDer
                  call CargaPant
                  ei
                  ret


ScrollHorIzq4
                  di
                  ld hl,22527
                  ld b,192

ScrollLinIzq:     xor a
                  rept 32
                       rld
                       dec hl
                  endm
                  djnz ScrollLinIzq
                  ei
                  ret



CON PROC/ENDP
Código: Seleccionar todo
ScrollHorDer4     proc
                  local ScrollLin
                  di
                  call GrabaPant
                  ld hl,BuffPant
                  ld b,192
ScrollLin:        xor a
                  rept 32
                       rrd
                       inc hl
                  endm
                  djnz ScrollLin
                  call CargaPant
                  ei
                  ret
                  endp


ScrollHorIzq4     proc
                  local ScrollLin
                  di
                  ld hl,22527
                  ld b,192

ScrollLin:        xor a
                  rept 32
                       rld
                       dec hl
                  endm
                  djnz ScrollLin
                  ei
                  ret
                  endp


En este ejemplo, uso la directiva LOCAL para especificar qué etiquetas quiero que sean locales. La otra opción es la que comentaba antes: poner un _ delante del nombre de las etiquetas que quiero que se consideren locales a cada rutina.

Todo esto evita que en códigos largos, una etiqueta sea referenciada desde otro sitio que no es correcto. También evita los nombres esotéricos que tiene uno que buscarse para, por ejemplo, decir que una etiqueta es el comienzo de un bucle: una vez que hemos gastado "bucle", podemos usar "bucle2", "bucle3", o sus análogas anglosajonas: "loop1", "loop2", etc... Y si no nos gustan los números, acabamos llamando a los bucles: "bucle_externo", "bucle_interno", "bucle_interno2", etc. No sé si me explico. El que siga sin ver la ventaja, no tiene más que mirarse el listado del desensamble de la ROM (con nombres de etiquetas de verdad, no del tipo "L03F7").
Web: ZX Projects | Twitter: @zxprojects
Avatar de Usuario
mcleod_ideafix
Johnny Jones
 
Mensajes: 3982
Registrado: Vie Sep 21, 2007 1:26 am
Ubicación: Jerez de la Frontera

Re: Mostrar números muy grandes en la pantalla

Notapor NotFound el Mie Abr 23, 2008 9:59 pm

Supongo que no usarías la directiva PROC en gens porque no la tiene, principalmente :wink:

La incluí en pasmo porque estaba acostumbrado a usarla en 8086 y cuando volví a hacer cosas en Z80 la echaba de menos. Lo único que hace es controlar el ámbito de LOCAL, lo de usar prefijos especiales no me gustaba (y sigue sin gustarme, el modo autolocal lo añadí a petición popular). Es decir, que a pesar del nombre no está pensada específicamente para procedimientos, es solo por usar un nombre familiar.

Y ya que hablamos de Pasmo ¿alguno ha probado las 0.5.4.beta?
NotFound
rst 0
 
Mensajes: 8
Registrado: Dom Feb 17, 2008 2:46 am

Re: Mostrar números muy grandes en la pantalla

Notapor mcleod_ideafix el Jue Abr 24, 2008 12:38 am

Pues mira, la voy a probar.
Por cierto... ¿PASMO soporta múltiples ficheros fuente? En la versión que tengo es lo único que le echo en falta... es que tener todo el programa en un único fichero llega a hacerse incómodo. Hasta ahora lo que hago son "includes", pero claro, cada vez que compila, lo compila todo.
Web: ZX Projects | Twitter: @zxprojects
Avatar de Usuario
mcleod_ideafix
Johnny Jones
 
Mensajes: 3982
Registrado: Vie Sep 21, 2007 1:26 am
Ubicación: Jerez de la Frontera

Re: Mostrar números muy grandes en la pantalla

Notapor NotFound el Jue Abr 24, 2008 12:58 am

La rama 0.6 permite ensamblar módulos en formato .REL y enlazarlos, pero esa rama aún está incompleta y poco probada, hay que usarla con cuidado. Otro problema es que el formato .REL trunca los símbolos externos a 6 caracteres.
NotFound
rst 0
 
Mensajes: 8
Registrado: Dom Feb 17, 2008 2:46 am

Re: Mostrar números muy grandes en la pantalla

Notapor mcleod_ideafix el Jue Abr 24, 2008 1:58 am

Gracias! Esa característica debe ser muy compleja de implementar...
Web: ZX Projects | Twitter: @zxprojects
Avatar de Usuario
mcleod_ideafix
Johnny Jones
 
Mensajes: 3982
Registrado: Vie Sep 21, 2007 1:26 am
Ubicación: Jerez de la Frontera


Volver a Programación y nuevos desarrollos

¿Quién está conectado?

Usuarios navegando este Foro: No hay usuarios registrados visitando el Foro y 3 invitados