[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 :

  1. Get lhs and rhs. Exit if rhs=0.
  2. Add lhs and rhs exponents
  3. Initialise result mantissa to 0.
  4. Get rightmost bit of rhs.
  5. If this bit is set then add the lhs mantissa to the result mantissa.
  6. Shift result mantissa right one bit.
  7. Get next bit of rhs mantissa. If not done all 24 bits, loop back to 5.
  8. 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
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

 

FDivByTen

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

 

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.
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

 

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
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...

 

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

 

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.
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

 


[Index] [Previous] [Next]