6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sun May 12, 2024 9:44 pm

All times are UTC




Post new topic Reply to topic  [ 65 posts ]  Go to page 1, 2, 3, 4, 5  Next
Author Message
PostPosted: Wed Sep 29, 2010 7:56 pm 
Offline

Joined: Sat Aug 21, 2010 7:52 am
Posts: 231
Location: Arlington VA
I've got a pretzel in my brain! Over the decades the standards requirement for division has shifted around quite a bit. I'm trying to figure out which ones to implement and looking for suggestions here.

I'm leaning toward PETTIL having these, with "floored division" throughout. That way I can legitimately include the word FORTH-83 in my dictionary. Also I'd like to have code for division/modulo and multiplication exactly once, in accordance with Ruby's DRY principle.

/ * */ */MOD /MOD MOD UM* UM/MOD M* M*/

Having grabbed Garth's code for division from http://6502.org/source/integers/ummodfix/ummodfix.htm and the multiplication from http://6502.org/source/integers/32muldiv.htm , the unsigned part of this is working. I'll implement the signed stuff by subroutine calls to the unsigned math, XORing the sign bits and a bit of branching.

Dilemma 1: Garth's code returns $FFFFFFFF in the event of an error (quotient overflow or division by zero attempt). This could be confused with the results of " -8. 7 sm/rem " which in gforth returns " -1 -1 ". I was thinking about having the rule "remainders are always positive" and then I could test the sign of the remainder to determine if it was an error or not, but something about violating all three standards documents bothers me.

On a semi-related note, I am thinking about implementing a complete suite of floating point, based on the simple expedient of calling the PET ROM math routines and passing things around as 24-bit values on the stack.

What say you, geniuses? Any thoughts?

Everything below this point was extracted verbatim from the various standards documents:

Forth-79 required words
/ * */ */MOD /MOD MOD U* U/

Forth-83 required words
/ * */ */MOD /MOD MOD UM* UM/MOD

DPANS adds these to Forth-83
FM/MOD M* M*/ SM/REM


Integer division

Division produces a quotient q and a remainder r by dividing operand a by operand b. Division operations return q, r, or both. The identity b*q + r = a shall hold for all a and b. When unsigned integers are divided and the remainder is not zero, q is the largest integer less than the true quotient.

The Forth-79 Standard specifies that the signed division operators (/, /MOD, MOD, */MOD, and */) round non-integer quotients towards zero (symmetric division). Forth-83 changed the semantics of these operators to round towards negative infinity (floored division). Some in the Forth community have declined to convert systems and applications from the Forth-79 to the Forth-83 divide. To resolve this issue, an ANS Forth system is permitted to supply either floored or symmetric operators. In addition, ANS Forth systems must provide a floored division primitive (FM/MOD), a symmetric division primitive (SM/REM), and a mixed precision multiplication operator (M*).

When signed integers are divided, the remainder is not zero, and a and b have the same sign, q is the largest integer less than the true quotient. If only one operand is negative, whether q is rounded toward negative infinity (floored division) or rounded towards zero (symmetric division) is implementation defined.

In cases where the operands differ in sign and the rounding direction matters, a program shall either include code generating the desired form of division, not relying on the implementation-defined default result, or have an environmental dependency on the desired rounding direction.

Floored division is integer division in which the remainder carries the sign of the divisor or is zero, and the quotient is rounded to its arithmetic floor.

Code:
Floored Division Example

Dividend        Divisor Remainder       Quotient
--------        ------- ---------       --------
10                 7       3                1
-10                7       4               -2
10                -7      -4               -2
-10               -7      -3                1


Symmetric division is integer division in which the remainder carries the sign of the dividend or is zero and the quotient is the mathematical quotient rounded towards zero or truncated.

Code:
Symmetric Division Example

Dividend        Divisor Remainder       Quotient
--------        ------- ---------       --------
10                 7       3                1
-10                7      -3               -1
10                -7       3               -1
-10               -7      -3                1


Last edited by chitselb on Wed Sep 29, 2010 11:06 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Sep 29, 2010 8:47 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8433
Location: Southern California
Quote:
Dilemma 1: Garth's code returns $FFFFFFFF in the event of an error (quotient overflow or division by zero attempt). This could be confused with the results of " -8. 7 sm/rem " which in gforth returns " -1 -1 ".

Remember the "U" in "UM/MOD" means "unsigned," so its input and output are always positive. Signs are handled externally to UM/MOD. I did the $FFFFFFFF so I could handle errors externally too, satisfying my own preference for the application. I don't like for it to just abort with an overflow error when sometimes the maximum representable number is an acceptable substitute and the program can keep going.

Quote:
On a semi-related note, I am thinking about implementing a complete suite of floating point, based on the simple expedient of calling the PET ROM math routines and passing things around as 24-bit values on the stack.

One of my hesitations about Forth when I started getting into it was that most Forths don't include floating-point, prefering the efficiency of fixed-point and scaled-integer instead (although they definitely don't prohibit floating-point). But as I've gained experience with Forth and with fixed-point and scaled-integer math, I've found that floating is completely unnecessary in most applications, including ones everyone thinks of as needing it. I started writing a column on how to free yourself from floating-point, at viewtopic.php?t=716 . It goes unfinished, like so many projects, but hopefully it will still be of value. Maybe some day I'll get back to continuing it. I gave the 8th post the title,
Installment #1: Introduction to scaled-integer arithmetic (or: "Why you probably don't need floating-point")
Edit: I further explain it on the front page of my website feature on huge look-up tables for ultrafast 16-bit scaled-integer math.

I also started a topic on having an extra stack for higher-precision math, at viewtopic.php?t=493 . The background and ideas are there, and of course responses.



chitselb, you can make the forum software stop throwing out the intentional added spaces by putting [co_de] and [/co_de] (but remove the underscores so the directives will do their jobs) around the portions you want to keep spaced, and it will look like this:

Code:
Dividend        Divisor Remainder       Quotient
--------        ------- ---------       --------
10                 7       3                1
-10                7       4               -2
10                -7      -4               -2
-10               -7      -3                1


and your quote in between in non-code: "Symmetric division is..." then back to code:

Code:
Dividend        Divisor Remainder       Quotient
--------        ------- ---------       --------
10                 7       3                1
-10                7      -3               -1
10                -7       3               -1
-10               -7      -3                1



Edit: I think it was the original fig-Forth mixed-precision multiplication that had a bug in it that gave totally wrong results under rare conditions. Maybe you've already found it. If not, I'll try to dig it up. [Edit again: It's at viewtopic.php?f=9&t=689 .] I don't remember any details at the moment. I corrected it in my '02 Forth (which was not fig-Forth, but I originally got it from a supplier that pulled a lot of fig-Forth components into it, then I heavily modified it), but I used the defective multiply routine for many years before I realized it was there. When I wrote an FFT word, I got some results that I originally thought were just from lack of precision, but as I looked into it further, found that my multiplication (which FFTs do a ton of) occasionally gave a result that was nowhere near the right answer.

_________________
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: Thu Sep 30, 2010 12:38 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8181
Location: Midwestern USA
chitselb wrote:
On a semi-related note, I am thinking about implementing a complete suite of floating point, based on the simple expedient of calling the PET ROM math routines and passing things around as 24-bit values on the stack.

The PET ROM functions are based on excess-128 notation, which in this case limits you to seven significant digits. Also, there are ASCII to binary conversion errors that will get you. Better to follow Garth's suggestion and use scaled integer math. You will not get the range that excess-128 produces, but you will get a lot more significant digits, which is usually more important than absolute range.

Be that as it may, using a 64 bit signed format (bit 63 is the sign bit) will express a range of ±9,223,372,036,854,775,808. That's more than enough to store the national debt or Bill Gates' net worth, even after shifting to account for the fractional component. :)

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Thu Sep 30, 2010 1:46 am 
Offline
User avatar

Joined: Thu Mar 11, 2004 7:42 am
Posts: 362
chitselb wrote:
Dilemma 1: Garth's code returns $FFFFFFFF in the event of an error (quotient overflow or division by zero attempt). This could be confused with the results of " -8. 7 sm/rem " which in gforth returns " -1 -1 ".


Note that for t / b, 0 <= abs(r) < abs(b) -- i.e. less than instead of less than or equal to abs(b) -- so in the unsigned case if you get a $FFFFFFFF it must be an error, since the largest b is $FFFFFFFF. In the signed case, you could return $80000000 for an error, since r cannot possibly have that value unless there was an error, because the largest (absolute value of) b is 2^31. Then you just test r in the for whichever of those two values applies depending on whether its signed or unsigned division.

chitselb wrote:
I was thinking about having the rule "remainders are always positive" and then I could test the sign of the remainder to determine if it was an error or not, but something about violating all three standards documents bothers me.


The "always positive remainder" is called Euclidian division. See:

http://en.wikipedia.org/wiki/Modulo_operation

(What the Forth spec(s) calls Symmetric division, Wikipedia calls Truncated division.)

I like Euclidian division, but it depends whether you need compatibility with ANS Forth or Forth-83. If so, there are implementations of FM/MOD in high-level Forth out there (eForth has one, I think) in terms of UM/MOD. To me, unsigned division/modulo is far more useful than signed anyway. I've tended to define U/ and UMOD as unsigned versions of / and MOD and then used the former instead of the latter.


Top
 Profile  
Reply with quote  
PostPosted: Thu Sep 30, 2010 2:49 pm 
Offline

Joined: Sat Aug 21, 2010 7:52 am
Posts: 231
Location: Arlington VA
dclxvi wrote:
The "always positive remainder" is called Euclidian division. See:

http://en.wikipedia.org/wiki/Modulo_operation

(What the Forth spec(s) calls Symmetric division, Wikipedia calls Truncated division.)

I like Euclidian division, but it depends whether you need compatibility with ANS Forth or Forth-83. If so, there are implementations of FM/MOD in high-level Forth out there (eForth has one, I think) in terms of UM/MOD. To me, unsigned division/modulo is far more useful than signed anyway. I've tended to define U/ and UMOD as unsigned versions of / and MOD and then used the former instead of the latter.


This was very helpful. Let's see what happens when we start with Leo Brodie's "quarters" word from Starting Forth and throw some negative numbers into the story.
Leo Brodie wrote:
For example, let's say you want to solve this problem: "How many dollar bills can I get in exchange for 22 quarters?"

Code:
: QUARTERS  4 /MOD . ." ones and "
    . ." quarters " ;

How about changing that to, "If I owe you 22 quarters and you want it in dollar bills..." (-22 quarters)

Blazin' Forth -22 quarters -6 ones and 2 quarters
gforth -22 quarters -6 ones and 2 quarters
PETTIL -22 quarters -6 ones and 2 quarters
Euclidian: -6 ones and 2 quarters
Floored/Forth-83: -6 ones and 2 quarters
Symmetric/Truncated/Forth'79: -5 ones and -2 quarters

So it boils down to I'd rather you owe me 50 cents than I owe you 50 cents. I'm having a hard time coming up with a real world division example that involves a negative divisor, which is where Euclidean division distinguishes itself from the other two flavors.

This raises the issue of how does one best do error handling? I could just ABORT" division overflow" from a "/" secondary, but UM/MOD is a primitive so calling ABORT" would be problematic.

1. Have a USER variable (called what?) that displays errors (true) or ignores them (false) or aborts on specific bitmask AND operations (positive)
2. implement something like Java's catch/throw exception processing. This seems like overkill.
3. define and return error values on the stack and let the application programmer deal with it. "0 remainder -32768" or $80000000 for signed results and "32727 remainder 32767" or $FFFFFFFF for unsigned results


Top
 Profile  
Reply with quote  
PostPosted: Sat Oct 02, 2010 8:45 pm 
Offline
User avatar

Joined: Thu Mar 11, 2004 7:42 am
Posts: 362
chitselb wrote:
This raises the issue of how does one best do error handling? I could just ABORT" division overflow" from a "/" secondary, but UM/MOD is a primitive so calling ABORT" would be problematic.


The way I wound up handling this was to have two words (UM/MOD) and UM/MOD, the first was a primitive that did no range checking (i.e. garbage in, garbage out) and the second was a high-level word that did an ABORT (or later on, a THROW, after I had implemented an exception mechanism (which I found helpful when dealing with files)) when (for t/b) upper-cell-of-t >= b. You can do a similar thing for signed division as well.

Those of you who are on a first name basis with irony may have already guessed what happened. I never really used the high-level word, since the primitive was faster and in almost all instances the parameters were already valid in by the time UM/MOD was executed; I can't recall ever adding any additional range checking for UM/MOD.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Sat Oct 02, 2010 10:43 pm 
Offline

Joined: Sat Jan 04, 2003 10:03 pm
Posts: 1706
This discussion is, in essence, the age-old "error code" versus "exception" religio...er...debate.

Towards that end, my take is that you need to ask yourself, "What is a function's range?" In the case of addition and multiplication of two integers, the result is always an integer. Division, though, requires a wider range.

If you want to think abstractly, you can think of division as being quite well defined for the case when the denominator is zero -- the result is a symbolic concept known as "undefined." In other words, the range of the integer division operator is the set of all integers and undefined.

If your program expects division to always yield a numeric result, it will be in error unless you can assert, prior to the division, that its denominator parameter will never equal zero. You can do this by an explicit IF statement, or through careful data/control-flow analysis in your program, or through formal techniques, such as the application of Hoare logic.

If you cannot guarantee, at all times, validity of input parameters, then you must now decide who is responsible for dealing with this problem. Errors tend to be best handled near their point of occurrence, so returning a sentinel value to indicate "undefined" seems a reasonable choice. Alternatively, you can document those conditions where a division-by-zero would result and place the burden on the programmer to avoid those preconditions. This way, a programmer at least has the knowledge to detect when bad things happen, as proximately as possible to invoking your word.

Exceptions, ideally, ought to be reserved for truly exceptional (e.g., completely unanticipated) problems. Out of stack space, parity errors, and the like.

This is just my minority opinion on this topic, influenced by my experience with the Haskell programming language.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Sun Oct 03, 2010 8:04 am 
Offline

Joined: Sat Aug 21, 2010 7:52 am
Posts: 231
Location: Arlington VA
Here it is in all it's "I just now got it working" glory, my 32-bit unsigned division code. My first thought is, it's gigantic. I wish I could make it smaller. The idea is I'll use this divmod subroutine everywhere else where division is used and then fiddle with the stack before and after to make the other flavors.
Code:
;--------------------------------------------------------------
;
;       UD/MOD   ( d1 n1 -- n2 d2 )
;
; * divstar
;
; d2 is the double quotient of d1/n1.  n2 is the remainder.  All
; values are unsigned.
; ~ fix for /0
udslashmodlfa   .word $adde
                .byt (udslashmod-*-1)|bit7
                .asc "UD/MO","D"|bit7
udslashmod      ldy #2
                jsr setup               ; udhi -> N0; udlo -> N1
                jsr divmod              ; unsigned u32/u16 -> 32quo rem16
                dex
                lda n+2
                sta stackl,x
                lda n+3
                sta stackh,x            ; remainder
                lda n+7
                sta tos+1
                lda n+6
                sta tos
                ldy n+5
                lda n+4
                jmp pushya
divmod          lda zi+1                ; 32/16 -> 32-quotient 16-remainder
                pha
                lda zi
                pha
                lda zi+3
                pha
                lda zi+2
                pha
                sty zi+1
                sty zi                  ; zero the 32-bit divisor
                sty zi+3
                sty zi+2
                sty n+5
                sty n+4
                sty n+7
                sty n+6                 ; zero the 32-bit quotient
                ldy #32+1               ; number of iterations = 32
                lda tos+1
                ora tos
                beq divmod05            ; error division by zero
divmod01        lsr tos+1
                ror tos
                ror zi+1
                ror zi
                ror zi+3                ; slide the 16-bit divisor over
                ror zi+2
                dey
                lda tos+1
                ora tos
                bne divmod01            ; until it's all slid over
divmod02        asl n+6
                rol n+7
                rol n+4
                rol n+5                 ; double the quotient
                sec
                lda n+2
                sbc zi+2
                pha                     ; save
                lda n+3
                sbc zi+3
                pha                     ; the
                lda n
                sbc zi
                pha                     ; answers
                lda n+1
                sbc zi+1
                bcc divmod03            ; keep or discard subtraction?
                sta n+1
                pla
                sta n
                pla
                sta n+3
                pla
                sta n+2
                inc n+6                 ; add 1 to quotient
                bcs divmod04            ; bra
divmod03        pla
                pla
                pla                     ; discard result of subtraction
divmod04        lsr zi+1
                ror zi
                ror zi+3
                ror zi+2
                dey
                bne divmod02
                pla
                sta zi+2
                pla
                sta zi+3
                pla
                sta zi
                pla
                sta zi+1
divmod05        rts


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Oct 04, 2010 11:59 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10800
Location: England
There's a neat trick called non-restoring division: you do the subtraction, take action according to the sign of the answer, and proceed. You don't have to add back, or save/restore the unsubtracted value. Instead your next 'subtraction' becomes an addition if you overflowed. The wikipedia writeup does not look useful - it implies a need to post-process, which is wrong - perhaps see the bottom of this page instead.

I can't quite figure out whether Garth's division page is non-restoring or not.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Oct 04, 2010 7:10 pm 
Offline

Joined: Tue Nov 18, 2003 8:41 pm
Posts: 250
BigEd wrote:
There's a neat trick called non-restoring division: you do the subtraction, take action according to the sign of the answer, and proceed.


Speaking of which here's Rodnay Zaks code for 16/8 non-restoring
division which I said I'd post and then forgot about
(couldn't lay my hands on the book at the time)

Code:
DIV
  ldy #8
  sec
  sbc d
LOOP
  php
  rol q
  asl b
  rol a
  plp
  bcc ADD
  sbc d
  jmp NEXT
ADD
  adc d
NEXT
  dey
  bne LOOP
  bcs LAST
  adc d
  clc
LAST
  rol q


(I still haven't tryed it)


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Oct 04, 2010 9:59 pm 
Offline
User avatar

Joined: Tue Mar 02, 2004 8:55 am
Posts: 996
Location: Berkshire, UK
That looks like a dodgy to me. What is the value in the accumulator on entry supposed to be? 'd' is subtracted from it but its not loaded or stored.

_________________
Andrew Jacobs
6502 & PIC Stuff - http://www.obelisk.me.uk/
Cross-Platform 6502/65C02/65816 Macro Assembler - http://www.obelisk.me.uk/dev65/
Open Source Projects - https://github.com/andrew-jacobs


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Oct 04, 2010 10:10 pm 
Offline

Joined: Sat Jan 04, 2003 10:03 pm
Posts: 1706
The accumulator appears to be the upper 8-bits of the 16-bit numerator. d appears to be the divisor.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Oct 04, 2010 10:21 pm 
Offline
User avatar

Joined: Tue Mar 02, 2004 8:55 am
Posts: 996
Location: Berkshire, UK
kc5tja wrote:
The accumulator appears to be the upper 8-bits of the 16-bit numerator. d appears to be the divisor.

It is. I dug out my copy of Zaks to see what he was up to. Looks weird but is probably right and I'm just too tired to read it correctly (started new job this morning, London tubes went on strike to celebrate, drove in to London at 6am got home at 8pm)

_________________
Andrew Jacobs
6502 & PIC Stuff - http://www.obelisk.me.uk/
Cross-Platform 6502/65C02/65816 Macro Assembler - http://www.obelisk.me.uk/dev65/
Open Source Projects - https://github.com/andrew-jacobs


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Oct 06, 2010 2:29 am 
Offline
User avatar

Joined: Thu Mar 11, 2004 7:42 am
Posts: 362
That non-restoring code looks like it has the UM/MOD bug (or what amounts to the equivalent of it). Compare $9000/$C0 to $4800/$60 (which should, of course, give the same remainder and quotient), or compare $00F0/$C0 to $00F0/$60 (same remainder, quotient of the former should be half the quotient of the latter -- also, note that this means that unlike restoring division, there's still an issue when the upper byte of the numerator is 0).

The red flag to look for is a bit being shifted out that is subsequently ignored/overwritten. In this case it's the "asl b rol a" sequence. (The rol q doesn't matter, since that's an output, not an input. In fact, you could return the quotient in b and eliminate two instructions from the loop below.)

Here's what I came up with. I haven't tested it for all (valid) cases, though.

Code:
DIV
  ldy #8
  sec
  sbc d
LOOP
  bcc ADD
  rol q
  asl b
  rol a
  bcc SKIPSUB
;
; when we get here, we shifted out a one, which means we have a
; positive number that will still be positive even after the
; subtraction, so we want to branch to NEXT with the carry set.
;
  sbc d
  sec
  bcs NEXT ;branch always
SKIPSUB
  sec
  sbc d
  jmp NEXT
ADD
  rol q
  asl b
  rol a
  bcs SKIPADD
;
; when we get here, we shifted out a zero, which means we have a
; negative number that will still be negative even after the
; addition, so we want to branch to NEXT with the carry clear.
;
  adc d
  clc
  bcc NEXT ;branch always
SKIPADD
  clc
  adc d
NEXT
  dey
  bne LOOP
  bcs LAST
  adc d
  clc
LAST
  rol q


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Oct 06, 2010 4:50 pm 
Offline

Joined: Tue Nov 18, 2003 8:41 pm
Posts: 250
the only "improvement" that I was contemplating is getting the remainder/
dividend aligned with the divisor.
Zak's code always does an add or subtract whether he needs to or not.
That's not so bad in his case because he's got a single byte divisor it'd
be a bit more costly with a 32/16 divide.

I put "improvement" in quotes because I did a 16/16 non-restoring
division routine that did try and keep the alignment. It was years
and years ago and I don't remeber the details (I've got it here
somewhere but I haven't been able to find it yet). From what I
recall it involved a moderately complicated bit of xoring of carries
and signs to determine whether to add, subtract or just shift, and
it was about ten-twenty times as much code and was painfully slow
(in my memory at least)

I'm sure I can do better now.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 65 posts ]  Go to page 1, 2, 3, 4, 5  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 1 guest


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: