[ Sekalaiset ] [ Sivukartta ]

Etumerkittömän kokonaisluvun konvertoiminen ASCII-muotoon MC680x0:lla
Markus Ketola

Konekieliohjelmoija törmännee ennemmin tai myöhemmin tilanteeseen, jossa datarekisterissä oleva arvo olisi esim. debuggauksen vuoksi saatava tulostettua näkyviin. Korkeamman tason ohjelmointikielillä muuttujan arvon tulostaminen käy kivuttomasti jonkin funktion (esim. C-kielen printf) tai käskyn (esim. Basicin print) avulla, mutta konekieliohjelmoijan on tehtävä kaikki itse.

Algoritmi luvun hajottamiseksi yksittäisiksi digiteiksi

Käsiteltävä luku tulkitaan 10-järjestelmän luvuksi.

  • Selvitetään luvun digittien määrä n
  • jaetaan n:s digit ykkösiin
  • tyhjennetään edellisen jaon jakojäännös
  • jaetaan vielä kerran 10:llä
  • nollataan kaikki muu paitsi jakojäännös;
  • haluttu digit on nyt jakojäännöksessä [, poimitaan talteen]
  • vähennetään n:ää yhdellä
  • toistetaan algoritmiä kunnes n = 0.

Toteutus 32-bittisille kokonaisluvuille MC68040:lla

Suurin mahdollinen positiivinen 32-bittinen kokonaisluku on 2^32 - 1 = 4294967295 ja pienin tietysti 0. Mutta miten saada helposti selville annetun luvun digittien määrä? Joskus laskimella leikkiessäni keksin sattumalta kaavaan

n = trunc(Log10(luku)) + 1

joka kertoo digittien määrän 10-järjestelmän luvussa. Nolla tosin täytyy käsitellä erikseen, koska siitä ei voi ottaa 10-kantaista logaritmia.

Esimerkkirutiinissa lisätään annettuun lukuun 0.1 ennen kuin siitä otetaan 10-kantainen logaritmi FPU:n FLOG10-käskyllä. Tämä siksi, että muutoin 10:n potensseista tulee kokonaisluvuksi katkaisun jälkeen ykkösen verran liian pieniä tulokseksi. Tämä on sikäli outoa, että luvuista 10n luulisi tulevan helposti tasaluku vastaukseksi. Testieni mukaan FLOG10-käsky antaa 10:stä vastaukseksi noin "1 - epsilon", eli jotain hyvin lähellä 1:tä olevaa. Tosin FPU:ssa on jokin rekisteri, joka määrää mihin päin tulos pyöristetään, mutta rekisteristä tiedän vain sen olemassaolon. Muistelenkin jostain lukeneeni, ettei FPU:n käskyjä suositella käytettävän suoraan. Olisinkin käyttänyt tässä matematiikkakirjastoja, jos vain omistaisin dokumentaatiota niiden käytöstä. Tosin C-kielellä tekemäni kokeilut viittaavat siihen, että kyseisillä kirjastoillakin tulee vastaava ongelma eteen.

Esimerkkirutiini:

;-------------------------------------------------------------------------------
; Integer2ASCII_32bit, '040 version.
; Uses CPU registers D0-D3,D7 & A2 and FPU register FP0.
;
; - Converts 32 bit unsigned integer given in D0 into ASCII, destination
;   address (where the digits are stored) is given in A2
;-------------------------------------------------------------------------------
Integer2ASCII_32bit
               moveq    #0,d2
               moveq    #0,d7

               cmp.l    #0,d0                ; Nolla tulostetaan erikseen,
               beq.s    justzero             ; koska siitä ei voi ottaa LOG10:tä

               fmove.l  d0,fp0               ; Konvertoitava numero FP0:aan

; Lasketaan digittien määrä, n = trunc(LOG10(number)) + 1

               fadd.x   #0.1,fp0             ; Pyöristysongelman vuoksi
               flog10.x fp0                  ; LOG10
               fmove.l  fp0,d7               ; DBF-käskyn vuoksi +1:tä
                                             ; ei tarvita

; Konvertoidaan annettu 32-bittinen kokonaisluku ASCII-muotooon

dloop          moveq    #0,d2
               moveq    #0,d3

               move.l   d0,d1                ; Työkopio D0:sta

               fmove.l  d7,fp0
               ftentox.l   fp0               ; FP0 = D = 10^(k-1)
               fmove.l  fp0,d3
goon                                         
               divs.l   d3,d1                ; Luku / D, jakojäännös tippuu
                                             ; automaattisesti
               divsl.l  #10,d2:d1            ; Jako 10:llä vielä kerran;
                                             ; jakojäännös menee D2:een, missä
                                             ; haluttu digit nyt on.
justzero       add.b    #'0',d2              ; Lisätään nollan ASCII-koodi
                                             ; saatuun digittiin
               move.b   d2,(a2)+             ; ja talletetaan saatu ASCII-arvo
               dbf      d7,dloop

Otsikosta huolimatta yllä olevan rutiinin pitäisi toimia sellaisenaan MC68020+ ja FPU -yhdistelmällä.

Toteutus 16-bittisille kokonaisluvuille MC68000:lla

Suurin mahdollinen 16-bittinen positiivinen kokonaisluku on 2^16 - 1 = 65535, ja pienin tietysti taas nolla. Erona edelliseen rutiiniin on lisäksi digittien määrän selvittämistapa: Nyt annettua lukua jaetaan 10:llä, kunnes kokonaisosa on nolla; jakojen määrä vastaa luvun digittien määrää.

Esimerkkilistaus:

;------------------------------------------------------------------------------
; Integer2ASCII, käyttää rekistereitä D0-D3,D7 & A2, konvertoitava luku D0:aan
;------------------------------------------------------------------------------
               moveq    #0,d2
               moveq    #0,d3

; Lasketaan digittien määrä

               and.l    #$0000ffff,d0        ; Varmistetaan, että luku on
                                             ; kork. 16-bittinen
; Lasketaan digittien määrä

               move.l   d0,d1
amdigits       addq.l   #1,d2
               divu     #10,d1
               and.l    #$0000ffff,d1
               cmp.w    #0,d1
               bgt.s    amdigits
               move.l   d2,d7                ; Digittien määrä-1 D7:ään
               subq.l   #1,d7                ; (-1 DBF-käskyn vuoksi)

; Muutetaan annettu 16-bittinen positiivinen kokonaisluku ASCII-versiokseen

               lea      Luku,a2              ; Luvun ASCII-version osoite
dsil           move.l   d0,d1                ; Työkopio D0:sta talteen

               moveq    #1,d3                ; Määritetään 10 potssnsiin
               move.l   d7,d2                ; digitin järjestysnumero - 1
potsil         subq.l   #1,d2                ;
               cmp.l    #-1,d2               ;
               ble.s    goon                 ;
               mulu     #10,d3               ;
               bra.s    potsil               ;

goon           move.l   d3,d2                ; Jaetaan muutettava luku
               divu     d2,d1                ; yllä määritetyllä luvulla
               and.l    #$0000ffff,d1        ; Nollataan jakojäännös
               divu     #10,d1               ; Jaetaan vielä 10:llä
               swap     d1                   ; Otetaan jakojäännös ja nollataan
               and.l    #$0000ffff,d1        ; muu osa luvusta; jäljellä on
                                             ; haettu digit!

               add.l    #'0',d1              ; Lisätään digittiin 0:n ASCII-koodi
               move.b   d1,(a2)+             ; ja kirjoitetaan digit ASCIIna
               dbf      d7,dsil

Toteutus C-kielellä :-)

C-kielellä muuttajan arvon saisi tulostettua tietysti suoraan printf-funktiolla, mutta otin tämän esimerkin mukaan tuodakseni esiin, että tässä C-versiossa kummittelee vastaava pyöristysongelma kuin FPU:n FLOG10:tä käyttävässä esimerkissä. Ongelmakohta on LOG10-funktion tuloksen sijoittaminen integer-muuttujaan - double-muuttujaan sijoittaessa tulee toki oikea arvo.

Esimerkki:

/* Lasketaan digittien määrä */

  n=(int)log10(0.1+(double)luku)             /* 0.1:llä avitetaan
                                                "pyöristystä"              */

/* Silmukka jossa luku hajotetaan yksittäisiksi digiteiksi */

   for (i=n+1; i>0; i--) {
      jakaja = (int)pow(10,(double)(i-1));   /* Määritetään 10^n           */
      apuluku = (int)luku / jakaja;          /* n:s digit ykkösissä        */
      apuluku = apuluku % 10;                /* Jaetaan vielä 10:llä;      */
                                             /* jakojäännöksessä n:s digit */ 
      numero[n+1-i] = apuluku + '0';
   }

Merkille pantavaa on, että tekemäni kymmenkantaista logaritmia testaava ohjelma antaa eri vastauksen 68k-pohjaisessa Amiga-ympäristössä kuin Pentium-pohjaisessa Linux-ympäristössä siten, että jälkimmäisessä ympäristössä ohjelman antama tulos on oikein! Tämä siis tilanteessa, jossa 10-kantainen logaritmi luvuista 10n katkaistaan integer-muuttujaan. Alla kuvat testiohjelman tuloksista eri ympäristöissä. Ohessa vielä paketti, jossa ovat kaikki ohjelmat esimerkkilistauksineen.


[ Sekalaiset ] [ Sivukartta ]