New forum member bumping up an old thread.
Powersoft wrote:
Nice discussing. I am playing also with these routines, but I strugle with the in- and output. You wrote a nice routine wozf to show the numbers. Plese can you show the code for this litle program
I put together some 6502 code to output the exact decimal representation of Rankin/Wozniak FP values. It uses only integer arithmetic, and (given a big enough ZP buffer) will output the exact decimal equivalent of the 32-bit FP value to 40+ significant figures. While not necessarily useful for real-world use, this may help with debugging. Code below, including a test harness. Assemble it with the Rankin/Wozniak routines, then call PFLOAT to print the value in X1,M1 via OUCH, a routine to print one ASCII character passed in A (which you or your monitor/firmware should supply). Execute from $0F00 to run the test harness which prints a table of values in hex and decimal.
When I copied across the values in the DCM directives in the exponent code for testing, I discovered that some versions of the original routines have a typo in the DCM for C2. The decimal value in the comments is .03465735903. Some versions give the corresponding hex values as $7B, $46, $FA, $70, whose exact value is 0.03465735912322998046875, matching the DCM value to 8 significant figures. However, some versions give the hex as $7B, $46, $4A, $70, which only agrees to 2 SF. OCR error somewhere along the line?
Comments welcome - especially from anyone who can make the code shorter. 151 bytes is quite embarrassing given the famous terseness of Woz's code!
Code:
; ****************************************************************************************
;
; PFLOAT - Print exact decimal value represented by Wozniak/Rankin FP value in X1, M1.
;
; Requirements: 1. A buffer in Zero Page, base address DIGITS, length BSIZ bytes.
; 2. OUCH, a subroutine that prints the ASCII character in A
; (which may be overwritten), and which preserves contents of
; the X register.
;
; Input: FP value to be printed in X1, M1.
;
; Output: Decimal value printed using OUCH, - OR -
; If the integer part of the decimal representation is longer
; than BSIZ-2 bytes, overflow indicated by printing "E!".
; If the fractional part of the decimal representation is longer
; than BSIZ-1 bytes, it's truncated at BSIZ-1.
; X1,M1 overwritten.
;
; This version assembled using atasm 1.08 (https://atari.miribilist.com/atasm/),
; adapted to write tape images for Tim Baldwin's excellent Compukit UK101
; emulator. Tested on emulator v1.4.0 (https://uk101.sourceforge.net/). I'll
; update to the latest versions of both assembler and emulator one day ...
;
; Andrew Watson
; notinteresting@gmx.com
; 13 November 2023
;
; ****************************************************************************************
* = $0D00
OUCH = $BF2D ; Print one character (UK101 MON01)
BSIZ = 100 ; Number of bytes in the print buffer
DIGITS = $50 ; Somewhere on zero page where we can use 100 consecutive bytes
; Print the float in X1 in non-scientific notation
PFLOAT LDA X1
BNE NOTZRO
LDA #'0
JMP OUCH ; It's zero. Handle as special case.
NOTZRO LDA M1
BPL NOTNEG ; It's positive, so just print it
LDA #'- ; It's negative - print minus sign
JSR OUCH
JSR FCOMPL ; Negate FP1, to make it positive, then fall through to print
; First, print the integer part
NOTNEG LDA #$80 ; Sentinel
STA DIGITS+BSIZ-1 ; Store a sentinel at start of decimal representation
STA DIGITS+BSIZ-3 ; ... and anoter at the other end
ASL ; Make $80 into zero
STA DIGITS+BSIZ-2 ; ... and store that as the initial digit
; Exponent tells us how many times to shift out integer part. $01-$7F = no integer bits,
; $80 = one bit, $81 = two bits, $82 = three bits, etc
LDY X1 ; Get exponent
INY ; Always shift left at least once (MSB into old sign bit)
DIG LDX #BSIZ-2 ; Pointer to first digit
ASL M1+2
ROL M1+1 ; Rotate 3 byte mantissa left by one bit
ROL M1 ; Most Significant Bit goes into C
LDA DIGITS,X ; Load A with least significant decimal digit
AGN ROL ; Double the digit, with carry in (carry out always 0)
CMP #10 ; Did digit overflow?
BCC M ; No
SBC #10 ; Doubling overflowed, so subtract 10 and set carry
M STA DIGITS,X ; Store the doubled digit back
DEX ; Point to next most significant decimal digit
LDA DIGITS,X ; Fetch it
BPL AGN ; If it is a digit, loop back to process it
BCC R ; It's the sentinel, not a digit. Branch away if no carry in.
DEX ; We're going to add a decimal digit, if there's room
BMI ERROR2 ; ... but branch away to error handler if digit buffer now full
STA DIGITS,X ; Store the sentinel ($80) to left of new Most Significant Digit
ROL DIGITS+1,X ; Turn previous sentinel into digit 1 (C is always 1 here)
R DEY ; Keep counting down the exponent
BMI DIG ; ... until it's $7F
; We're done converting the integer part - print it (it could be 0).
JSR PRB
; Now print the fractional part - which is potentially all 24 bits of the FP number
LDA #$82
STA DIGITS ; For fractional part, use $82 as the sentinel
LDY #$E8 ; Bit counter could be -24 (we'll count up)
LDA X1
BMI DIG1 ; If exponent $80 to $FF, we must shift mantissa 24 times
CLC ; If exponent $01 to $7F, we'll calculate shift count
ADC #$69 ; Shift count: $7F -> -24 ($E8), $7E -> -25, $7D -> -26, etc.
TAY ; Put count in Y.
DIG1 LSR M1
ROR M1+1
ROR M1+2 ; Rotate 3 byte mantissa one bit right, LS bit into C
LDX #0 ; Start shifting decimal representation at MS digit
BEQ AGN1 ; Always
LX BCC NOA ; No carry down
ADC #9 ; Add the carry into this digit (C is always set)
NOA LSR ; Divide digit by two
STA DIGITS,X ; Store it back
INX ; Step on to next digit
AGN1 LDA DIGITS,X ; Load that digit (or sentinel)
BPL LX ; Loop, unless it's the sentinel
SENT BCC R1 ; We're at the sentinel. Branch away if no carry into it.
CPX #BSIZ-1 ; Would moving sentinel overflow the buffer?
BCS R1 ; If it would, don't do it
SEC ; Reset carry after CPX
ROL DIGITS,X ; Turn old sentinel into a 5
STA DIGITS+1,X ; Store the sentinel in its new location
R1 INY ; Count number of mantissa shifts
BNE DIG1 ; Loop back if we still have mantissa bits to shift
LDA DIGITS ; Is the sentinel at the start of the buffer?
BMI NOFRAC ; If so, there is no fraction, so don't print anything
LDA #'. ; Ready to print decimal point
LDX #$FF ; Ready to step through digits of the fractional part
NC JSR OUCH ; Print digit (or decimal point first time)
PRB INX ; Step on to first/next digit
LDA DIGITS,X ; Fetch it
ORA #'0 ; If it's a digit, make it ASCII
BPL NC ; But if it was sentinel, fall through instead
NOFRAC RTS ; All done
ERROR2 LDA #'E ; Error handler if the integer part overflows
JSR OUCH ; Print 'E'
LDA #'! ; ... and an exclamation mark
JMP OUCH ; ... and exit
; From here to the end is a test harness that loads floating point numbers into X1 and
; prints them in both hex and floating point.
; Table of floating point test values
TESTS = *
.BYTE $00, $00, $00, $00 ; 0
.BYTE $80, $40, $00, $00 ; 1
.BYTE $81, $60, $00, $00 ; 3
.BYTE $83, $60, $00, $00 ; 12
.BYTE $85, $78, $00, $00 ; 60
.BYTE $85, $88, $00, $00 ; -60
.BYTE $88, $44, $80, $00 ; 274
.BYTE $93, $7A, $12, $00 ; 1,000,000
.BYTE $80, $C0, $00, $00 ; -1
.BYTE $7C, $40, $00, $00 ; 0.0625
.BYTE $96, $7F, $FF, $FF ; 8,388,607
.BYTE $93, $7A, $11, $FF ; 999,999.875
.BYTE $93, $7A, $11, $F8 ; 999,999
.BYTE $A0, $40, $00, $00 ; 4,294,967,296
.BYTE $80, $44, $00, $00 ; 1+2E-4 = 1.0625
.BYTE $80, $40, $00, $01 ; 1+2E-22 = 1.0000002384185791015625
; Values extracted from the Rankin/Wozniak source, with PFLOAT output for comparison
.BYTE $7E, $6F, $2D, $ED ; 0.4342945
; (actually 0.434294521808624267578125, same to 7 SF)
.BYTE $80, $5A, $82, $7A ; 1.4142136 SQRT(2)
; (actually 1.414213657379150390625, same to 8 SF)
.BYTE $7F, $58, $B9, $0C ; 0.69314718 LOG BASE E OF 2
; (actually 0.693147182464599609375, same to 8 SF)
.BYTE $80, $52, $B0, $40 ; 1.2920074
; (actually 1.2920074462890625, same to 8 SF)
.BYTE $81, $AB, $86, $49 ; -2.6398577
; (actually -2.639857769012451171875, same to 8 SF)
.BYTE $80, $6A, $08, $66 ; 1.6567626
; (actually 1.656762599945068359375, same to 7 SF)
.BYTE $7F, $40, $00, $00 ; 0.5
; (actually 0.5, same)
.BYTE $80, $5C, $55, $1E ; 1.4426950409 LOG BASE 2 OF E
; (actually 1.442695140838623046875, same to 7 SF)
.BYTE $86, $57, $6A, $E1, ; 87.417497202
; (actually 87.4174957275390625, same to 7 SF)
.BYTE $89, $4D, $3F, $1D, ; 617.9722695
; (actually 617.9722900390625, same to 7 SF)
.BYTE $7B, $46, $FA, $70, ; 0.03465735903
; (actually 0.03465735912322998046875, same to 8 SF)
.BYTE $83, $4F, $A3, $03, ; 9.9545957821
; (actually 9.9545955657958984375, same to 7 SF)
LASTST = * - TESTS
; Print byte in hex
PBYTE PHA ; Save input byte
LSR
LSR
LSR
LSR ; High nybble shifted down
JSR PNYBBLE ; Print it
PLA ; Get original byte back
PNYBBLE AND #$0F ; Print nybble
ORA #$30 ; 0-9 -> ASCII 0-9
CMP #$3A ; Greater than 9?
BCC NOADJ ; No - it's already a valid digit
ADC #6 ; Need to add 7 so 10 -> A, 11 -> B, etc
NOADJ JMP OUCH ; ... and print it
; Print X1 in hex
PX1 LDX #0
PXX LDA X1,X
JSR PBYTE ; Print a byte
LDA #$20
JSR OUCH ; Print space
INX
CPX #4
BNE PXX ; For all 4 bytes
RET RTS
; Load value pointed to by X in table TESTS into X1
LOADX LDY #0
PT2 LDA TESTS,X
STA X1,Y
INX
INY
CPY #4
BNE PT2 ; Copy next 4 bytes from table
RTS
; Print X1 in hex, then as float
PBOTH LDA #13
JSR OUCH
LDA #10
JSR OUCH ; CRLF
JSR PX1 ; Print X1 in hex
LDA #':
JSR OUCH
LDA #$20
JSR OUCH
JMP PFLOAT ; Print X1 as floating decimal
; The test harness. Located at $0F00 just for ease of running under the UK101 monitor.
* = $0F00
LDX #0
NTST JSR LOADX ; Load value pointed to by X
TXA
PHA ; Save X across PBOTH
JSR PBOTH ; Print it
PLA
TAX ; Get X back
CPX #LASTST ; Was that the last one?
BNE NTST ; If not, do another
.BYTE $02 ; Special halt operation for UK101 Simulator