blah
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 :
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 | ||
08E5 | EF | RST FTestSign | If rhs==0 then exit | |
08E6 | C8 | RZ | ||
Add the exponents. | ||||
08E7 | 2E00 | MVI L,00 | L=0 to signify exponent add | |
08E9 | CD9B09 | CALL FExponentAdd | ||
Store the lhs mantissa in the operands for FMulInnerLoop | ||||
08EC | 79 | MOV A,C | ||
08ED | 321709 | STA FMulInnerLoop+13 | ||
08F0 | EB | XCHG | ||
08F1 | 221209 | SHLD FMulInnerLoop+8 | ||
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 | ||||
08F9 | 215E08 | LXI H,FNormalise+3 | ||
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. | ||||
08FD | 210509 | LXI H,FMulOuterLoop | ||
0900 | E5 | PUSH H | ||
0901 | E5 | PUSH H | ||
0902 | 216F01 | LXI H,FACCUM | ||
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. | ||||
0919 | CDD708 | CALL FMantissaRtOnce+1 | ||
091C | 2D | DCR L | ||
091D | 7C | MOV A,H | Restore mantissa byte and | |
091E | C20A09 | JNZ FMulInnerLoop | jump back if L is not yet 0. | |
0921 | E1 | PopHLandReturn | POP H | Restore FACCUM ptr |
0922 | C9 | RET | Return to FMulOuterLoop, or if finished that then exit to FNormalise |
Divides FACCUM by 10. Used in FOut to bring the number into range before printing.
0923 | CD020A | CALL FPush | ||
0926 | 012084 | LXI B,8420 | BCDE=(float)10; | |
0929 | 110000 | LXI D,0000 | ||
092C | CD120A | CALL FLoadFromBCDE |
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. | ||||
0931 | EF | RST FTestSign | ||
0932 | CAD301 | JZ DivideByZero | ||
Subtract exponents. | ||||
0935 | 2EFF | MVI L,FF | ||
0937 | CD9B09 | CALL FExponentAdd | ||
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 | ||
0972 |
E1 | POP H | ||
0973 | 79 | MOV A,C | ||
0974 | 3C | INR A | ||
0975 | 3D | DCR A | ||
0976 | 1F | RAR | ||
0977 | FA7F08 | JM FRoundUp+1 | ||
097A | 17 | RAL | ||
097B | CD9008 | CALL FMantissaLeft | ||
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 | ||
098C | C25409 | JNZ FDivLoop | ||
Finally divide FACCUM by 2 and loop back unless overflowed. | ||||
098F | E5 | PUSH H | ||
0990 | 217201 | LXI H,FACCUM+3 | ||
0993 | 35 | DCR M | ||
0994 | E1 | POP H | ||
0995 | C25409 | JNZ FDivLoop | ||
0998 | C3A408 | JMP Overflow |
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 | ||
099D | CABA09 | JZ FExponentAdd+31 | ||
Exponent arithmetic. | ||||
09A0 | 7D | MOV A,L | A=0 for add, FF for subtract. | |
09A1 | 217201 | LXI H,FACCUM+3 | ||
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 | ||
09AA | F2B909 | JP FExponentAdd+30 | If | |
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 | ||
09B0 | CA2109 | JZ PopHLandReturn | ||
09B3 | CD370A | CALL FUnpackMantissas | ||
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... |
Sets FACCUM to zero. Zero is stored in a slightly special way : the exponent is zero'ed without bias.
09BE | AF | FZero | XRA A | |
09BF | 327201 | STA FACCUM+3 | ||
09C2 | C9 | RET |
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. | ||||
09C3 | CD1D0A | FMulByTen | CALL FCopyToBCDE | |
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 | ||
09CB | DAA408 | JC Overflow | ||
09CE | 47 | MOV B,A | ||
Add BCDE to FACCUM. So FACCUM is now the old FACCUM times 5. | ||||
09CF | CD1208 | CALL FAdd+2 | ||
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. | ||||
09D2 | 217201 | LXI H,FACCUM+3 | ||
09D5 | 34 | INR M | ||
09D6 | C0 | RNZ | ||
09D7 | C3A408 | JMP Overflow |