1

I have to prepare program for 8086 processor which converts quaternary to octal number.

My idea:

Multiplying every digit by exponents of 4 and add to register. Later check the highest exponent of 8 not higher than sum from first step. Divide by exponents of 8 until remainder equals 0. Every result of dividing is one digit in octal. But for 16-digits number last exponent of 4 is 4^15. I suppose It isn't optimal algorithm.

Is there any other way? Maybe to binary and group by 3 digits.

Adam A
  • 157
  • 1
  • 18
  • I'm not quite clear on the intent here. Are you planning to input a quaternary as a string and need to print it as octal string? I assume this is a school project of some type (why else write it in asm?), so using functions like itoa are out of the question? – David Wohlferd Dec 14 '16 at 07:28
  • Yes, excactly user must input quaternary string and program will print octal string. – Adam A Dec 14 '16 at 07:48
  • "for 16-digits number" - The largest value the 16bit 8086 can hold in a single register is (decimal) 65535 (5 digits). This is (quaternary) 33333333 (8 digits) and (octal) 177777 (6 digits). If you want to support 16 digit numbers, this is going to be an interesting project. There may be a simple way to compute the octal digit directly from a quaternary digit, but I'm not seeing it. Each (3bit) octal digit will contain all of the corresponding (2bit) quanternary digit, plus part of the digit next to it. Gotta be simpler to just load the whole quanternary number into a reg and parse. – David Wohlferd Dec 14 '16 at 09:39
  • @DavidWohlferd I think you can't get the number directly, because when you solve 4^x = 8, x isn't an integer. If you do the same to go from binary to hex: 2^x = 16 => x = 4 => 4 binary digits (i.e. 4 bits) will become 1 hex digit. – maraca Dec 14 '16 at 10:54
  • @maraca - "Can't" is a pretty big word. That's why I said there's no "simple" way. Let's take a 3 digit q: 132. Working right to left, we load the first char into ax and subtract 48: 2. Load the next q into bx and subtract 48: 3. Peel off the rightmost bit from bx and 'or' it into the 3rd position of ax. ax now contains the rightmost oct digit. Shift bx right by 1. Load the next q (1) into ax (sub 48). Shift ax left by 1 and 'or' it with bx. ax now contains the second oct digit. This pattern of 'borrow' and 'shift' repeats, allowing arbitrarily large q's to be converted. But it ain't simple. – David Wohlferd Dec 14 '16 at 22:26
  • (You write `exponent` where _power_ seems appropriate. You seem to be using `digit` implying various bases without notice.) – greybeard Dec 18 '16 at 12:44

1 Answers1

1

Turns out you can indeed process values 3 digits at a time. Done this way, you can process strings of arbitrary length, without being limited by the size of a register. Not sure why you might need to, unless aliens try to communicate with us using ascii strings of quaternary digits with huge lengths. Might happen.

It's possible to do the translation either way (Right to Left or Left to Right). However, there's a bit of a challenge to either:

If you are processing RtL, you need to know the length of the output string before you start (so that you know where to write the digits as you compute them). This is do-able, but a bit tricky. In simplest terms, the length is ((strlen(Q) + 2) / 3) * 2. That almost gets it. However, you can end up with a blank space at the beginning for a number of cases. "1" as well as "10" will give the blank space. "20" won't. The correct value can be computed, but it's annoying.

Likewise, processing LtR has a similar problem. You don't have the problem of figuring out where to write digits, but consider: If the string to convert it "123", then the conversion is simple (33 octal). But what if you start processing, and the complete string is "1231" (155 octal)? In that case what you need to process it like "001231" (01 55). IOW, digits can be processed in groups of 3, but you need to handle the initial case where the number of digits doesn't evenly divide by 3.

Posting solutions to homework is usually something I avoid. However I doubt you are going to turn this in as your 'solution,' and it's (barely) possible that google might send someone here who needs something similar.

A few things to note:

  1. This code is intended to be called from C using Microsoft's fastcall (it made testing easier) and compiled with masm.
  2. While it is written in 32bit (my environment), there's nothing that particularly requires 32bit in it. Since you said you were targeting 8086, I've tried to avoid any 'advanced' instructions. Converting to 16bit or even 64bit should not present much of a challenge.
  3. It processes from left to right.
  4. As with any well-written routine, it validates its parameters. It outputs a zero length string on error, such as invalid digits in the input string.
  5. It will crash if the output buffer is NULL. I suppose I could return a bool on error (currently returns void), but, well, I didn't.
  6. I'm sure the code could be tighter (couldn't it always?), but for "homework project quality," it seems reasonable.

Other than that, that comments should explain the code.

.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

I've run a string with 1,000,000,000 quaternary digit thru it as well as all the values from 0-4,294,967,295. Seems to work.

I for one welcome our new 4-digited alien overlords.

David Wohlferd
  • 7,110
  • 2
  • 29
  • 56