6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sun Nov 24, 2024 8:09 pm

All times are UTC




Post new topic Reply to topic  [ 22 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Mon Feb 18, 2008 12:46 am 
Offline

Joined: Thu Jul 05, 2007 4:11 pm
Posts: 33
Can anyone tell me if this simple division routine should work or not? I'm just trying to have a simple 8-bit division. i.e. devide one 8bit number by another 8bit number and come up with an 8bit result.

Code:
div    STY divisor
       LDY #$0      ;reset y to 0
       SEC      ;set carry
div1   SBC divisor   ;subtract divisor from divedend
       BCC div2   ;if carry cleared, meaning subtraction went below 0
       INY      ;increment y to increase quotient
       BNE div1
div2   rts      ;quotient in Y, remainder in A


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Feb 18, 2008 1:35 am 
Offline

Joined: Thu Jul 05, 2007 4:11 pm
Posts: 33
Never mind... I found that it does work!

Thanks anyway.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 20, 2008 12:05 am 
Offline
User avatar

Joined: Thu Mar 11, 2004 7:42 am
Posts: 362
By the way, there are also a couple of division routines on the wiki at:

http://6502org.wikidot.com/software-math-intdiv

Division routines are a fairly common request here on the forum, so I am planning to add more routines to the article, but it's been awhile since I've updated it.

Your routine is fine for simple uses, though. Its main disadvantage is that the time it takes varies so widely, e.g. 240/3 loops many times, but 21/7 only loops a few times. However, that's often not that big of a deal for 8-bit division.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 20, 2008 4:06 am 
Offline

Joined: Thu Jul 05, 2007 4:11 pm
Posts: 33
I used a table based Multiplication routine... Very fast as there is no looping involved, but I couldn't find anything equivilent for division. Is there such a thing?


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 20, 2008 5:04 am 
Offline

Joined: Tue Nov 18, 2003 8:41 pm
Posts: 250
DonnaD wrote:
I used a table based Multiplication routine... Very fast as there is no looping involved, but I couldn't find anything equivilent for division. Is there such a thing?


Sort of, use a table of reciprocals and multiply.

But it's more usual to use a small table of approximate reciprocals
and refine the values using Newton-Raphson iteration - if you have
fast enough multiplication and you want enough bits to make
the additional rigamarole worth while (if you're only doing
8 bits/8bits there's probably better ways especially if you're using
Newton-Raphson to refine your reciprocals, if you're doing
64bits/64bits it might be worth it)

For some values you can only have approximate reciprocals.
like the binary analog of (decimal) 1/3 being .333...
(in binary, .010101...)
So you run into the complication of how accurate is accurate enough
and how do you get the remainder if you need it and where was the
decimal point?
3 x .333... = .999... presumably you'd like to get 1 so now
you have to decide how when and where to round.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 20, 2008 5:09 am 
Offline

Joined: Tue Nov 18, 2003 8:41 pm
Posts: 250
bogax wrote:
DonnaD wrote:
I used a table based Multiplication routine... Very fast as there is no looping involved, but I couldn't find anything equivilent for division. Is there such a thing?


Sort of, use a table of reciprocals and multiply.




And then there's logarithms ..


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 20, 2008 5:26 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8546
Location: Southern California
Quote:
And then there's logarithms ..

16-bit base-2 logarithms can be interpolated fairly accurately from a surprisingly small table; but the interpolation itself requires multiplication and division. The more mickey-mouse way to do it, faster and somewhat more acceptable for when you're only needing 8-bit accuracy, is to say that the integer part of the log, say the three high bits in an 8-bit log of an 8-bit argument, reflect the number of the highest bit in the argument that has a 1 in it, counted down from 8 as you shift left and stop when the shift results in a 1 in the carry flag. Then the fractional part of the log is just the rest of the number, shifted as necessary to make it start in bit 4 in this case.

Edit, 7/26/12, since someone asked for 6502 code to show the more mickey-mouse way: "DFS" in the C32 assembler means "DeFine Storage," which is used for variables, and the parameter tells how many bytes to set aside for that variable. I ran the code, kind of spot-checking with different input values and watching the output, so I'm pretty confident I got it right. The output is scaled by 32, so an output of 255 ($FF) is just under 8 (actually 111.11111 in binary, 8 bits), as the base-2 log of $100 is 8.

Edit: There's an improved version four posts down, and the inverse log on the next page.

First the structured version for 8-bit log of an 8-bit number:
Code:
LOG_INPUT:   DFS  1
LOG_OUTPUT:  DFS  1

8BIT_LOG:                  ; (structured version)
        LDA  #7            ; Init the integer part of the output.  Each 0 bit at the
        STA  LOG_OUTPUT    ; left end of the input will decrement it by 1.  (Taking
                           ; the log of 0 is not allowed, so this won't reach -1.)
        BEGIN
           ASL  LOG_INPUT  ; Shift the input left to find the first 1 bit.
        WHILE_C_CLR        ; If carry is still clear, we didn't find it, so
           DEC  LOG_OUTPUT ; decrement what will become the integer part of the answer.
        REPEAT             ; Repeat the loop until we've found the first 1 bit.

        LSR   LOG_INPUT    ; What's left of the input after finding and stripping off
        LSR   LOG_INPUT    ; the first 1 bit will be the fractional part of the answer.
        LSR   LOG_INPUT    ; but its high bit needs to be in bit 4's position, not 7's,
                           ; so shift it over three bit places.
        LDA   LOG_OUTPUT   ; The _integer_ part of the answer is now in bits 2-0,
        ASL   A            ; but we need it in bits 7-5, so shift it over.  The
        ASL   A            ; fractional part of the answer will go in bits 4-0.
        ASL   A
        ASL   A
        ASL   A

        ORA   LOG_INPUT    ; Put the two parts together.
        STA   LOG_OUTPUT   ; (Optional)

        RTS
 ;--------------------


or the non-structured version:
Code:
LOG_INPUT:   DFS  1
LOG_OUTPUT:  DFS  1

8BIT_LOG:
         LDA  #7            ; Init the integer part of the output.  Each 0 bit at the
         STA  LOG_OUTPUT    ; left end of the input will decrement it by 1.  (Taking
                            ; the log of 0 is not allowed, so this won't reach -1.)

 loop:   ASL  LOG_INPUT     ; Shift the input left to find the first 1 bit.
         BCS  exloop        ; If carry is set, we found it, so exit the loop.  Otherwise,
         DEC  LOG_OUTPUT    ; decrement what will become the integer part of the answer.
         BRA  loop          ; Repeat the loop until we've found the first 1 bit.

 exloop: LSR   LOG_INPUT    ; What's left of the input after finding and stripping off
         LSR   LOG_INPUT    ; the first 1 bit will be the fractional part of the answer.
         LSR   LOG_INPUT    ; but its high bit needs to be in bit 4's position, not 7's,
                            ; so shift it over three bit places.
         LDA   LOG_OUTPUT   ; The _integer_ part of the answer is now in bits 2-0,
         ASL   A            ; but we need it in bits 7-5, so shift it over.  The
         ASL   A            ; fractional part of the answer will go in bits 4-0.
         ASL   A
         ASL   A
         ASL   A

         ORA   LOG_INPUT    ; Put the two parts together.
         STA   LOG_OUTPUT   ; (Optional)

         RTS
 ;--------------------

If you want all the speed and accuracy you can get in 8 bits and can spare 256 bytes for a table, that would be the ticket, where every value is correct, pre-calculated, and all you need is TAX, LDA table,X.

_________________
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  
 Post subject:
PostPosted: Wed Feb 20, 2008 6:51 am 
Offline

Joined: Sat Jan 04, 2003 10:03 pm
Posts: 1706
Wow, it is shocking to see how so few people remember "rationals." Don't divide until you need to, basically.

The PC/GEOS (aka GeoWorks Ensemble) operating system used rationals for its GUI and was one of the first OSes on the entire planet to make use of scalable font technology, all without an FPU, and with adequate performance on an 8086 processor.

Rationals are just fractions, in case you were wondering. For example, the approximation of pi, 355/113, is a rational, as is 31415/10000. Each rational would be stored in memory as two, adjacent integers of appropriate size.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Sun Feb 24, 2008 12:14 pm 
Offline

Joined: Tue Sep 24, 2002 4:56 pm
Posts: 50
Location: Essex, UK
I have recently acquired a couple of BBC Micro-related books off of eBay, and one of them (at least) had multiplication and division routines; at the moment, I'm working off the top of my head, so the following is in "pseudo-code" rather than assembler, but should be pretty much in line with what I read:

To perform: (dividend / divisor) => [quotient, remainder]; divisor != 0
Code:
start:
    quotient := 0;
    remainder := 0;
    shift_count := 8;
    temp := 0;
loop:
    dividend := (dividend << 1);     \ Shifts MSB out
    temp := (temp << 1);             \ Shifts MSB in
    if(temp >= divisor) {
        quotient = (quotient + 1);
        temp := (temp - dividend);
    }
    shift_count := (shift_count - 1);
    if(shift_count > 0) then goto loop;
done:
    remainder := temp;
    return;
end:


As I _am_ working off memory here, I may well be off with the above algorithm, but it would be about right; also, the way the algorithm is coded, it is not just the "opposite" of the equivalent multiply routine (i.e. shift-and-add), but would be easy to implement in 6502 assembler.

HTH,

--Martin

_________________
Martin Penny

.sig in beta - full release to follow.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Sun Feb 24, 2008 2:11 pm 
Offline

Joined: Tue Sep 24, 2002 4:56 pm
Posts: 50
Location: Essex, UK
I knew I'd forget something - if the algorithm has an extra line added to it, it should then work (and, yes, it assumes unsigned integers; and "shift_count" is the size of the dividend).

[...]
loop:
quotient := (quotient << 1);
dividend := (dividend << 1); \ Shifts MSB out
[...]

--Martin

_________________
Martin Penny

.sig in beta - full release to follow.


Top
 Profile  
Reply with quote  
PostPosted: Sat Dec 19, 2020 7:00 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8546
Location: Southern California
I don't why this didn't occur to me years ago, but I was revisiting this and realized my quick-n-dirty log routine above could be more efficient:
Code:
LOG_INPUT:  DFS  1


8BIT_LOG: STA  LOG_INPUT   ; Start with input in A, and get the base-2 log, scaled by 32.
          LDA  #11100000B  ; Init the integer part of the output.  Each 0 bit at the
                           ; left end of the input will decrement it by 1(00000).  (Taking
loop:     ASL  LOG_INPUT   ; the log of 0 is not allowed, so this won't reach -1.)
          BCS  exloop      ; If carry is set, we found it, so branch.  Otherwise,
          SBC  #011111B    ; decrement what will become the integer part of the answer.
          BRA  loop        ; Repeat the loop until we've found the first 1 bit.
                           ; (It's 011111B above instead of 100000B because C is clear.)
exloop:   LSR  LOG_INPUT   ; What's left of the input after finding and stripping off
          LSR  LOG_INPUT   ; the first 1 bit will be the fractional part of the answer.
          LSR  LOG_INPUT   ; but its high bit needs to be in bit 4's position, not 7's,
                           ; so shift it over three bit places.
          ORA  LOG_INPUT   ; Output is in A.
          RTS
 ;--------------------

Again, the value in logs in the context of this topic is that you can multiply and divide by adding and subtracting the logs.  (I should do the antilog now.)

_________________
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: Sat Dec 19, 2020 10:24 am 
Online
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10986
Location: England
Just checking: this is an approximate log, yes? Any idea roughly how accurate the resulting multiplication and division will be?


Top
 Profile  
Reply with quote  
PostPosted: Sat Dec 19, 2020 10:34 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8546
Location: Southern California
BigEd wrote:
Just checking: this is an approximate log, yes?
Definitely.

Quote:
Any idea roughly how accurate the resulting multiplication and division will be?
I (or someone) would have to run some tests (which I might do anyway for the application that caused me to revisit this).  It may be fine for things like calculating graphics on a low-res display.

_________________
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: Sat Dec 19, 2020 9:01 pm 
Offline

Joined: Sun Apr 26, 2020 3:08 am
Posts: 357
I think the only way to do division using a multiplication table is with the use of a binary search method.
Using this 16x16 multiplication table, let's see how this would work

4000:01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10
4010:02 04 06 08 0A 0C 0E 10 12 14 16 18 1A 1C 1E 20
4020:03 06 09 0C 0F 12 15 18 1B 1E 21 24 27 2A 2D 30
4030:04 08 0C 10 14 18 1C 20 24 28 2C 30 34 38 3C 40
4040:05 0A 0F 14 19 1E 23 28 2D 32 37 3C 41 46 4B 50
4050:06 0C 12 18 1E 24 2A 30 36 3C 42 48 4E 54 5A 60
4060:07 0E 15 1C 23 2A 31 38 3F 46 4D 54 5B 62 69 70
4070:08 10 18 20 28 30 38 40 48 50 58 60 68 70 78 80
4080:09 12 1B 24 2D 36 3F 48 51 5A 63 6C 75 7E 87 90
4090:0A 14 1E 28 32 3C 46 50 5A 64 6E 78 82 8C 96 A0
40A0:0B 16 21 2C 37 42 4D 58 63 6E 79 84 8F 9A A5 B0
40B0:0C 18 24 30 3C 48 54 60 6C 78 84 90 9C A8 B4 C0
40C0:0D 1A 27 34 41 4E 5B 68 75 82 8F 9C A9 B6 C3 D0
40D0:0E 1C 2A 38 46 54 62 70 7E 8C 9A A8 B6 C4 D2 E0
40E0:0F 1E 2D 3C 4B 5A 69 78 87 96 A5 B4 C3 D2 E1 F0
40F0:10 20 30 40 50 60 70 80 90 A0 B0 C0 D0 E0 F0 00


I think most know how to do a binary search. You have a max-length and min-length which are the number of entries for each value. The max would then be 15 ($F) and the min starts out at 0. We add the two lengths and divide by 2 with a simple LSR. We also have to check that the difference between MIN and MAX is <= 1 to signal the end of a search.

Say for instance we want to find 12/5. The 5 being the divisor is the fifth row we start our search (starts at $4040)
We start with (MIN=0 + MAX=15)/2 = 15/2 = 7. We then put 7 into the x-reg and add to $4040,x. We see that the 7th value of the 5th line = 40 ($28). Compare the value 12 with 40 makes it less-than and also NOT-EQUAL, so we will store MAX as 7 and keep MIN as 0. If it is greater-than, we would make MIN=7 and keep MAX as 15. If they are equal then we stop.

Now get the half way point of the 2 ranges again with a simple LSR. Also check that the difference between MIN and MAX is <= 1.

(MIN=0 + MAX=7)/2 = 7/2 = 3. The 3rd value of $4040,x is 20 ($14). Our original value is still less-than this value, so halve the MIN/MAX values again.

(MIN=0 + MAX=3)/2 = 1. The 1st value of $4040,x is 10 ($0A). Our original value of 12 is now greater-than this value, now we make MIN=1.

(MIN=1 + MAX=3)/2 = 2. The 2nd value of $4040,x is 15 ($0F). Our original value of 12 is now less-than this value, and we make MAX =2.

(MIN=1 + MAX=2)/2 = 1. Our program should now have caught the MIN/MAX difference of <= 1. and we have also determined that our original value and last value found do not match. Which means we have a value between the number at MIN and the number at MAX. All that is left is to subtract the value at MIN from the original to get our remainder. And since our look-up table is using an index of 0-15 (0-$F), but our division factors deal with 1-16 (1-$10), we have to increment MIN by 1 to get the actual divisor.

12 (original value) - 10 (x=MIN=1; value at $4040,x) = 2 (remainder)
MIN + 1 = divisor

RESULT: 12/5 = quotient = 2 and remainder = 2

This only took 3 times thru the loop to find the result, which one will find that is pretty much standard times through the loop for division of any two 8-bit numbers.

BTW: since this search method takes longer for smaller factors, we can do a simple quick check to decide which routine would be faster. The cut-off line seems to be with 4-5 times through the loop. So if we look at the 4th or 5th column of the divisor and compare the value there with the dividend. If less-than we use the routine in the very first post of this thread. If greater-than then this binary-search method will be faster.

As an example, a division of 46/5 using the first-post method will take 9 times through the loop. This binary-search method will still only take 3-4 loops. This doesn't seem like much time savings being twice as fast, but this is just a small table. Imagine the time saving on 16-bit, 24-bit or 32-bit values.

I guess the next problem to solve is, is the speed and size of these tables worth the price of memory?


Top
 Profile  
Reply with quote  
PostPosted: Sun Dec 20, 2020 8:13 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8546
Location: Southern California
GARTHWILSON wrote:
BigEd wrote:
Just checking: this is an approximate log, yes?
Definitely.

Quote:
Any idea roughly how accurate the resulting multiplication and division will be?
I (or someone) would have to run some tests (which I might do anyway for the application that caused me to revisit this).  It may be fine for things like calculating graphics on a low-res display.
If I did it right, checking the cheap-n-dirty log approximation for all 255 input values (1-$FF), I find only 39 that were what they should have been for a one-byte output.  66 (or over one-fourth) were one count low, 119 (or nearly half) were two counts low, and 31 were three counts low.  The worst areas were approximately $4E-$6D where adding one count to all these would generally improve accuracy, and $99-$DC where adding two counts would generally improve accuracy.  Of course, if you start looking for ranges to 'fix,' it won't be cheap 'n' dirty anymore.  Even just adding one count to everything would mostly improve it except that you'd be way off at inputs 1 and 2.

I have not tested the effects on multiplication and division yet.

_________________
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  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 22 posts ]  Go to page 1, 2  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 16 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: