【问题标题】:Converting quaternary to octal. ASM 8086将四进制转换为八进制。 ASM 8086
【发布时间】:2016-12-14 06:57:18
【问题描述】:

我必须为 8086 处理器准备将四进制数转换为八进制数的程序。

我的想法:

将每个数字乘以 4 的指数并添加到寄存器。稍后检查不高于第一步总和的 8 的最高指数。 除以 8 的指数,直到余数等于 0。除法的每个结果都是八进制的一位数。 但是对于 16 位数字,4 的最后一个指数是 4^15。我想这不是最优算法。

还有其他方法吗?可能是二进制并按 3 位分组。

【问题讨论】:

  • 我不太清楚这里的意图。您是否打算将四进制输入为字符串并需要将其打印为八进制字符串?我假设这是某种类型的学校项目(为什么还要用 asm 编写它?),所以使用 itoa 之类的函数是不可能的?
  • 是的,用户必须输入四进制字符串,程序将打印八进制字符串。
  • "for 16-digits number" - 16bit 8086 可以在单个寄存器中保存的最大值是(十进制)65535(5 位)。这是(四进制)33333333(8 位)和(八进制)177777(6 位)。如果你想支持 16 位数字,这将是一个有趣的项目。可能有一种简单的方法可以直接从四进制数字计算八进制数字,但我没有看到。每个(3 位)八进制数字将包含所有相应的(2 位)四进制数字,加上它旁边的部分数字。只需将整个四进制数加载到 reg 中并解析就更简单了。
  • @DavidWohlferd 我认为您无法直接获得数字,因为当您解决 4^x = 8 时,x 不是整数。如果你从二进制到十六进制执行相同的操作:2^x = 16 => x = 4 => 4 个二进制数字(即 4 位)将变为 1 个十六进制数字。
  • @maraca - “不能”是一个很大的词。这就是为什么我说没有“简单”的方法。让我们取一个 3 位数的 q:132。从右到左,我们将第一个字符加载到 ax 中并减去 48:2.将下一个 q 加载到 bx 中并减去 48:3.从 bx 和“或”中剥离最右边的位它进入斧头的第三个位置。 ax 现在包含最右边的八进制数字。将 bx 右移 1。将下一个 q (1) 加载到 ax (sub 48) 中。将 ax 向左移动 1 并用 bx 进行“或”。 ax 现在包含第二个八进制数字。这种“借用”和“移位”模式重复,允许转换任意大的 q。但这并不简单。

标签: algorithm x86-16 octal number-systems


【解决方案1】:

事实证明,您确实可以一次处理 3 位数的值。通过这种方式,您可以处理任意长度的字符串,而不受寄存器大小的限制。不知道你为什么需要这样做,除非外星人试图使用长度很大的四位数字 ascii 字符串与我们交流。可能会发生。

可以以任何一种方式进行翻译(从右到左或从左到右)。但是,这两者都存在一些挑战:

如果您正在处理 RtL,则需要在开始之前知道输出字符串的长度(以便在计算数字时知道在哪里写入数字)。这是可行的,但有点棘手。用最简单的术语来说,长度是 ((strlen(Q) + 2) / 3) * 2。几乎得到它。但是,在许多情况下,您可能会在开头出现空格。 “1”和“10”将给出空格。 “20”不会。可以计算出正确的值,但是很烦人。

同样,处理 LtR 也有类似的问题。您没有弄清楚在哪里写数字的问题,但请考虑:如果将字符串转换为“123”,那么转换很简单(八进制33)。但是如果你开始处理,并且完整的字符串是“1231”(155 八进制)怎么办?在这种情况下,您需要像“001231”(01 55)一样处理它。 IOW,数字可以以 3 个为一组进行处理,但是您需要处理最初的情况,即数字的数量不能被 3 整除。

我通常避免发布家庭作业的解决方案。但是,我怀疑您是否会将其作为您的“解决方案”,并且(几乎)谷歌可能会派人来这里需要类似的东西。

需要注意的几点:

  1. 此代码旨在使用 Microsoft 的 fastcall 从 C 调用(它使测试更容易)并使用 masm 编译。
  2. 虽然它是用 32 位(我的环境)编写的,但没有什么特别需要 32 位的。既然你说你的目标是 8086,我就尽量避免任何“高级”指令。转换为 16 位甚至 64 位应该不会带来太大的挑战。
  3. 从左到右处理。
  4. 与任何编写良好的例程一样,它会验证其参数。出错时输出长度为零的字符串,例如输入字符串中的无效数字。
  5. 如果输出缓冲区为 NULL,它将崩溃。我想我可以在错误时返回布尔值(当前返回 void),但是,我没有。
  6. 我确信代码可以更严格(不是总是这样吗?),但对于“家庭作业项目质量”来说,这似乎是合理的。

除此之外,cmets应该解释代码。

.386
.model flat
.code

; Call from C via:
; extern "C" void __fastcall PrintOct(const char *pQuat, char *pOct);

; On Entry:
; ecx: pQuat
; edx: pOct

; On Exit:
; eax, ecx, edx clobbered
; all others preserved
; If pOct is zero bytes long, an error occurred (probably invalid digits)

@PrintOct@8 PROC

; -----------------------
; If pOct is NULL, there's nothing we can do
    test edx, edx
    jz Failed

; -----------------------
; Save the registers we modify (except for
; eax, edx and ecx which we treat as scratch).
    push esi
    push ebx
    push edi

    mov esi, ecx
    mov edi, edx
    xor ebx, ebx

; -----------------------
; esi: pQuat
; edi: pOct
; ebx: zero (because we use lea)
; ecx: temp pointer to pQuat

; Reject NULL pQuat
    test esi, esi
    jz WriteNull

; -----------------------
; Reject 0 length pQuat
    mov bl, BYTE PTR [esi]
    test bl, bl
    jz WriteNull

; -----------------------
; How many chars in pQuat?
    mov dl, bl ; bl is first digit as ascii.  Preserve it.

CountLoop:
    inc ecx ; One more valid char

; While we're counting, check for invalid digits
    cmp dl, '0'
    jl WriteNull
    cmp dl, '3'
    jg WriteNull

    mov dl, BYTE PTR [ecx] ; Read the next char
    test dl, dl ; End of string?
    jnz CountLoop

    sub ecx, esi

; -----------------------
; At this point, there is at least 1 valid digit, and
; ecx contains # digits
; bl still contains first digit as ascii

; Normally we process 3 digits at a time.  But the number of
; digits to process might not be an even multiple of 3.

; This code finds the 'remainder' when dividing ecx by 3.
; It might seem like you could just use 'div' (and you can),
; but 'div' is so insanely expensive, that doing all these
; lines is *still* cheaper than a single div.
    mov eax, ecx
    mov edx, 0AAAAAAABh
    mul edx
    shr edx, 1
    lea edx, [edx+edx*2]
    sub ecx, edx ; This gives us the remainder (0-2).

; If the remainder is zero, use the normal 3 digit load
    jz LoadTriplet

; -----------------------
; Build a triplet from however many leading 'odd' digits
; there are (1 or 2).  Result is in al.

    lea eax, DWORD PTR [ebx-48] ; This get us the first digit

; If there was only 1 digit, don't try to load 2
    cmp cl, 1
    je OneDigit

; Load the other digit

    shl al, 2
    mov bl, BYTE PTR [esi+1]
    sub bl, 48
    or al, bl

OneDigit:

    add esi, ecx ; Update our pQuat pointer
    jmp ProcessDigits

; -----------------------
; Build a triplet from the next 3 digits.
; Result is in al.

; bl contains the first digit as ascii
LoadTriplet:

    lea eax, DWORD PTR [ebx-48]

    shl al, 4 ; Make room for the other 2 digits.

    ; Second digit
    mov cl, BYTE PTR [esi+1]
    sub cl, '0'
    shl cl, 2
    or al, cl

    ; Third digit
    mov bl, BYTE PTR [esi+2]
    sub bl, '0'
    or al, bl

    add esi, 3 ; Update our pQuat pointer

; -----------------------
; At this point
; al: Triplet
; ch: DigitWritten (initially zeroed when computing remainder)
ProcessDigits:

    mov dl, al
    shr al, 3 ; left digit
    and dl, 7 ; right digit

    ; If we haven't written any digits, and we are
    ; about to write a zero, skip it. This deals
    ; with both "000123" and "2" (due to OneDigit,
    ; the 'left digit' might be zero).

    ; If we haven't written any digits yet (ch == 0), and the
    ; value we are are about to write is zero (al == 0), skip
    ; the write.
    or ch, al
    jz Skip1

    add al, '0' ; Convert to ascii
    mov BYTE PTR [edi], al ; Write a digit
    inc edi ; Update pointer to output buffer

    jmp Skip1a ; No need to check again

Skip1:
    or ch, dl ; Both check and update DigitWritten
    jz Skip2

Skip1a:

    add dl, '0' ; Convert to ascii
    mov BYTE PTR [edi], dl ; Write a digit
    inc edi ; Update pointer to output buffer

Skip2:

; Load the next digit.
    mov bl, BYTE PTR [esi]
    test bl, bl
    jnz LoadTriplet

; -----------------------
; All digits processed.  We know there is at least 1 valid digit
; (checked on entry), so if we never wrote anything, the value
; must have been zero.  Since we skipped it to avoid
; unnecessary preceding zeros, deal with it now.

    test ch, ch
    jne WriteNull
    mov BYTE PTR [edi], '0'
    inc edi

; -----------------------
; Write the trailing NULL.  Note that if the returned string is
; 0 bytes long, an error occurred (probably invalid digits).
WriteNull:
    mov BYTE PTR [edi], 0

; -----------------------
; Cleanup
    pop edi
    pop ebx
    pop esi

Failed:
    ret

@PrintOct@8 ENDP

end

我已经通过它运行了一个包含 1,000,000,000 个四位数字的字符串以及 0-4,294,967,295 之间的所有值。似乎有效。

我特别欢迎我们新的 4 位外星霸主。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-08-21
    • 2017-09-27
    • 1970-01-01
    • 1970-01-01
    • 2015-09-15
    • 2021-09-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多