FM/MOD bug in CamelForth. (19 Aug 1999)

Versions of CamelForth prior to August 1999 will return an incorrect result if the division of two positive numbers yields a zero quotient and a nonzero remainder. For example, 3 0 5 FM/MOD should return a quotient of 0 and a remainder of 3; instead, it returns a quotient of -1 and a remainder of 8.

The original CamelForth FM/MOD was written in terms of SM/REM. After trying to fix the logic which adjusts the symmetric quotient and remainder for floored division, I realized that it would be faster and smaller to redefine FM/MOD in terms of the fundamental unsigned division operator, UM/MOD. Here is the corrected definition of FM/MOD, in Forth source and in 8051 assembly.

Note: if you install the 8051 fix yourself, be aware that the the new FM/MOD is slightly larger than the old, so some ACALLs will have to be changed to LCALLs.

;C FM/MOD   d1 n1 -- n2 n3   floored signed div'n
;   DUP >R              divisor
;   2DUP XOR >R         sign of quotient
;   >R                  divisor
;   DABS R@ ABS UM/MOD
;   SWAP R> ?NEGATE SWAP  apply sign to remainder
;   R> 0< IF              if quotient negative,
;       NEGATE
;       OVER IF             if remainder nonzero,
;         R@ ROT -  SWAP 1-     adjust rem,quot
;       THEN
;   THEN  R> DROP ;
; Ref. dpANS-6 section 3.2.2.1.
        .drw link
        .set link,*+1
        .db  0,6,"FM/MOD"
FMSLASHMOD: lcall DUP
        lcall TOR
        acall TWODUP
        lcall XOR
        lcall TOR
        lcall TOR
        acall DABS
        lcall RFETCH
        acall ABS
        lcall UMSLASHMOD
        lcall SWOP
        lcall RFROM
        acall QNEGATE
        lcall SWOP
        lcall RFROM
        lcall ZEROLESS
        lcall zerosense
        jz fmmod1
        lcall NEGATE
        lcall OVER
        lcall zerosense
        jz fmmod1
        lcall RFETCH
        lcall ROT
        lcall MINUS
        lcall SWOP
        lcall ONEMINUS
fmmod1: lcall RFROM
        lcall DROP
        ret