• shorter code to print a 16bit number in 8086

    From luser.droog@nospicedham.gmail.com@21:1/5 to All on Thu Jul 29 22:11:00 2021
    I wrote this machine code to trace the LIT word in my forth interpreter.
    Is it possible to tighten up this code? It's trying to print a 16bit signed integer (ignoring INT_MIN) left to right with no leading zeros.

    This is assembly code, but I hope you'll forgive my ideosyncratic syntax.
    The operands map left to right to the reg and reg/mem fields into the mod-reg-reg/mem byte and the opcodes target the "to" form (direction
    field = 1), eg. in "MOV(,R,BX,AX)", BX is the dest, AX is the source, the
    R is needed to select register-to-register mode (the mod field). The empty first argument is for tweaks to the opcode which could be F (clear the direction field by subtracting 2) or BYTE (clear the word field by subtracting 1).

    It tests each digit for zero where it jumps over the int 10h call except
    the last digit.

    It occurs to me that the DIV instruction has a mod field. So that means
    the divisor could be register indirect, right? like through SI or BX, maybe? Then it could probably be rolled up in a loop, right? Sorry if I'm jumping
    the gun asking for help before exerting the requisite effort.

    CODE(lit, lit, LODS, PUSH(AX)

    #ifdef TRACE

    , MOV(,R,BX,AX), OR(,R,BX,BX), JGE,7, MOVI(AX,0x0E00+'-'), INT(10), NEG(R,BX),

    MOV(,R,AX,BX), XOR(,R,DX,DX), MOVI(CX,10000), DIV(R,CX),

    OR(,R,AX,AX), JZ,6, MOVBI(AH,0x0E), ADDAL(48), INT(10),

    MOV(,R,AX,DX), XOR(,R,DX,DX), MOVI(CX,1000), DIV(R,CX),

    OR(,R,AX,AX), JZ,6, MOVBI(AH,0x0E), ADDAL(48), INT(10),

    MOV(,R,AX,DX), MOVBI(CL,100), XOR(BYTE,R,CH,CH), BYTE+DIV(R,CL),

    MOV(BYTE,R,DL,AH),

    OR(BYTE,R,AL,AL), JZ,6, MOVBI(AH,0x0E), ADDAL(48), INT(10),

    MOV(BYTE,R,AL,DL), XOR(BYTE,R,AH,AH), MOVBI(CL,10), BYTE+DIV(R,CL),

    MOV(BYTE,R,DL,AH),

    OR(BYTE,R,AL,AL), JZ,6, MOVBI(AH,0x0E), ADDAL(48), INT(10),

    MOVBI(AH,0x0E), MOV(BYTE,R,AL,DL), ADDAL(48), INT(10),

    MOVI(AX,0x0E00+' '), INT(10)

    #endif

    )/**/

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From wolfgang kern@21:1/5 to luser...@nospicedham.gmail.com on Fri Jul 30 14:53:56 2021
    On 30.07.2021 07:11, luser...@nospicedham.gmail.com wrote:
    I wrote this machine code to trace the LIT word in my forth interpreter.
    Is it possible to tighten up this code? It's trying to print a 16bit signed integer (ignoring INT_MIN) left to right with no leading zeros.

    I'm sure that thjis could be made much faster and shorter too :)

    This is assembly code, but I hope you'll forgive my ideosyncratic syntax.
    The operands map left to right to the reg and reg/mem fields into the mod-reg-reg/mem byte and the opcodes target the "to" form (direction
    field = 1), eg. in "MOV(,R,BX,AX)", BX is the dest, AX is the source, the
    R is needed to select register-to-register mode (the mod field). The empty first argument is for tweaks to the opcode which could be F (clear the direction field by subtracting 2) or BYTE (clear the word field by subtracting 1).

    It tests each digit for zero where it jumps over the int 10h call except
    the last digit.

    to speed it up you can use reciprocal MUL instead if DIV
    tried to fall through instead of jump over ?
    better would be a buffer and only a single write to screen.

    It occurs to me that the DIV instruction has a mod field. So that means
    the divisor could be register indirect, right? like through SI or BX, maybe? Then it could probably be rolled up in a loop, right? Sorry if I'm jumping the gun asking for help before exerting the requisite effort.

    don't you use them already? DIV(R,CX)


    CODE(lit, lit, LODS, PUSH(AX)

    #ifdef TRACE

    , MOV(,R,BX,AX), OR(,R,BX,BX), JGE,7, MOVI(AX,0x0E00+'-'), INT(10), NEG(R,BX),

    MOV(,R,AX,BX), XOR(,R,DX,DX), MOVI(CX,10000), DIV(R,CX),

    OR(,R,AX,AX), JZ,6, MOVBI(AH,0x0E), ADDAL(48), INT(10),

    MOV(,R,AX,DX), XOR(,R,DX,DX), MOVI(CX,1000), DIV(R,CX),

    OR(,R,AX,AX), JZ,6, MOVBI(AH,0x0E), ADDAL(48), INT(10),

    MOV(,R,AX,DX), MOVBI(CL,100), XOR(BYTE,R,CH,CH), BYTE+DIV(R,CL),

    MOV(BYTE,R,DL,AH),

    OR(BYTE,R,AL,AL), JZ,6, MOVBI(AH,0x0E), ADDAL(48), INT(10),

    MOV(BYTE,R,AL,DL), XOR(BYTE,R,AH,AH), MOVBI(CL,10), BYTE+DIV(R,CL),

    MOV(BYTE,R,DL,AH),

    OR(BYTE,R,AL,AL), JZ,6, MOVBI(AH,0x0E), ADDAL(48), INT(10),

    MOVBI(AH,0x0E), MOV(BYTE,R,AL,DL), ADDAL(48), INT(10),

    MOVI(AX,0x0E00+' '), INT(10)

    #endif

    I'm not familiar with this syntax, but I see many equal lines before the
    too many INT 10.

    F7 F0 ...F7 F7 and F6 F7..F6 F7 are indeed DIV by reg
    same for the IDIV groups F7 F8...
    __
    wolfgang

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kerr-Mudd, John@21:1/5 to luser...@nospicedham.gmail.com on Fri Jul 30 13:52:44 2021
    On Thu, 29 Jul 2021 22:11:00 -0700 (PDT)
    "luser...@nospicedham.gmail.com" <luser.droog@nospicedham.gmail.com> wrote:

    I wrote this machine code to trace the LIT word in my forth interpreter.
    Is it possible to tighten up this code? It's trying to print a 16bit signed integer (ignoring INT_MIN) left to right with no leading zeros.

    This is assembly code, but I hope you'll forgive my ideosyncratic syntax.
    The operands map left to right to the reg and reg/mem fields into the mod-reg-reg/mem byte and the opcodes target the "to" form (direction
    field = 1), eg. in "MOV(,R,BX,AX)", BX is the dest, AX is the source, the
    R is needed to select register-to-register mode (the mod field). The empty first argument is for tweaks to the opcode which could be F (clear the direction field by subtracting 2) or BYTE (clear the word field by subtracting 1).

    It tests each digit for zero where it jumps over the int 10h call except
    the last digit.

    It occurs to me that the DIV instruction has a mod field. So that means
    the divisor could be register indirect, right? like through SI or BX, maybe? Then it could probably be rolled up in a loop, right? Sorry if I'm jumping the gun asking for help before exerting the requisite effort.

    CODE(lit, lit, LODS, PUSH(AX)

    #ifdef TRACE

    , MOV(,R,BX,AX), OR(,R,BX,BX), JGE,7, MOVI(AX,0x0E00+'-'), INT(10), NEG(R,BX),

    MOV(,R,AX,BX), XOR(,R,DX,DX), MOVI(CX,10000), DIV(R,CX),

    OR(,R,AX,AX), JZ,6, MOVBI(AH,0x0E), ADDAL(48), INT(10),

    MOV(,R,AX,DX), XOR(,R,DX,DX), MOVI(CX,1000), DIV(R,CX),

    OR(,R,AX,AX), JZ,6, MOVBI(AH,0x0E), ADDAL(48), INT(10),

    MOV(,R,AX,DX), MOVBI(CL,100), XOR(BYTE,R,CH,CH), BYTE+DIV(R,CL),

    MOV(BYTE,R,DL,AH),

    OR(BYTE,R,AL,AL), JZ,6, MOVBI(AH,0x0E), ADDAL(48), INT(10),

    MOV(BYTE,R,AL,DL), XOR(BYTE,R,AH,AH), MOVBI(CL,10), BYTE+DIV(R,CL),

    MOV(BYTE,R,DL,AH),

    OR(BYTE,R,AL,AL), JZ,6, MOVBI(AH,0x0E), ADDAL(48), INT(10),

    MOVBI(AH,0x0E), MOV(BYTE,R,AL,DL), ADDAL(48), INT(10),

    MOVI(AX,0x0E00+' '), INT(10)

    #endif

    )/**/

    I think I got the jist of it;

    here's some std asm code from earlier, rejigged for int 0x10 - it requires 5 free stack positions.
    uses ax,bx,cx,dx - 36 bytes

    PrtNum: ; ax to con using int10 fn 0E: no leading spaces or zeros but show '-'
    test ax,ax ; Is it positive?
    jge NotNeg
    push ax
    mov ax,0x0E00+'-'
    int 0x10 ; bios prtc
    pop ax
    neg ax ; show as positive: hibit set only for 0x8000!# NotNeg:
    mov bx,10 ; decimal
    xor cx,cx ; Digit count (5 max)
    NextDigit:
    inc cx ; prt at least 1 digit (i.e. '0')
    xor dx,dx ; clear prev, cant cwd for #8000
    div bx
    push dx ; Remainder is the digit
    test ax,ax ; Is it zero yet?
    jnz NextDigit
    PutDigit:
    pop ax ; get digit (000x)
    add ax,0x0E+'0' ; digit to display value, prtfn in ah
    int 0x10 ; bios prtc
    loop PutDigit







    --
    Bah, and indeed Humbug.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From R.Wieser@21:1/5 to All on Fri Jul 30 17:26:59 2021
    luser,

    It's trying to print a 16bit signed integer (ignoring INT_MIN)
    left to right with no leading zeros.
    ...
    It tests each digit for zero where it jumps over the int 10h call
    except the last digit.

    Those two do not match. And as your code seem to be written according to
    the second ...
    Try printing the value 3002 (or something else with embedded zeroes) to see what I mean.

    Is it possible to tighten up this code?

    I'm not sure what "tighten up" is ment to be read as. Smaller ? Faster ?
    In the first case, how many bytes is it now ?

    My own, erstwhile "trick" was, while doing the same as you (dividing by the largest divisor first), to load a register with the divisor and than call a small routine doing the division and printing.

    But in the light of your "skip leading zeroes" you could do worse than to
    look at what John posted, as that method automatically discards leading
    zeroes - it simply stops as soon as the remainder becomes zero. I'm not so sure about if that "loop" at the end will work though, as the INT 0x10 could easily trash cx..

    It occurs to me that the DIV instruction has a mod field. So that
    means the divisor could be register indirect, right? like through SI
    or BX, maybe?

    Yes, would work. And that same list can than also be used to skip leading zeroes.

    Sorry if I'm jumping the gun asking for help before exerting the
    requisite effort.

    I can imagine that you ask, but I do not quite understand why you did not
    test your own code first ...

    Regards,
    Rudy Wieser

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kerr-Mudd, John@21:1/5 to John" on Fri Jul 30 16:45:29 2021
    On Fri, 30 Jul 2021 13:52:44 +0100
    "Kerr-Mudd, John" <admin@nospicedham.127.0.0.1> wrote:

    On Thu, 29 Jul 2021 22:11:00 -0700 (PDT)
    "luser...@nospicedham.gmail.com" <luser.droog@nospicedham.gmail.com> wrote:

    I wrote this machine code to trace the LIT word in my forth interpreter.
    Is it possible to tighten up this code? It's trying to print a 16bit signed integer (ignoring INT_MIN) left to right with no leading zeros.


    []

    I think I got the jist of it;

    here's some std asm code from earlier, rejigged for int 0x10 - it requires 5 free stack positions.
    uses ax,bx,cx,dx - 36 bytes

    ; corrected version - 37


    PrtNum: ; ax to con using int10 fn 0E: no leading spaces or zeros but show '-'
    test ax,ax ; Is it positive?
    jge NotNeg
    push ax
    mov ax,0x0E00+'-'
    int 0x10 ; bios prtc
    pop ax
    neg ax ; show as positive: hibit set only for 0x8000!# NotNeg:
    mov bx,10 ; decimal
    xor cx,cx ; Digit count (5 max)
    NextDigit:
    inc cx ; prt at least 1 digit (i.e. '0')
    xor dx,dx ; clear prev, cant cwd for #8000
    div bx
    push dx ; Remainder is the digit
    test ax,ax ; Is it zero yet?
    jnz NextDigit
    PutDigit:
    pop ax ; get digit (000x)

    add ax,0x0E+'0' ; digit to display value, prtfn in ah
    I didn't test! Make that line:
    add ax,0x0E00+'0' ; digit to display value, prtfn in ah

    int 0x10 ; bios prtc
    loop PutDigit







    --
    Bah, and indeed Humbug.



    --
    Bah, and indeed Humbug.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kerr-Mudd, John@21:1/5 to R.Wieser on Fri Jul 30 19:55:09 2021
    On Fri, 30 Jul 2021 17:26:59 +0200
    "R.Wieser" <address@nospicedham.not.available> wrote:

    luser,

    It's trying to print a 16bit signed integer (ignoring INT_MIN)
    left to right with no leading zeros.
    ...
    It tests each digit for zero where it jumps over the int 10h call
    except the last digit.

    Those two do not match. And as your code seem to be written according to the second ...
    Try printing the value 3002 (or something else with embedded zeroes) to see what I mean.

    Is it possible to tighten up this code?

    I'm not sure what "tighten up" is ment to be read as. Smaller ? Faster ? In the first case, how many bytes is it now ?

    My own, erstwhile "trick" was, while doing the same as you (dividing by the largest divisor first), to load a register with the divisor and than call a small routine doing the division and printing.

    But in the light of your "skip leading zeroes" you could do worse than to look at what John posted, as that method automatically discards leading zeroes - it simply stops as soon as the remainder becomes zero. I'm not so sure about if that "loop" at the end will work though, as the INT 0x10 could easily trash cx..

    Seems to be OK on this "DOS" VDM under XP - but yes, the int 10 was put in for the OP - originally the code put to a buffer at di, with a single print (ah=9, Int 21) at the end.

    It occurs to me that the DIV instruction has a mod field. So that
    means the divisor could be register indirect, right? like through SI
    or BX, maybe?

    Yes, would work. And that same list can than also be used to skip leading zeroes.

    Sorry if I'm jumping the gun asking for help before exerting the
    requisite effort.

    I can imagine that you ask, but I do not quite understand why you did not test your own code first ...

    Regards,
    Rudy Wieser





    --
    Bah, and indeed Humbug.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From R.Wieser@21:1/5 to All on Fri Jul 30 21:23:19 2021
    John,

    sure about if that "loop" at the end will work though, as the INT 0x10
    could easily trash cx..

    Seems to be OK on this "DOS" VDM under XP - but yes, the int 10 was
    put in for the OP - originally the code put to a buffer at di, with a
    single
    print (ah=9, Int 21) at the end.

    Yes, it also works here (XP Pro sp3). But its not something I will bet my
    life on - because the specs say that a(ny) function call (including INTs)
    may trash AX thru DX at will.

    I know its hard to take, but the extra two bytes for a push/pop cx around
    that last INT 0x10 are really needed. :-)

    Regards,
    Rudy Wieser

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Frank Kotler@21:1/5 to Rod Pemberton on Fri Jul 30 18:39:39 2021
    On 07/30/2021 06:01 AM, Rod Pemberton wrote:
    On Thu, 29 Jul 2021 22:11:00 -0700 (PDT)
    "luser...@nospicedham.gmail.com" <luser.droog@nospicedham.gmail.com>
    wrote:


    Sorry Frank,
    Sorry Rod for the delay in posting = you keep changing your address so
    you're not in the "whitelist" (name changed soon to "karenlist" so it's
    not racist)
    Try to stay on topic everyone... but it's bot as if we're swamped with traffic...

    Best,
    Frank
    [moderator]

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rod Pemberton@21:1/5 to All on Fri Jul 30 05:01:13 2021
    On Thu, 29 Jul 2021 22:11:00 -0700 (PDT)
    "luser...@nospicedham.gmail.com" <luser.droog@nospicedham.gmail.com>
    wrote:


    Sorry Frank, the first part of this may be a off-topic,
    but I haven't been reading or posting to comp.lang.forth
    in a while. So, I can understand if the OP posts here
    with some Forth-ish assembly ...

    I wrote this machine code to trace the LIT word in
    my forth interpreter.

    LIT is usually very simple and just stores an integer from
    the interpreter's input stream into the current Forth word.


    In C, my LIT primitive - the low-level function which goes
    with the LIT dictionary entry - is:

    void lit__(void)
    {
    push((cell_t)*ip);
    ip++;
    }

    This dereferences/fetches what's at the Forth interpreter's
    instruction pointer IP, stores that value on the parameter/data
    stack, and then increments the IP.


    I have LIT in the initial dictionary in C code (which I
    use instead of assembly), because LIT is needed prior to
    loading a high-level Forth dictionary. LIT is not a
    standardized Forth word (subroutine).


    You should be able to construct a LIT from your COMPILE
    definition. E.g., just drop the , (comma) or equivalent
    Forth word like COMPILE, from the end of the definition:

    : COMPILE R> DUP CELL+ >R @ , ;
    : LIT R> DUP CELL+ >R @ ;

    For my personal Forth interpreter, the LIT is high-level
    version of LIT equivalent to the low-level C code above.

    I.e., the R> (r-from) captures the interpreter's saved IP,
    DUPlicates the IP, increments one copy of the IP to skip
    over the value, places the adjusted IP onto the return stack
    via >R (to-r), fetches the value from the original IP onto
    the parameter/data stack. That is the LIT value.

    Is it possible to tighten up this code? It's trying to
    print a 16bit signed integer (ignoring INT_MIN) left to
    right with no leading zeros.

    Well, I really didn't look at the Forth-ish assembly,
    but my suggestion to make your life easier would be
    to print the decimal(?) "16bit signed integer" instead
    in hexadecimal as a "16bit UN-signed integer".


    --
    ...

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luser.droog@nospicedham.gmail.com@21:1/5 to luser...@nospicedham.gmail.com on Fri Aug 6 22:04:30 2021
    On Friday, July 30, 2021 at 12:13:00 AM UTC-5, luser...@nospicedham.gmail.com wrote:
    I wrote this machine code to trace the LIT word in my forth interpreter.
    Is it possible to tighten up this code? It's trying to print a 16bit signed integer (ignoring INT_MIN) left to right with no leading zeros.

    This is assembly code, but I hope you'll forgive my ideosyncratic syntax.
    The operands map left to right to the reg and reg/mem fields into the mod-reg-reg/mem byte and the opcodes target the "to" form (direction
    field = 1), eg. in "MOV(,R,BX,AX)", BX is the dest, AX is the source, the
    R is needed to select register-to-register mode (the mod field). The empty first argument is for tweaks to the opcode which could be F (clear the direction field by subtracting 2) or BYTE (clear the word field by subtracting 1).

    It tests each digit for zero where it jumps over the int 10h call except
    the last digit.

    It occurs to me that the DIV instruction has a mod field. So that means
    the divisor could be register indirect, right? like through SI or BX, maybe? Then it could probably be rolled up in a loop, right? Sorry if I'm jumping the gun asking for help before exerting the requisite effort.


    Thanks for the help, everyone! Thanks to Rudy for pointing out that my algorithm was malformed. I had tested it (sort of, I ran it and saw that
    there was output), but I didn't check it against known values and so
    didn't see the problem of suppressing inner zeros. (the numbers it
    printed did appear weird, but I didn't properly consider and valuate
    that clue).

    John's code is so sweet and simple, I am envious of the ability to write
    in such a way. I translated it to my syntax and then tried a few of the variations alluded to earlier, like using BX to point to a table of divisors. But I haven't actually implemented the LOOP instruction in the emulator
    and the syntax I have for using labels in machine code hasn't been
    incorporated into the emulator at all yet. So I haven't tested any of the following code except to run it through CPP to check that macro expansion works.

    First I just translated John's code to my syntax (now with labels) and
    lo and behold, it's super short and sweet. Exactly what I asked for.

    CODE(lit, (LIT, LODS, PUSH(AX))
    #ifdef TRACE
    , (PrtNum, TEST(R,AX,AX), JGE, NotNeg-_, \
    PUSH(AX), MOVAX(0xE00+'-'), INT(10), POP(AX), NEG(R,AX)),
    (NotNeg, MOVBX(10), XOR(,R,CX,CX)),
    (NextDigit, INC(CX), XOR(DX,DX), DIV(BX), PUSH(DX), TEST(AX,AX), JNZ, NextDigit-_),
    (PutDigit, POP(AX), ADDAX(0xE00+'0'), INT(10), LOOP, PutDigit-_) #endif
    )

    First I tried doing it with a table of divisors and unrolled code to divide by each
    word in the table.

    CODE(lit, (LIT, LODS, PUSH(AX))
    #ifdef TRACE
    , (stub, sJMP, CODE-_),
    (divisors, DW(10000), DW(1000), DW(100), DW(10)),
    (CODE, MOVBX(divisors), XOR(,R,DX,DX),
    DIV(Z,BX_), ADDAX(0xE00+'0'), INT(10)),
    (L1, MOV(,R,AX,DX), DIV(B,BX_),2, ADDAX(0xE000+'0'), INT(10),
    MOV(,R,AX,DX), DIV(B,BX_),4, ADDAX(0xE000+'0'), INT(10)),
    (L2, MOV(,R,AX,DX), DIV(B,BX_),6, ADDAX(0xE000+'0'), INT(10),
    MOD(,R,AX,DX), ADDAX(0xE00+'0'), INT(10))
    #endif
    )

    Which is longer, uglier, replete with repetition.

    Then I tried doing it with a table of divisors and a loop to run through left-to-right.

    CODE(lit, (LIT, LODS, PUSH(AX))
    #ifdef TRACE
    , (tracelit, MOVBX(divisors), XOR(,R,DX,DX), MOVCX(5)),
    (digit, DIV(Z,BX_), ADDAX(0xE00+'0'), INT(10),
    MOV(,R,AX,DX), ADDI(BX,2), LOOP, digit-_,
    NEXT)
    (divisors, DW(10000), DW(1000), DW(100), DW(10), DW(1)),
    #endif
    )

    where NEXT is a macro for jumping to the next Forth word, pointed to by SI. That's pretty short, but it treats all numbers as unsigned and makes
    no attempt to suppress leading zeros.

    Then I tried without using a table and calling a procedure for each digit.

    CODE(lit, (LIT, LODS, PUSH(AX))
    #ifdef TRACE
    , (tracelit, XOR(,R,DX,DX), MOVCX(5),
    TEST(R,AX,AX), JGE,notneg,
    MOVI(AX,0xE00+'-'), INT(10), NEG(R,AX), JNO, notneg,
    INC(R,DX)),
    (notneg, MOVI(BX,10000), DIV(R,BX), CALL, print,
    MOVI(BX,1000), MOV(,R,AX,DX), DIV(R,BX), CALL, print,
    MOVI(BX,100), MOV(,R,AX,DX), DIV(R,BX), CALL, print,
    MOVI(BX,10), MOV(,R,AX,DX), DIV(R,BX), CALL, print,
    MOV(,R,AX,DX), CALL, print,
    NEXT),
    (print, ADDAX(0xE00'0'), INT(10), RET)
    #endif
    )

    It's pretty cool that adding labels also lets me have local procedures,
    but I feel like the call instruction vs, the ADD and INT instructions
    doesn't really save much and is slower and longer on the screen.
    But maybe all that just means that I should be using a DOS string
    printing function instead of a character printing function.

    And also, Rod was right and this whole effort was a wholy
    unnecessary X/Y problem. And if I just add a high-level LIT word,
    then I can call U. to print the number be done with this snipe hunt.

    On a side note, I stumbled across the source on github for DOS 1.25
    and 2.0 and I'm browsing ASM.ASM from 1.25 to get ideas about how to
    make an assembler with a more common syntax.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)