[
Index] [
Previous] [
Next]
2.4 Multiplication & Division
blah
FMul
Multiplying two floating point numbers is theoretically simple. All we have to do is add the exponents, multiply the mantissas, and normalise the result. The only problem is that the 8080 didn't have a MUL instruction. Therefore the fundamental logic of multiplication (shift and add) is done by hand in this function. FMul's logic read something like this :
- Get lhs and rhs. Exit if rhs=0.
- Add lhs and rhs exponents
- Initialise result mantissa to 0.
- Get rightmost bit of rhs.
- If this bit is set then add the lhs mantissa to the result mantissa.
- Shift result mantissa right one bit.
- Get next bit of rhs mantissa. If not done all 24 bits, loop back to 5.
- Jump to FNormalise
Alternatively, here's some C++ pseudo-code :
float FMul(float lhs, float rhs)
{
float result = 0;
for (int bit=0 ; bit<24 ; bit++) {
if (lhs.mantissa & (2^bit)) {
result.mantissa += rhs.mantissa;
}
result.mantissa>>=1;
}
return FNormalise(result);
}
(fixme: Show why this works)
08E3
C1
FMul
POP B
Get lhs in BCDE
08E4
D1
POP D
08E6
C8
RZ
Add the exponents.
08E7
2E00
MVI L,00
L=0 to signify exponent add
Store the lhs mantissa in the operands for FMulInnerLoop
08EC
79
MOV A,C
08F0
EB
XCHG
Initialise result mantissa CDEB to 0.
08F4
010000
LXI B,0000
08F7
50
MOV D,B
08F8
58
MOV E,B
Set return address to FNormalise
08FC
E5
PUSH H
A great trick! FMul's outer loop works on one byte of multiplicand at a time. There are three bytes to do, so by pushing the address of the loop onto the stack twice, we can cheaply run the loop 3 times without needing a counter.
0900
E5
PUSH H
0901
E5
PUSH H
0905
7E
FMulOuterLoop
MOV A,M
A=FACCUM mantissa byte
0906
23
INX H
0907
E5
PUSH H
Preserve FACCUM ptr
0908
2E08
MVI L,08
8 bits to do
Inner loop processes a single byte of multiplicand.
090A
1F
FMulInnerLoop
RAR
Test lowest bit of mantissa byte
090B
67
MOV H,A
Preserve mantissa byte
090C
79
MOV A,C
A=result mantissa's high byte
090D
D21909
JNC 0919
If that bit of multiplicand was 0, then skip over adding mantissas.
Add the lhs mantissa to the result mantissa
0910
E5
PUSH H
0911
210000
LXI H,0000
0914
19
DAD D
0915
D1
POP D
0916
CE00
ACI 00
A=result mantissa high byte. This gets back to C
0918
EB
XCHG
in the call to FMantissaRtOnce+1.
Shift result mantissa right and loop back to inner loop if we haven't done all 8 bits yet.
091C
2D
DCR L
091D
7C
MOV A,H
Restore mantissa byte and
0921
E1
PopHLandReturn
POP H
Restore FACCUM ptr
0922
C9
RET
Return to FMulOuterLoop, or if finished that then exit to FNormalise
FDivByTen
Divides FACCUM by 10. Used in FOut to bring the number into range before printing.
0926
012084
LXI B,8420
BCDE=(float)10;
0929
110000
LXI D,0000
FDiv
fixme: work out how this works!
Get lhs into BCDE.
092F
C1
FDiv
POP B
0930
D1
POP D
If rhs is zero, then divide-by-zero error.
Subtract exponents.
0935
2EFF
MVI L,FF
Multiply FACCUM by 4, the easy way.
093A
34
INR M
093B
34
INR M
Decrement HL so it points to most-significant byte of FACCUM's mantissa.
093C
2B
DCX H
Copy FACCUM's mantissa to places in FDivLoop.
093D
7E
MOV A,M
093E
326009
STA 0960
0941
2B
DCX H
0942
7E
MOV A,M
0943
325C09
STA 095C
0946
2B
DCX H
0947
7E
MOV A,M
0948
325809
STA 0958
Load B with most significant byte of lhs mantissa, and load HL with 0 (DE was zeroed previously)
094B
41
MOV B,C
094C
EB
XCHG
Initialise A and CDE to 0.
094D
AF
XRA A
094E
4F
MOV C,A
094F
57
MOV D,A
0950
5F
MOV E,A
0951
326309
STA 0963
Long division loop.
0954
E5
FDivLoop
PUSH H
0955
C5
PUSH B
0956
7D
MOV A,L
0957
D600
SUI 00
0959
6F
MOV L,A
095A
7C
MOV A,H
095B
DE00
SBI 00
095D
67
MOV H,A
095E
78
MOV A,B
095F
DE00
SBI 00
0961
47
MOV B,A
0962
3E00
MVI A,00
0964
DE00
SBI 00
0966
3F
CMC
0967
D27109
JNC 0971
096A
326309
STA 0963
096D
F1
POP PSW
096E
F1
POP PSW
LXI over the restoration of ?? ?
096F
37
STC
0970
D2....
JNC ....
0971
C1
POP B
0973
79
MOV A,C
0974
3C
INR A
0975
3D
DCR A
0976
1F
RAR
097A
17
RAL
DIVTEMP *= 2
097E
29
DAD H
097F
78
MOV A,B
0980
17
RAL
0981
47
MOV B,A
0982
3A6309
LDA 0963
0985
17
RAL
0986
326309
STA 0963
If CDE is not zero yet, then continue the long division loop.
0989
79
MOV A,C
098A
B2
ORA D
098B
B3
ORA E
Finally divide FACCUM by 2 and loop back unless overflowed.
098F
E5
PUSH H
0993
35
DCR M
0994
E1
POP H
FExponentAdd
Here is code common to FMul and FDiv and is called by both of them. It's main job is to add (for FMul) or subtract (for FDiv) the binary exponents of the lhs and rhs arguments, for which on entry L=0 for addition or L=FF respectively.
If BCDE is 0, then we don't need to do anything and can jump to the function exit.
099B
78
FExponentAdd
MOV A,B
099C
B7
ORA A
Exponent arithmetic.
09A0
7D
MOV A,L
A=0 for add, FF for subtract.
09A4
AE
XRA M
XOR with FAccum's exponent.
09A5
80
ADD B
Add exponents
09A6
47
MOV B,A
09A7
1F
RAR
Carry (after the add) into bit 7.
09A8
A8
XRA B
XOR with old bit 7.
09A9
78
MOV A,B
Add exponent bias, store in FACCUM+3 and if the result is 0 then discard return address and return to caller's caller.
09AD
C680
ADI 80
09AF
77
MOV M,A
09B6
77
MOV M,A
09B7
2B
DCX H
09B8
C9
RET
09B9
B7
ORA A
09BA
E1
POP H
Ignore return address so we'll end
09BB
FAA408
JM
Overflow
up to returning to caller's caller. And fall into FZero...
FZero
Sets FACCUM to zero. Zero is stored in a slightly special way : the exponent is zero'ed without bias.
09BE
AF
FZero
XRA A
09C2
C9
RET
FMulByTen
Multiplies FACCUM by 10. Seems to be here for speed reasons, since this could be done very simply with a call to FMul.
Copy FACCUM to BCDE and return if it's 0.
09C6
78
MOV A,B
09C7
B7
ORA A
09C8
C8
RZ
Multiply BCDE by 4. This is done by adding 2 to BCDE's exponent and erroring out if it overflows (ie exponent > 127 plus bias).
09C9
C602
ADI 02
09CE
47
MOV B,A
Add BCDE to FACCUM. So FACCUM is now the old FACCUM times 5.
Multiply FACCUM by 2, done by incrementing the exponent. Now we have multiplied FACCUM by 10 and can exit, but notice that we also test for exponent overflow again.
09D5
34
INR M
09D6
C0
RNZ
[Index] [Previous] [Next]