; 386-specific fixed point routines.
; Tested with TASM 3.0.
ROUNDING_ON EQU 1     ; 1 for rounding, 0 for no rounding
                      ; No rounding is faster, rounding is more accurate
ALIGNMENT   EQU 1
    .model tpascal
    .386
    .code
;===========================================================================
; Multiplies two fixed-point values together.
; C far-callable as:
;     Fixedpoint FixedMul(Fixedpoint M1, Fixedpoint M2);
;===========================================================================
FMparms struc
    DW  3 dup (?)      ; return address & pushed BP
M1  DD  ?
M2  DD  ?
FMparms ends

PFMparms struc
     DW  3 dup (?)      ; return address & pushed BP
PM1  DD  ?
PM2  DD  ?
PFMparms ends

    align   ALIGNMENT
    public  _FixedMul
_FixedMul      proc far
       PUSH    BP
       MOV     BP,SP
       MOV     EAX,[BP + M1]
       IMUL    DWORD PTR [BP + M2]   ; Multiply
if ROUNDING_ON
       ADD     EAX,8000h             ; Round by adding 2^(-17)
       ADC     EDX,0                 ; Whole part of result is in DX
endif ; ROUNDING_ON
       SHR     EAX,16                ; Put the fractional part in AX
       POP     BP
       RETF
_FixedMul      endp

    align   ALIGNMENT
    public  _PFixedMul
_PFixedMul      proc far
       PUSH    BP
       MOV     BP,SP
       MOV     EAX,[BP + PM1]
       IMUL    DWORD PTR [BP + PM2]   ; Multiply
if ROUNDING_ON
       ADD     EAX,8000h             ; Round by adding 2^(-17)
       ADC     EDX,0                 ; Whole part of result is in DX
endif ; ROUNDING_ON
       SHR     EAX,16                ; Put the fractional part in AX
       POP     BP
       RETF    8
_PFixedMul      endp

;===========================================================================
; Divides one fixed-point value by another.
; C far-callable as:
;     Fixedpoint FixedDiv(Fixedpoint Dividend, Fixedpoint Divisor);
;===========================================================================
FDparms struc
    DW  3 dup (?)      ; return address & pushed BP
Dividend DD ?
Divisor  DD ?
FDparms ends

    align   ALIGNMENT
    public  _FixedDiv
_FixedDiv      proc far
       PUSH    BP
       MOV     BP,SP
if ROUNDING_ON
       SUB     CX,CX                 ; Assume positive result
       MOV     EAX,[BP + Dividend]
       AND     EAX,EAX               ; Positive dividend?
       JNS     FDP1                  ; Yes
       INC     CX                    ; Mark it's a negative dividend
       NEG     EAX                   ; Make the dividend positive
FDP1:  SUB     EDX,EDX               ; Make it a 64-bit dividend, then shift
                                     ;  left 16 bits so that result will be
                                     ;  in EAX
       SHLD    EDX,EAX,16            ; Put whole part of dividend in EDX
       SHL     EAX,16                ; Put fractional part of dividend in
                                     ;  high word of EAX
       MOV     EBX,DWORD PTR [BP + Divisor]
       AND     EBX,EBX               ; Positive divisor?
       JNS     FDP2                  ; Yes
       DEC     CX                    ; Mark it's a negative divisor
       NEG     EBX                   ; Make divisor positive
FDP2:  DIV     EBX                   ; Divide
       SHR     EBX,1                 ; Divisor/2, minus 1 if the divisor is
       ADC     EBX,0                 ;  even
       DEC     EBX
       CMP     EBX,EDX               ; Set Carry if remainder is at least
       ADC     EAX,0                 ;  half as large as the divisor, then
                                     ;  use that to round up if necessary
       AND     CX,CX                 ; Should the result be made negative?
       JZ      FDP3                  ; No
       NEG     EAX                   ; Yes, negate it
FDP3:
else ;!ROUNDING_ON
       MOV     EDX,[BP + Dividend]
       SUB     EAX,EAX
       SHRD    EAX,EDX,16           ; Position so that result ends up in EAX
       SAR     EDX,16
       IDIV    DWORD PTR [BP + Divisor]
endif ;ROUNDING_ON
       SHLD    EDX,EAX,16            ; Whole part of result in DX;
                                     ;  fractional part is already in AX
       POP     BP
       RETF
_FixedDiv      endp

PFDparms struc
    DW  3 dup (?)      ; return address & pushed BP
PDivisor  DD ?
PDividend DD ?
PFDparms ends

    align   ALIGNMENT
    public  _PFixedDiv
_PFixedDiv      proc far
       PUSH    BP
       MOV     BP,SP
if ROUNDING_ON
       SUB     CX,CX                 ; Assume positive result
       MOV     EAX,[BP + PDividend]
       AND     EAX,EAX               ; Positive dividend?
       JNS     PFDP1                 ; Yes
       INC     CX                    ; Mark it's a negative dividend
       NEG     EAX                   ; Make the dividend positive
PFDP1: SUB     EDX,EDX               ; Make it a 64-bit dividend, then shift
                                     ;  left 16 bits so that result will be
                                     ;  in EAX
       SHLD    EDX,EAX,16            ; Put whole part of dividend in EDX
       SHL     EAX,16                ; Put fractional part of dividend in
                                     ;  high word of EAX
       MOV     EBX,DWORD PTR [BP + PDivisor]
       AND     EBX,EBX               ; Positive divisor?
       JNS     PFDP2                 ; Yes
       DEC     CX                    ; Mark it's a negative divisor
       NEG     EBX                   ; Make divisor positive
PFDP2: DIV     EBX                   ; Divide
       SHR     EBX,1                 ; Divisor/2, minus 1 if the divisor is
       ADC     EBX,0                 ;  even
       DEC     EBX
       CMP     EBX,EDX               ; Set Carry if remainder is at least
       ADC     EAX,0                 ;  half as large as the divisor, then
                                     ;  use that to round up if necessary
       AND     CX,CX                 ; Should the result be made negative?
       JZ      PFDP3                 ; No
       NEG     EAX                   ; Yes, negate it
PFDP3:
else ;!ROUNDING_ON
       MOV     EDX,[BP + PDividend]
       SUB     EAX,EAX
       SHRD    EAX,EDX,16           ; Position so that result ends up in EAX
       SAR     EDX,16
       IDIV    DWORD PTR [BP + PDivisor]
endif ;ROUNDING_ON
       SHLD    EDX,EAX,16            ; Whole part of result in DX;
                                     ;  fractional part is already in AX
       POP     BP
       RETF    8
_PFixedDiv      endp
       end
