; Fixed point routines.
; Tested with TASM 3.0.
USE386 equ 1 ;1 for 386-specific opcodes, 0 for
; 8088 opcodes
MUL_ROUNDING_ON equ 0 ;1 for rounding on multiplies,
; 0 for no rounding. Not rounding is faster,
; rounding is more accurate and generally a
; good idea
DIV_ROUNDING_ON equ 0 ;1 for rounding on divides,
; 0 for no rounding. Not rounding is faster,
; rounding is more accurate, but because
; division is only performed to project to
; the screen, rounding quotients generally
; isn't necessary
ALIGNMENT equ 2
.model large
.386
.code
;=====================================================================
; Multiplies two fixed-point values together.
; C near-callable as:
; Fixedpoint FixedMul(Fixedpoint M1, Fixedpoint M2);
FMparms struc
dd
dw ? ;return address & pushed BP
M1 dd ?
M2 dd ?
FMparms ends
align ALIGNMENT
public _FixedMul
_FixedMul proc far
push bp
mov bp,sp
if USE386
mov eax,[bp+M1]
imul dword ptr [bp+M2] ;multiply
if MUL_ROUNDING_ON
add eax,8000h ;round by adding 2^(-17)
adc edx,0 ;whole part of result is in DX
endif ;MUL_ROUNDING_ON
shr eax,16 ;put the fractional part in AX
else ;!USE386
;do four partial products and
; add them together, accumulating
; the result in CX:BX
push si ;preserve C register variables
push di
;figure out signs, so we can use
; unsigned multiplies
sub cx,cx ;assume both operands positive
mov ax,word ptr [bp+M1+2]
mov si,word ptr [bp+M1]
and ax,ax ;first operand negative?
jns CheckSecondOperand ;no
neg ax ;yes, so negate first operand
neg si
sbb ax,0
inc cx ;mark that first operand is negative
CheckSecondOperand:
mov bx,word ptr [bp+M2+2]
mov di,word ptr [bp+M2]
and bx,bx ;second operand negative?
jns SaveSignStatus ;no
neg bx ;yes, so negate second operand
neg di
sbb bx,0
xor cx,1 ;mark that second operand is negative
SaveSignStatus:
push cx ;remember sign of result; 1 if result
; negative, 0 if result nonnegative
push ax ;remember high word of M1
mul bx ;high word M1 times high word M2
mov cx,ax ;accumulate result in CX:BX (BX not used
; until next operation, however)
;assume no overflow into DX
mov ax,si ;low word M1 times high word M2
mul bx
mov bx,ax
add cx,dx ;accumulate result in CX:BX
pop ax ;retrieve high word of M1
mul di ;high word M1 times low word M2
add bx,ax
adc cx,dx ;accumulate result in CX:BX
mov ax,si ;low word M1 times low word M2
mul di
if MUL_ROUNDING_ON
add ax,8000h ;round by adding 2^(-17)
adc bx,dx
else ;!MUL_ROUNDING_ON
add bx,dx ;don't round
endif ;MUL_ROUNDING_ON
adc cx,0 ;accumulate result in CX:BX
mov dx,cx
mov ax,bx
pop cx
and cx,cx ;is the result negative?
jz FixedMulDone ;no, we're all set
neg dx ;yes, so negate DX:AX
neg ax
sbb dx,0
FixedMulDone:
pop di ;restore C register variables
pop si
endif ;USE386
pop bp
ret
_FixedMul endp
;=====================================================================
; Divides one fixed-point value by another.
; C near-callable as:
; Fixedpoint FixedDiv(Fixedpoint Dividend, Fixedpoint Divisor);
FDparms struc
dd
dw ? ;return address & pushed BP
Dividend dd ?
Divisor dd ?
FDparms ends
align ALIGNMENT
public _FixedDiv
_FixedDiv proc far
push bp
mov bp,sp
if USE386
if DIV_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
rol eax,16 ;put fractional part of dividend in
; high word of EAX
mov dx,ax ;put whole part of dividend in DX
sub ax,ax ;clear low 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 the 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 ;!DIV_ROUNDING_ON
mov edx,[bp+Dividend]
sub eax,eax
shrd eax,edx,16 ;position so that result ends up
sar edx,16 ; in EAX
idiv dword ptr [bp+Divisor]
endif ;DIV_ROUNDING_ON
shld edx,eax,16 ;whole part of result in DX;
; fractional part is already in AX
else ;!USE386
;NOTE!!! Non-386 division uses a 32-bit dividend but only the upper 16 bits
; of the divisor; in other words, only the integer part of the divisor is
; used. This is done so that the division can be accomplished with two fast
; hardware divides instead of a slow software implementation, and is (in my
; opinion) acceptable because division is only used to project points to the
; screen (normally, the divisor is a Z coordinate), so there's no cumulative
; error, although there will be some error in pixel placement (the magnitude
; of the error is less the farther away from the Z=0 plane objects are). This
; is *not* a general-purpose divide, though; if the divisor is less than 1,
; for instance, a divide-by-zero error will result! For this reason, non-386
; projection can't be performed for points closer to the viewpoint than Z=1.
;figure out signs, so we can use
; unsigned divisions
sub cx,cx ;assume both operands positive
mov ax,word ptr [bp+Dividend+2]
and ax,ax ;first operand negative?
jns CheckSecondOperandD ;no
neg ax ;yes, so negate first operand
neg word ptr [bp+Dividend]
sbb ax,0
inc cx ;mark that first operand is negative
CheckSecondOperandD:
mov bx,word ptr [bp+Divisor+2]
and bx,bx ;second operand negative?
jns SaveSignStatusD ;no
neg bx ;yes, so negate second operand
neg word ptr [bp+Divisor]
sbb bx,0
xor cx,1 ;mark that second operand is negative
SaveSignStatusD:
push cx ;remember sign of result; 1 if result
; negative, 0 if result nonnegative
sub dx,dx ;put Dividend+2 (integer part) in DX:AX
div bx ;first half of 32/16 division, integer part
; divided by integer part
mov cx,ax ;set aside integer part of result
mov ax,word ptr [bp+Dividend] ;concatenate the fractional part of
; the dividend to the remainder (fractional
; part) of the result from dividing the
; integer part of the dividend
div bx ;second half of 32/16 division
if DIV_ROUNDING_ON EQ 0
shr bx,1 ;divisor/2, minus 1 if the divisor is
adc bx,0 ; even
dec bx
cmp bx,dx ;set Carry if the remainder is at least
adc ax,0 ; half as large as the divisor, then
adc cx,0 ; use that to round up if necessary
endif ;DIV_ROUNDING_ON
mov dx,cx ;absolute value of result in DX:AX
pop cx
and cx,cx ;is the result negative?
jz FixedDivDone ;no, we're all set
neg dx ;yes, so negate DX:AX
neg ax
sbb dx,0
FixedDivDone:
endif ;USE386
pop bp
ret
_FixedDiv endp
end