Page 1 of 1

Print Integer from Hex

Posted: Sun Jul 20, 2025 6:47 am
by vib77
Hello,
I hope the following is not to obvious and has not been answered 10^2 times ;)

I have an apple II, and I want to output integer to screen from hex value in memory,
I have a routine working on my emulator (Virtual II) but not working on my real Apple II

Code: Select all

; Michael Pohoreski
; https://github.com/Michaelangel007/apple2_print_uint16
; Optimized from printm
; Thanks to qkumba for optimizations
; Thanks to Gids for nudging a zero-page version

; F8 ROM Entry Points
PRHEXZ  = $FDE5
SCRN2   = $F879

; Print unsigned 16-bit integer
; A=High byte
; X=Low  byte
; Also see: Applesoft LINPRT @ ED24
; ======================================================================
PrintUint16
        LDY #0          ;                         S=F3
        PHA             ; S+5 = _temp+1 = $105,X  S=F1
        PHX             ; S+4 = _temp+0 = $104,X  S=F2
        PHY             ; S+3 = _bcd[2] = $103,X  S=F0
        PHY             ; S+2 = _bcd[1] = $102,X  S=EF
        PHY             ; S+1 = _bcd[0] = $101,X  S=EE

Dec2BCD
        LDY   #16       ; 16 bits
        SED             ; "Double Dabble"
_Dec2BCD                ; https://en.wikipedia.org/wiki/Double_dabble
        TSX             ;                         X=EE
        STX _BCDbegin+1 ; *** SELF-MODIFYING

;       ASL _temp+0     ;     abcd efgh | ijkl mnop |
;       ROL _temp+1     ; C=a bcde fghi | jklm nop0 |
;                       ; Bit 7654_3210 | 7654_3210 |
        ASL $104,X      ; _temp+0
        ROL $105,X      ; _temp+1

;       LDX #$FD        ; $00-$FD=-3 bcd[0] bcd[1] bcd[2] bcd[3]
_DoubleDabble           ;              Y=FD   Y=FE   Y=FF   Y=00
        JSR Dabble      ;                         X=EF
        JSR Dabble      ;                         X=F0
        JSR Dabble      ;                         X=F1
        DEY
        BNE _Dec2BCD

        CLD             ; Y=0 = output length

BCD2Chars
        LDA $100,X      ; X=F1, 1F1 -> bcd[2]
        JSR HexA        ; print 0, 1, or 2 hex digits
        DEX
_BCDbegin
        CPX #00         ; *** SELF-MODIFIED  X == EE ?
        BNE BCD2Chars

; Safe to restore stack now since we are done with vars
        TXA
        ADC #$04        ; C=1 from CPX #EE
        TAX
        TXS

        TYA             ; Handle special case input = $0000 of no output
        BEQ _HaveLeadingDigit
_PrintDone
        RTS
Dabble
        LDA $101,X      ; bcd,X
        ADC $101,X
        STA $101,X
        INX
        RTS

; Converts A to high ASCII digits, prints as they become available
; @return: A will be bottom nibble in high ASCII
HexA
        PHA
        JSR SCRN2+2     ; LSR x4 == 0>> 4
        JSR _HexNib
        PLA
        AND #$F
_HexNib
        BNE _HaveLeadingDigit   ; If have leading zero and no output yet ...
        DEY                     ; ... then skip storing it

_HaveLeadingDigit
        INY             ; Y = output string length
        BEQ _PrintDone
        JMP PRHEXZ


is there something shorten ?
I only need 1 byte From 00 to FF to be outputted to 0 to 255 on screen

Thanks
Vincent

Re: Print Integer from Hex

Posted: Sun Jul 20, 2025 7:38 am
by BigDumbDinosaur
vib77 wrote:
I have an apple II, and I want to output integer to screen from hex value in memory. I have a routine working on my emulator (Virtual II) but not working on my real Apple II.

By “not working,” what specifically is failing?

It’s likely a “Duh!” question, but I see you have 65C02 instructions in your code (PHX, PHY, etc.).  Since the program runs on your Virtual II simulator (not “emulator”), it must have a virtual 65C02 for an MPU.  Does your real Apple ][ also have a 65C02?

Also, I see a bunch of stackrobatics going on, but nothing to indicate where the stack pointer (SP) is pointing when you enter the routine.  How do you know you aren’t wrapping SP and trashing something else on the stack?

BTW, the Wikipedia double-dabble article refers to the hardware implementation, which is not necessarily the most efficient way to do it in code.  Back when I started programming (late 1960s), the term referred to a software algorithm for generating compressed (aka packed) BCD from binary.  The below code snippet does the double-dabble as I learned it about 50 years ago.  It’s in 65C816 assembly language, and the binary integer being converted is 64 bits in size.  The integer is in FACA in little-endian format.

Code: Select all

ibinbcd  rep #%00100000        ;16-bit accummulator
         sep #%00010000        ;8-bit index
         ldx #4                ;BCD workspace size in words -1 word
;
.init    stz facc,x            ;zero BCD workspace
         dex                   ;decrement twice to...
         dex                   ;accommodate word-sized...
         bpl .init             ;processing
;
         lda !#64              ;set number of bits...
         sta icounter          ;to process
         sed                   ;decimal arithmetic
;
.main    ldx #0                ;rotation index
         ldy #4                ;integer words to process
         clc                   ;no initial carry
;
.main010 rol faca,x            ;pick up bit 63
         inx
         inx
         dey
         bne .main010
;
         tyx                   ;$00 —> .X
         ldy #5                ;words in BCD result
;
.main020 lda facc,x            ;BCD result × 2...
         adc facc,x            ;plus bit 63, if set
         sta facc,x
         inx
         inx
         dey
         bne .main020
;
         dec icounter          ;integer fully processed?
         bne .main             ;no
;
         cld                   ;binary arithmetic
         rts

The !# notation in the LDA !#64 instruction tells the assembler to generate a 16-bit operand.  By changing the loop counts (number of bits etc.), the above code can be adapted to the 65C02.

Re: Print Integer from Hex

Posted: Sun Jul 20, 2025 10:18 am
by BigEd
Welcome, Vincent!

As BDD says, if you want to run on a 6502, you need 6502 code, whereas 65C02 code may well misbehave.

This site (6502.org) has a reference section as well as the forum, and in particular the source code collection is here:
Source Code Repository

In there you'll find 3 links to pages on converting from hex to decimal. Probably the best for you is this one
More Hexadecimal to Decimal Conversion
where the late Andrew Jacobs offers you an 8 bit routine and also a 16 bit routine.

There's also a link to this forum thread
hex to decimal conversion

(Another resource is, or was, Codebase64 - I found this archived page 6502/6510 Maths)

Re: Print Integer from Hex

Posted: Sun Jul 20, 2025 3:29 pm
by Broti

Re: Print Integer from Hex

Posted: Sun Jul 20, 2025 4:50 pm
by BigEd
(Thanks!)

Re: Print Integer from Hex

Posted: Sun Jul 20, 2025 8:47 pm
by BigDumbDinosaur
BigEd wrote:
In there you'll find 3 links to pages on converting from hex to decimal. Probably the best for you is this one
More Hexadecimal to Decimal Conversion
where the late Andrew Jacobs offers you an 8 bit routine and also a 16 bit routine.

Andrew’s routines use the “classic” software double-dabble as well.  His code is better-suited to 8- and 16-bit integers than the routine I earlier posted, as his routines don’t have the inner loops and bit counter (ICOUNTER) I used.  Hence his code will run faster with only a modest increase in code.

Comparison of Andrew’s routine with the one I posted illustrates the classic dichotomy of code size versus speed, but also illustrates where the 65C816 in native mode has a significant advantage over the 65C02 in arithmetic processing performance.  The bit counter and inner shift and addition loops in my routine were used to keep code size from ballooning when I scaled up things to handle 64-bit integers.  With the inner loops, I could not use either index register as the bit counter without constantly pushing and pulling it, at a cost of seven clock cycles per iteration—7 × 64 = 448 cycles total.  As it turned out, the cost to push and pull .X in each iteration was noticeably more than that of decrementing ICOUNTER, five cycles per iteration—320 cycles total.

Offsetting the processing time expended in manipulating .X and .Y, plus the slightly-slower execution time of direct-page indexed addressing (five cycles vs. four), is the significant gain realized in the shift and addition operations, in which each ROL and ADC processes 16 bits.  Even after factoring in the cost of using .X and .Y for looping as well as the extra clock cycle consumed in fetching and storing a 16-bit quantity from/to memory, outer loop processing runs about 70 percent faster than with the 65C02 over the same number of integer bits.

BTW, here is the same routine with the inner loops unrolled.  It will run somewhat faster, but also is bigger. Size vs. speed...

Code: Select all

ibinbcd  rep #%00100000        ;16-bit accummulator
         sep #%00010000        ;8-bit index
         ldx #5-1              ;BCD workspace size in words -1 word
;
.init    stz facc,x            ;zero BCD workspace
         dex                   ;decrement twice to...
         dex                   ;accommodate word-sized...
         bpl .init             ;processing
;
         ldx #64               ;number of bits to process
         sed                   ;decimal arithmetic
;
.main    asl faca              ;pick...
         rol faca+2            ;up...
         rol faca+4            ;bit...
         rol faca+6            ;63
         lda facc              ;BCD result × 2...
         adc facc              ;plus bit 63, if set
         sta facc
         lda facc+2
         adc facc+2
         sta facc+2
         lda facc+4
         adc facc+4
         sta facc+4
         lda facc+6
         adc facc+6
         sta facc+6
         lda facc+8
         adc facc+8
         sta facc+8
         dex                   ;integer fully processed?
         bne .main             ;no
;
         cld                   ;binary arithmetic
         rts

The shift and addition instructions could be “condensed” by using macros instead of writing out the shifting and adding of each word, but the end result would be the same when assembled: 58 bytes of code vs. 50 for the looped version.

Incidentally, my obsession with code size in the above was from the days when my POC units only had 8 KB of ROM—the binary-to-BCD function is part of the M/L monitor.  I was aiming to pack as much functionality into that ROM as possible, so every byte counted.

Re: Print Integer from Hex

Posted: Mon Jul 21, 2025 4:36 am
by barrym95838
Vincent, if you just want to output an unsigned byte as decimal, I think you can use something like this (untested, but hopefully properly down-sized from my own 16-bit routine, which has been tested):

Code: Select all

printdec:           ; 32 bytes
    sta  temp       ; print accumulator as decimal
prdec2:             ;
    lda  #0         ; remainder (current digit)
    clv             ; (sets if quotient > 0)
    ldy  #8         ;
prdec3:             ; 8-bit divide by ten {
    cmp  #5         ;   partial rem >= radix/2?
    bcc  prdec4     ;
    sbc  #133       ;   yes: update rem, set V and
    sec             ;     C for non-zero quotient
prdec4:             ;
    rol  temp       ;   quotient forms in temp
    rol             ;   remainder forms in A
    dey             ;
    bne  prdec3     ; } continue 8-bit divide
    bvc  prdig      ; quotient > 0?
    pha             ;   yes: one or more digits
    jsr  prdec2     ;     must be printed before
    pla             ;     this one, so recurse to
prdig:              ;     handle them first
    eor  #"0"       ; convert remainder to ascii
    jmp  putchar    ; print the digit
If I got it right, it should work on every 65xx ever made, including the old ceramic ones with no ROR. :D
It's not a speed demon, but it's compact, has built-in leading zero suppression, and can be easily modified for bases 8, 6, 4, and 2 by placing base/2 into the CMP# instruction and 128+(base/2) into the SBC# instruction. It also uses a sneaky V-flag trick to save a few bytes, which I credit to John Brooks. It also "conveniently" zeros out Y and the byte at label "temp".

[P.S. I just checked it out on Kowalski, and it appears to be good to go, although Kowalski prefers #'0' over #"0". :) ]

Re: Print Integer from Hex

Posted: Tue Jul 29, 2025 1:47 pm
by jgharston
The most efficient "output hex" code is just straight-forward brute force:
PRHEX:
PHA
LSR A:LSR A:LSR A:LSR A ; get top nybble
JSR PYBYBBLE
PLA
PRNYBBLE:
AND #15
CMP #10:BCC PRDIGIT:ADC #6
PRDIGIT:
ADC "0":JMP PRCHAR

It can be optimised by playing with Decimal mode:

Code: Select all

    .PrHex
    PHA                        :\ Save A
    LSR A:LSR A:LSR A:LSR A    :\ Move top nybble to bottom nybble
    JSR PrNybble
    PLA
    AND #15                    :\ Mask out original bottom nybble
    .PrNybble
    SED                        :\ Switch to decimal arithmetic
    CLC
    ADC #&90                   :\ Produce &90-&99+CC or &00-&05+CS
    ADC #&40                   :\ Produce &30-&39    or &41-&46
    CLD                        :\ Switch back to binary arithmetic
    JMP OSWRCH                 :\ Print it
See link

Re: Print Integer from Hex

Posted: Tue Jul 29, 2025 1:54 pm
by jgharston
Ok, I've re-read the thread and am confused. The OP says printing hex, but loads of posts are printing decimal. For a simple 8-bit decimal printout, where you're not optimising with other code, I've often used this - translated from the PDP11:

Code: Select all

    .PrDec
    LDX #ASC"0"-1:SEC          :\ Prepare for subtraction
    .PrDec100
    INX:SBC #100:BCS PrDec100  :\ Count how many 100s
    JSR PrDecDigit             :\ Print the 100s
    ADC #100                   :\ Balance last SUB 100
    LDX #ASC"0"-1:SEC          :\ Prepare for subtraction
    .PrDec10
    INX:SBC #10:BCS PrDec10    :\ Count how many 10s
    JSR PrDecDigit             :\ Print the 10s
    ADC #ASC"0"+1:TAX          :\ Convert back to units into X
    .PrDecDigit
    PHA:TXA:JSR OSWRCH         :\ Print digit
    PLA:RTS                    :\ Restore A and return
and in many cases I only need 00-99, so I trim it down to:

Code: Select all

    .PrDec
    LDX #ASC"0"-1:SEC          :\ Prepare for subtraction
    .PrDec10
    INX:SBC #10:BCS PrDec10    :\ Count how many 10s
    JSR PrDecDigit             :\ Print the 10s
    ADC #ASC"0"+1:TAX          :\ Convert back to units into X
    .PrDecDigit
    PHA:TXA:JSR OSWRCH         :\ Print digit
    PLA:RTS                    :\ Restore A and return

Re: Print Integer from Hex

Posted: Wed Jul 30, 2025 1:38 am
by barrym95838
The OP's terminology was a bit confusing, but I just went with the last sentence:
Quote:
I only need 1 byte From 00 to FF to be outputted to 0 to 255 on screen
and worked with that. :)
If anyone can beat 32 bytes, I will be thoroughly impressed. 8)

Bruce Clark has his own take on the subject here, and his code is faster but a bit larger than mine.

Re: Print Integer from Hex

Posted: Sat Aug 09, 2025 9:30 am
by BillG
Much of that bit manipulation in double dabble is making do without a decimal adjust instruction. I have posted code for a number of processors for converting binary to decimal. If there is interest, I can try to find it.

The first question is do you want smaller code size or fast execution?

Re: Print Integer from Hex

Posted: Wed Aug 13, 2025 7:09 am
by barrym95838
BillG wrote:
... The first question is do you want smaller code size or fast execution?
Yes please. :mrgreen:

Re: Print Integer from Hex

Posted: Wed Aug 13, 2025 2:47 pm
by BigDumbDinosaur
BillG wrote:
The first question is do you want smaller code size or fast execution?
barrym95838 wrote:
Yes please. :mrgreen:

With the 65C816 in native mode, sometimes you can get faster execution with smaller code.  :D  This is especially the case in loops that are doing arithmetic, as being able to process 16 bits at a time often results in fewer branch instructions being used.

Re: Print Integer from Hex

Posted: Mon Feb 09, 2026 3:44 pm
by barnacle
Having a need for an unsigned 32-bit->decimal output, I amused myself with double-dabble, which I haven't played with previously: (see https://en.wikipedia.org/wiki/Double_dabble for the gory details)

Code: Select all

u32_hextobcd:
	stz tmpq1
	stz tmpq1+1
	stz tmpq1+2
	stz tmpq1+3
	stz tmpq1+4 		; which is tmpq2 low byte
	
	ldy #32				; number of bits in input
u32_htb_01:		
		; check each nibble in the output
		; if it's five or greater, add three
		ldx #0
u32_htb_02:
		lda tmpq1,x
		and #$0f			; low nibble
		cmp #$5
		bcc u32_htb_03		; if it's greater than 5
			; carry is set, so just add two, not three
			lda tmpq1,x
			adc #2
			sta tmpq1,x
u32_htb_03:
		lda tmpq1,x
		and #$f0
		cmp #$50			; is the high nibble > 5?
		bcc u32_htb_04		; nope
			lda tmpq1,x
			adc #$2f		
			sta tmpq1,x			; again, carry is set
u32_htb_04:
		inx
		cpx #5
		bne u32_htb_02		; and repeat another four times
		; do the shifts
		asl lba
		rol lba+1
		rol lba+2
		rol lba+3			; the input shifts
		rol tmpq1
		rol tmpq1+1
		rol tmpq1+2
		rol tmpq1+3
		rol tmpq1+4 		; into the output
		dey					; and then do the rest of the bits!
		bne u32_htb_01
u32_htb_x:
	rts
The input value is in lba (four bytes) and the output appears a BCD in tmpq1 (five bytes; I'm borrowing a byte from another variable but don't tell anyone!). In both cases the result is little-endian:

Code: Select all

$00000064 -> 00 01 00 00 00 
$FFFFFFFF -> 95 72 96 94 42
$499602D2 -> 90 78 56 34 12
Obviously you'll need to sort out conversion to ASCII (just add $30 to each nibble) and leading zero suppression. Enjoy!

Neil

Re: Print Integer from Hex

Posted: Wed Feb 11, 2026 2:53 pm
by barnacle
And just to keep things tidy, here's a print routine that handles the leading zero suppression. It outputs the uint32 value in lba as a decimal value, left aligned. En passant, it kills lba and tmpq[0..4].

Code: Select all

putlbadec:
	jsr u32_iszero		; the easy case!
	bne put_ld_00
		lda #'0'
		jsr putchar
		bra put_ld_ex	; and we're done
put_ld_00:
	jsr u32_hextobcd
	ldx #5				; start with the big numbers!
put_ld_01:
		dex
		lda tmpq1,x
		beq put_ld_01		; skip over all $00 bytes
	; does this byte have a leading zero nibble?
	and #$f0
	bne put_ld_10
		; it's zero, so just the low digit please
		lda tmpq1,x
		jsr hexcharout
		dex
		bmi put_ld_ex		; exit if this was the last digit
put_ld_10:
	; else print the digit pair
	lda tmpq1,x
	jsr hex2out
	dex
	bpl put_ld_10
put_ld_ex:
	rts
Notes:
  1. u32_iszero() returns Z if lba is zero
  2. putchar() prints the ascii character in Acc
  3. hexcharout() prints the hex value in the low nibble of Acc as a hex digit
  4. hex2out() prints Acc as a hex pair, high nibble first
  5. none of the output routines damage X or Y
Neil