6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Thu May 16, 2024 4:28 am

All times are UTC




Post new topic Reply to topic  [ 14 posts ] 
Author Message
PostPosted: Tue Jan 03, 2023 12:10 am 
Offline

Joined: Mon Nov 27, 2017 7:11 pm
Posts: 13
So I'm trying to figure out what's going on here - I'm having a go at writing a high-level compiler for the 6502, and it'd be nice to have a 'float' datatype, but I'm not managing to understand '1-2' at this point :)

I wrote a little routine to convert a float to 'woz' format, and it gives me:

Code:
@elysium xtal % wozf 1
 f: 80:400000
@elysium xtal % wozf 2
 f: 81:400000

... which makes sense to me given the description of the floating point format here (PDF). For '1', the exponent is 0 and the units bit is 1, sign=0. For '2', the mantissa is the same, the exponent is one higher.. So far so good.

I set up space in ZP for three 4-byte FP "registers" at $80 (flt_{x,m}2), $84 (flt_{x,m}1) and $88 (flt_e) - where each one is laid out as {1-byte exponent (x), 3 byte mantissa (m) inc sign bit}. After loading in the representation for '1' into flt2 and '2' into flt1, I see:
Code:
0080: 80 40 00 00 81 40 00 00  ;·@···@··
0088: 00 00 00 00 00 00 00 00  ;········

Next we call fsub (result in flt1 = flt2 - flt1), which is implemented as:
Code:
jsr fcompl                 ; create the -ve of flt1
jsr alignsw                ; align flt1 and swap flt1,flt2 if needed
 ... (fall through into fadd)


For clarity, the overall call hierarchy looks like:
Code:
┌─────────────┐                                                 
│    f_sub    │                                                 
└─────────────┘                                                 
       │                                                         
       │  ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
       ├─▶│   fcompl    │────▶│   addend    │────▶│    norm     │
       │  └─────────────┘     └─────────────┘     └─────────────┘
       │                                                         
       │                                                         
       │  ┌─────────────┐     ┌─────────────┐                   
       ├─▶│   alignsw   │────▶│    swap     │                   
       │  └─────────────┘     └─────────────┘                   
       │                                                         
       │                                                         
       │  ┌─────────────┐                                       
       └─▶│    fadd     │                                       
          └─────────────┘                                       


Tracking through that, we start off in fcompl, which is:
Code:
   sec                     ; set the carry
   ldx #$03                ; set up for 3 byte subtraction
compl1:
   lda #0                  ; clear A
   sbc flt_x1,x            ; subtract byte of exp1
   sta flt_x1,x            ; restore it
   dex                     ; next byte
   bne compl1              ; loop until done
compdone:
   beq addend              ; normalise (or shift right on overflow)

... which (at the point of 'compdone' has the expected effect of turning the $40 in flt1 to $c0. No change to the exponent because it's just operating on the mantissa, and the other two bytes of this mantissa are 0..
Code:
0080: 80 40 00 00 81 C0 00 00  ;·@······
0088: 00 00 00 00 00 00 00 00  ;········

I should probably point out that these are actually the values I want to see...

80:400000 is "1", and
81:C00000 is "-2"

However, the next call is to addend, which is implemented as either a branch to 'norm' (normalize the mantissa) or 'rtlog' (shift the 6 bytes of flt1/e mantissas one bit to the right):
Code:
addend:
   bvc norm                ; no overflow, normalise results
   bvs rtlog               ; ov: shift mant1 right, note carry is correct sign

In this case, V is clear, so we jump to 'norm'... which is implemented as
Code:
norm1:
   lda flt_m1              ; high order mantissa-1 byte
   cmp #$c0                ; upper 2 bits unequal ?
   bmi rts1                ; y: return
   
   dec flt_x1              ; decrement exponent-1
   
   asl flt_m1+2            ; 3-byte shift of mantissa-1 left
   rol flt_m1+1
   rol flt_m1
   
norm:
   lda flt_x1              ; Mantissa-1 high-byte
   bne norm1               ; no, continue normalising
rts1:
   rts                     ; all done

Tracing this, We
  • load A with contents of $84 (set to $81) as above,
  • it's not 0, so we run through the norm1 code.
  • On the first pass, flt_m1 (the first byte of flt1's mantissa) is $C0 (sign bit, $80, and the top mantissa bit both set), so we fail the BMI test
  • then we decrement the exponent (old value of $81 -> $80)
  • then do the asl/rol sequence,
  • loop around again because $80 isn't 0,
  • this time we pass the CMP test and return.

At this point, the data in page zero looks like:
Code:
0080: 80 40 00 00 80 80 00 00  ;·@······
0088: 00 00 00 00 00 00 00 00  ;········

So now we're back in fp_sub, at the 'jsr algnsw' stage. The code for algnsw is:
Code:
algnsw:
   bcc swap               ; swap if carry clear, else shift right
   ...

In this case the carry is clear, so we jump to swap, which just swaps the bytes in the two floating point registers flt1 and flt2 (also storing a copy of the flt2 mantissa in flt_e)
Code:
swap:
   ldx #$04               ; set up for 4-byte swap
swap1:
   sty flt_e-1,x          ; copy to E
   lda flt_x1-1,x         ; swap a byte of exp/mant1 with..
   ldy flt_x2-1,x         ; exp/mant2 and leave a copy of..
   sty flt_x1-1,x         ; mant1 in E (3 bytes). E+3 used
   sta flt_x2-1,x
   dex                    ; next byte
   bne swap1              ; loop until done
   rts

And once that's called, we look (predictably) like:
Code:
0080: 80 80 00 00 80 40 00 00  ;·····@··
0088: 40 00 00 00 00 00 00 00  ;@·······


At this point, we're about to drop into the floating-point add routine, and add the negative-of-what-we-passed-into-flt1 to what-we-passed-into-flt2. The problem is that the two numbers in the registers are now

flt2 (@$80) = 80:800000 or "-0" [this started off as 2]
flt1 (@$84) = 80:400000 or "1" [this is unchanged, just swapped from the other register]

unsurprisingly the add routine returns the wrong results... I have checked and re-checked that this code accurately represents the code in the afore-linked PDF (and I used the second (later) version, assuming the one burnt into the Apple ][ ROM would be "good"), so I'm a bit confused over what I'm doing wrong here. I'm assuming [grin] that there isn't a mistake in code that's almost 50 years old and has had a lot of eyes on it - so perhaps there's some setup I'm not aware of, or something similar ? I did try to keep the register-layout order the same in case there was rol/asl from one register into another...

The test with $C0 looks "iffy" to me, because (ignoring the no-errors-in-code-this-used credo for a moment), the test is supposed to be (as I understand it) that "the top two bits of the mantissa ought not be the same", but the test against $C0 is testing the top-bit of the mantissa with the sign bit of the mantissa, and I didn't *think* the sign bit counted. If that is a bug, then an ASL A inserted before the CMP $C0 would fix it, I think.
Yeah, scratch that. Inserting an ASL A in there causes the addition of 1 to 2 to break, whereas it previously worked.

Any advice gratefully received :)


Top
 Profile  
Reply with quote  
PostPosted: Tue Jan 03, 2023 9:20 pm 
Offline

Joined: Thu Apr 23, 2020 5:04 pm
Posts: 45
I have not looked deeper into the woz code for some times, but I compiled a small test program with vbcc using the woz library and checked a few of your steps.
SpacedCowboy wrote:
So I'm trying to figure out what's going on here - I'm having a go at writing a high-level compiler for the 6502, and it'd be nice to have a 'float' datatype, but I'm not managing to understand '1-2' at this point :)

I wrote a little routine to convert a float to 'woz' format, and it gives me:

Code:
@elysium xtal % wozf 1
 f: 80:400000
@elysium xtal % wozf 2
 f: 81:400000


Those values look ok to me.
...
Quote:
At this point, the data in page zero looks like:
Code:
0080: 80 40 00 00 80 80 00 00  ;·@······
0088: 00 00 00 00 00 00 00 00  ;········

Same here.
...
Quote:
At this point, we're about to drop into the floating-point add routine, and add the negative-of-what-we-passed-into-flt1 to what-we-passed-into-flt2. The problem is that the two numbers in the registers are now

flt2 (@$80) = 80:800000 or "-0" [this started off as 2]
flt1 (@$84) = 80:400000 or "1" [this is unchanged, just swapped from the other register]

I also get those values, but when calling my printf with 80800000, it prints -2 rather than -0, so probably those values are ok?

Quote:
unsurprisingly the add routine returns the wrong results...

What value do you get and what value do you expect? I get 7f800000 which is printed as -1.

Quote:
I have checked and re-checked that this code accurately represents the code in the afore-linked PDF (and I used the second (later) version, assuming the one burnt into the Apple ][ ROM would be "good"), so I'm a bit confused over what I'm doing wrong here. I'm assuming [grin] that there isn't a mistake in code that's almost 50 years old and has had a lot of eyes on it - so perhaps there's some setup I'm not aware of, or something similar ? I did try to keep the register-layout order the same in case there was rol/asl from one register into another...

I think some fixes were made to the original code at some time, but I would also assume that 1-2 has always been correctly calculated.

Quote:
The test with $C0 looks "iffy" to me, because (ignoring the no-errors-in-code-this-used credo for a moment), the test is supposed to be (as I understand it) that "the top two bits of the mantissa ought not be the same", but the test against $C0 is testing the top-bit of the mantissa with the sign bit of the mantissa, and I didn't *think* the sign bit counted. If that is a bug, then an ASL A inserted before the CMP $C0 would fix it, I think.
Yeah, scratch that. Inserting an ASL A in there causes the addition of 1 to 2 to break, whereas it previously worked.

Any advice gratefully received :)

Hopefully that helps a bit?


Top
 Profile  
Reply with quote  
PostPosted: Tue Jan 03, 2023 10:54 pm 
Offline

Joined: Mon Nov 27, 2017 7:11 pm
Posts: 13
vbc wrote:
I have not looked deeper into the woz code for some times, but I compiled a small test program with vbcc using the woz library and checked a few of your steps.
Hopefully that helps a bit?

Nope, it doesn't. It helps a lot :D

I think what is apparent is that I need to understand the FP format better, if 80800000 is printing as -2 for you. I went back and looked at the PDF again, and in the FP examples he gives, there's no representation of -2, but there is a -4 (which is 81800000) - and that's consistent with -2 being 80800000. I just don't understand how :)

Elsewhere in the docs, it lays out the mantissa as:
Code:
HIGH-ORDER MANTISSA BYTE
     01.XXXXXX  Positive mantissa.
     10.XXXXXX  Negative mantissa.
     00.XXXXXX  Unnormalized mantissa.
     11.XXXXXX  Exponent = -128.

... and specifically says an exponent of $80 is 0. I can't easily reconcile that with 80800000 being -2. I would expect it to be:
Code:
@elysium ~ % wozf -2
 f: 81:c00000

I have checked other values from the source code against my 'wozf' command line app - and they seem to match up, these are in the code as constants:
Code:
1DD5  7F 58    LE2    DCM 0.69314718  LOG BASE E OF 2
      B9 0C
1DE1  80 6A    C      DCM 1.6567626
      08 66
1DE5  7F 40    MHLF.  DCM 0.5
      00 00

And I get the same bit layout:
Code:
@elysium ~ % wozf 0.69314718 1.6567626 0.5
 f: 7f:58b90c
 f: 80:6a0866
 f: 7f:400000


So maybe there's some sort of implied '1' somewhere if the mantissa is 0 ? I was working on it just being a simple two's-complement number but there appear to be wrinkles :)

Anyway, thank you very much for the reply, and for the data you're seeing. Being able to compare is half the battle :)


Top
 Profile  
Reply with quote  
PostPosted: Wed Jan 04, 2023 12:58 am 
Offline

Joined: Thu Apr 23, 2020 5:04 pm
Posts: 45
SpacedCowboy wrote:
I think what is apparent is that I need to understand the FP format better, if 80800000 is printing as -2 for you. I went back and looked at the PDF again, and in the FP examples he gives, there's no representation of -2, but there is a -4 (which is 81800000) - and that's consistent with -2 being 80800000. I just don't understand how :)

Elsewhere in the docs, it lays out the mantissa as:
Code:
HIGH-ORDER MANTISSA BYTE
     01.XXXXXX  Positive mantissa.
     10.XXXXXX  Negative mantissa.
     00.XXXXXX  Unnormalized mantissa.
     11.XXXXXX  Exponent = -128.

... and specifically says an exponent of $80 is 0. I can't easily reconcile that with 80800000 being -2. I would expect it to be:
Code:
@elysium ~ % wozf -2
 f: 81:c00000


vbcc also creates 81C00000 for -2. However, according to that description that would imply an exponent of -128? It does seem to work though, and both values do compare equal:
Code:
volker@legion5:/tmp$ cat f.c
#include <stdio.h>
long x=0x00008080,y=0x0000c081;
int main()
{
 printf("%g %g %d\n",*(double*)&x,*(double*)&y,(*(double*)&x)==(*(double*)&y));
}
volker@legion5:/tmp$ vc +s6502 f.c -lm
volker@legion5:/tmp$ emulator
-2 -2 1


Quote:
So maybe there's some sort of implied '1' somewhere if the mantissa is 0 ? I was working on it just being a simple two's-complement number but there appear to be wrinkles :)

I presume for normal numbers the mantissa should be between [-2..-1[ or [1..2[, with:
01 0...0 => +1.0 to 01 1...1 => +2.0 (almost), and
10 0...0 => -2.0 to 10 1...1 => -1.0 (almost)

So 0x80800000 would be -2.0*2^0. Not sure if 0x81C00000=-1.0*2^1 is an actually intended representation.


Top
 Profile  
Reply with quote  
PostPosted: Wed Jan 04, 2023 2:01 am 
Offline

Joined: Mon Nov 27, 2017 7:11 pm
Posts: 13
To answer your earlier question, I too get 7f:800000 as my answer for 1-2, which means it was actually working, I just didn't understand how that representation was supposed to be interpreted.

If 80:800000 is -2, and following your logic, I could see that being reasonable, then it follows that 7f:800000 is -1 (as in: -2 >> 1)

I really don't think the documentation is very clear on this though :)


Top
 Profile  
Reply with quote  
PostPosted: Wed Jan 04, 2023 2:06 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8436
Location: Southern California
SpacedCowboy wrote:
I too get 7f:800000 as my answer for 1-2

Let me encourage use of capital A-F in hex numbers.  The '7f' above sure looks like '71' on my monitor.  I only knew from the context that it was an F.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Wed Jan 04, 2023 8:01 am 
Offline
User avatar

Joined: Sun Nov 01, 2020 10:36 am
Posts: 35
Location: Tatooine
0x81C00000 has "C0" as upper byte, which has the two upper bits set (11xxxxxx), so it is not normalized per the woz format


Top
 Profile  
Reply with quote  
PostPosted: Tue Jun 27, 2023 6:22 am 
Offline

Joined: Thu May 13, 2021 8:56 am
Posts: 25
Location: Hellevoetsluis-NL
SpacedCowboy wrote:
vbc wrote:
I have not looked deeper into the woz code for some times, but I compiled a small test program with vbcc using the woz library and checked a few of your steps.
Hopefully that helps a bit?

Nope, it doesn't. It helps a lot :D

I think what is apparent is that I need to understand the FP format better, if 80800000 is printing as -2 for you. I went back and looked at the PDF again, and in the FP examples he gives, there's no representation of -2, but there is a -4 (which is 81800000) - and that's consistent with -2 being 80800000. I just don't understand how :)

Elsewhere in the docs, it lays out the mantissa as:
Code:
HIGH-ORDER MANTISSA BYTE
     01.XXXXXX  Positive mantissa.
     10.XXXXXX  Negative mantissa.
     00.XXXXXX  Unnormalized mantissa.
     11.XXXXXX  Exponent = -128.

... and specifically says an exponent of $80 is 0. I can't easily reconcile that with 80800000 being -2. I would expect it to be:
Code:
@elysium ~ % wozf -2
 f: 81:c00000

I have checked other values from the source code against my 'wozf' command line app - and they seem to match up, these are in the code as constants:
Code:
1DD5  7F 58    LE2    DCM 0.69314718  LOG BASE E OF 2
      B9 0C
1DE1  80 6A    C      DCM 1.6567626
      08 66
1DE5  7F 40    MHLF.  DCM 0.5
      00 00

And I get the same bit layout:
Code:
@elysium ~ % wozf 0.69314718 1.6567626 0.5
 f: 7f:58b90c
 f: 80:6a0866
 f: 7f:400000


So maybe there's some sort of implied '1' somewhere if the mantissa is 0 ? I was working on it just being a simple two's-complement number but there appear to be wrinkles :)

Anyway, thank you very much for the reply, and for the data you're seeing. Being able to compare is half the battle :)

Quote:
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


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 13, 2023 11:11 pm 
Offline

Joined: Mon Nov 13, 2023 7:33 pm
Posts: 3
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


Last edited by AndrewW on Thu Nov 16, 2023 7:29 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Tue Nov 14, 2023 9:56 am 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1929
Location: Sacramento, CA, USA
AndrewW wrote:
New forum member bumping up an old thread.

Welcome! It looks like you're off to a productive start here. I have played around with WOZFLOATs a bit myself, but I never got as far as you seem to have progressed. My plate is full at the moment, but I'm looking forward to checking out your code when time and attitude permit.

_________________
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)


Top
 Profile  
Reply with quote  
PostPosted: Tue Nov 14, 2023 11:07 am 
Offline

Joined: Fri Jul 09, 2021 10:12 pm
Posts: 741
Nice job. I only had a glance at the code, and spotted one instruction that seems unnecessary (the ASL three lines after NOTNEG), but that's a bit of a silly optimisation - there are probably more significant ones elsewhere!


Top
 Profile  
Reply with quote  
PostPosted: Tue Nov 14, 2023 5:37 pm 
Offline

Joined: Mon Nov 13, 2023 7:33 pm
Posts: 3
gfoot wrote:
Nice job. I only had a glance at the code, and spotted one instruction that seems unnecessary (the ASL three lines after NOTNEG), but that's a bit of a silly optimisation - there are probably more significant ones elsewhere!

Thanks!

Here's that ASL instruction in context:
Code:
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
The ZP location at DIGITS+BSIZ-2 needs initialising with 0. We could just do:LDA #$0 then STA DIGITS+BSIZ-2 (4 bytes), but since we know the accumulator contains $80, ASL then STA DIGITS+BSIZ-2 does the same job and saves one whole byte (gosh).

Andrew


Top
 Profile  
Reply with quote  
PostPosted: Tue Nov 14, 2023 6:34 pm 
Offline

Joined: Fri Jul 09, 2021 10:12 pm
Posts: 741
The reason I said it's unnecessary is really because of what happens next - it doesn't actually matter whether that memory location gets initialised with $00 or $80, nor the additional side effect of the ASL (setting the carry), as the next 20 lines execute the same either way, and this value in memory then gets overwritten by the line labelled M. I'm pretty sure this sequence will end up with the same result whether the ASL is present or not.


Top
 Profile  
Reply with quote  
PostPosted: Thu Nov 16, 2023 7:24 pm 
Offline

Joined: Mon Nov 13, 2023 7:33 pm
Posts: 3
gfoot wrote:
The reason I said it's unnecessary is really because of what happens next - it doesn't actually matter whether that memory location gets initialised with $00 or $80, nor the additional side effect of the ASL (setting the carry), as the next 20 lines execute the same either way, and this value in memory then gets overwritten by the line labelled M. I'm pretty sure this sequence will end up with the same result whether the ASL is present or not.
You're right! Good spot.

The normalised representation of zero generated by NORM is $00, $00, $00, $00, but other numbers may have exponent=0. For instance, Woz's code seems happy to operate on the normalised representation of 2^-128 ($00, $40, $00, $00 = 2.93873587705571876992 ... × 10^-39), which has exponent=0. The previous version of PFLOAT incorrectly assumed that all numbers with exponent=0 are 0; this is easily fixed by removing the first 4 lines. PFLOAT is happy to print denormalised representations of zero (e.g. $FF, $00, $00, $00 prints as decimal 0), even though that causes Woz's routines to deliver erroneous values. For instance, denormalised 0 ($FF, $00, $00, $00) + 3 ($81, $60, $00, $00) incorrectly yields 0 ($FF, $00, $00, $00), but if NORM is called on the denormalised 0 before the addtion, it correctly yields 3 ($81, $60, $00, $00).

However, PFLOAT can't print the decimal representation of -2^128, even though that can be represented ($FF, $80, $00, $00), because in trying to do so it'll try to negate its input, and 2^128 is not representable.

Corrected and optimised PFLOAT with gfoot's optimisation, correct printing of numbers with exponent=0 plus simplifications to the loop management code (and now down to 130 bytes);

Code:
OUCH    = $BF2D             ; Print one character in A (UK101 MON01). X & Y must be preserved.
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 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 another at the other end
        STA DIGITS+BSIZ-2   ; ... and store that as the initial digit (ROL later turns it into zero)

; 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              ; Load exponent, and add one, since we always shift left at
        INY                 ; 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+BSIZ-2   ; Load A with least significant decimal digit
   
AGN     ROL                 ; Double the digit, with carry in (carry out ignored)
        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 or less
 
; 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
   
DIG1    LSR M1
        ROR M1+1
        ROR M1+2            ; Rotate 3 byte mantissa one bit right, LS bit into C
        LDX #<[1-BSIZ]      ; Index into buffer in ZP, counting up to zero
        BNE 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+BSIZ-1,X ; Store it back
        INX                 ; Step on to next digit
        BEQ R1              ; If we're now pointing at last byte in buffer, exit loop
AGN1    LDA DIGITS+BSIZ-1,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.
        ROL DIGITS+BSIZ-1,X ; Turn old sentinel into a 5
        STA DIGITS+BSIZ,X   ; Store the sentinel in its new location
   
R1      INY                 ; Count number of mantissa shifts
        CPY #$7F+24         ; Iterate for (24 + number of bits to right of binary point)
        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   


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 14 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 5 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: