assembly 摩托罗拉68k:将数字转换为ascii

iqxoj9l9  于 2023-03-08  发布在  其他
关注(0)|答案(2)|浏览(168)

我想把无符号数字转换成一个68k asm格式的ascii字符串,我可以对单词范围内的一些字符进行转换:

move.w #12345,d0    ;example number to convert (unsigned word)
lea   text,a0       ;adress of output string 
;first digit    
and.l #$ffff,d0     ;mask lower word    
divu  #10000,d0     ;calc 10k digit -> d0: remainder.result
add.w #$30,d0       ;convert d0 lowerword to ascii
move.b d0,(a0)+     ;write ascii byte to mem
;next digit
swap  d0            ;continue with remainder
and.l #$ffff,d0
divu  #1000,d0      ;calc 1k digit
add.w #$30,d0
move.b d0,(a0)+
;...and so on

上面的代码可以工作。它是展开的,可能可以打包成一个循环。然而,我真正想做的是把unsigned long转换成它们的ascii表示。但是,当然,divu只允许16位的字作为除数,所以divby#10000是可能的,但是divby#100000是不可能的。如何对unsigned long的32位数字范围进行转换呢?

lokaqttq

lokaqttq1#

我为一个街机重制版写了一个long到ascii的整数转换器。
实际上,您可以使用高达(1<<16*10)-1的DIVU,因为655359/10仍然适合16位(并且是65535)
更进一步,将一个长数除以100006553590000 > 1<<32也不能大于655359,所以结果将适合16位。
然后继续进行你刚才得到的除法的余数,对下半部分做同样的操作。
如果num = (1<<32)-1(最大无符号值,又称$ffffffff),num除以10000等于429496,小于65536*10-1,所以可以处理,余数也可以用同样的方法处理。
这是我从我的游戏改编并测试过的一些代码。零填充看起来有点过,但你可能不需要它。

; main test code
main:
    move.l  #$12345678,d0
    lea     outbuf(pc),a1
    move.w  #0,d1
    bsr     write_decimal_number
    ; A1 contains "305419896"
    move.l  #2000001,d0
    lea     outbuf(pc),a1
    move.w  #0,d1
    bsr     write_decimal_number
    ; A1 contains "2000001"

    rts
    
outbuf
    ds.b    100,0
    
; the conversion routine
; < A1: buffer to write into
; < D0: 32 bit unsigned number
; < D1: number of padding zeroes
    
write_decimal_number:
    movem.l A0-A1/D2-d5,-(a7)
    move.l  d1,d3   ; quickly adapted, old code needed D0 and D1 for coordinates
    move.l  d0,d2
    cmp.w   #18,d3
    bcs.b   .padok
    move.w  #18,d3
.padok
    cmp.l   #655360,d2
    bcs.b   .one
    sub.l   #4,d3
    move.w  d0,d5
    ; first write high part    
    divu    #10000,d2
    swap    d2
    moveq.l #0,d4
    move.w   d2,d4
    clr.w   d2
    swap    d2
    bsr     .write_num
    subq.w  #1,a1   ; cancel last zero
    move.l  d4,d2
    moveq   #4,d3   ; pad to 4
.one
    bsr     .write_num
    clr.b   (a1)
    movem.l (a7)+,A0-A1/D2-d5
    rts
.write_num
    lea .buf+20(pc),a0
    tst.w   d2
    beq.b   .zero
.loop
    divu    #10,d2
    swap    d2
    add.b   #'0',d2
    subq    #1,d3
    move.b  d2,-(a0)
    clr.w   d2
    swap    d2
    tst.w   d2          ; probably unnecessary, swap sets Z flag already
    beq.b   .write
    bra.b   .loop
.zero
    subq    #1,d3
    move.b  #'0',-(a0)
.write
    tst.b   d3
    beq.b   .w
    bmi.b   .w
    subq    #1,d3
.pad
    move.b  #'0',-(a0)
    dbf d3,.pad
.w
    move.b  (a0)+,(a1)+
    bne.b   .w
    rts
.buf
    ds.b    20
    dc.b    0
    even
xqk2d5yq

xqk2d5yq2#

您所展示的算法向前提取数字,例如,从高阶十进制数字开始,然后到低阶。
DIVU指令将32位被除数除以16位除数,得到16位商与16位余数。
所以,关键是要使用这个特性,在某种程度上,这意味着首先提取低位数字,这可以在标准的itoa中看到。
与从最左到最右使用更小的10的幂不同,这对从右到左的每个数字使用10的除法和模数。
由于数字是按逆序生成的,因此有几种方法可以反转字符串,例如:

  • 在产生数字后反转数字(如上面的例子所示);
  • 从缓冲区的末尾开始,并将它们反向相加(例如,使用-(a0)代替(a0)+)。
  • 然后可以用ascii 0(零)数字填充缓冲区的剩余部分,或者,
  • 执行movmem的等效操作以将其滑动到缓冲区的开头,或者,
  • 如果你能忍受的话:返回缓冲区中放置最后一个数字的指针给调用者,这样他们就有了字符串的开头。

这种方法的一个优点是,当商为0时,您可以提前停止,因为所有剩余的数字都将为零。
当然,DIVU的问题是,由于只捕获16位结果,因此大数在除以10时会产生溢出。在溢出情况下,由于目标寄存器保持不变,因此既得不到商也得不到余数。
因此,这里有一个技巧,涉及到两个除法运算。x86版本见这里:Trying to Display a 32bit number in Assembly 8086 32bit
算法是这样的:

#include <stdio.h>

// using simple global variable to hold the resulting string
// but of course this can be edited to be parameterized instead
static char buf[20];

char *div2(unsigned long dividend) {
    unsigned short dvUpper16 = dividend >> 16;
    unsigned short dvLower16 = (unsigned short) dividend;
    char *bufPtr = buf + sizeof(buf);
    *--bufPtr = 0;

    for (;;) {
        unsigned long 
        dvTemp32  = dvUpper16;       // zero extending upper16 to 32 bits
        dvUpper16 = dvTemp32 / 10;   // first DIVU
        unsigned short 
        remainOne = dvTemp32 % 10;   // get mod from first DIVU
        
        dvTemp32 = (remainOne << 16) + dvLower16;
        dvLower16 = dvTemp32 / 10;  // second DIVU
        unsigned short 
        remainTwo = dvTemp32 % 10;  // get mod from second DIVU

        // print or capture remainTwo
        printf("%d\n", remainTwo);
        *--bufPtr = remainTwo + '0';
        
        if ( (dvUpper16 | dvLower16) == 0 )
            break;
    }
    
    return bufPtr;
}

int main()
{
    char *ptr = div2(543217689);
    printf("%s\n", ptr);
}

这种双除法安排适用于32位被除数和16位除数,并且永远不会溢出16位结果。
这将很容易地转换为68 k,因为所有需要的操作符都在那里。
由于DIVU同时产生商和余数,并且两者都需要,在68 k中这总共只需要两个DIVU。

相关问题