Re: floating point multiplier
- From: Jerry Coffin <jerryvcoffin@xxxxxxxxxxxxxxxxxxxxxxxxx>
- Date: 01 Oct 2009 15:48:06 GMT
In article <4ac3c768$0$5653$9a6e19ea@xxxxxxxxxxxxxxxxxxxxxxxxx>,
mightycatniyander@xxxxxxxxxxxxxxxxxxxxxxxxx says...
hello,
can any one tell me how a 32-bit single precision floating point
multiplier works.
The simple answer is that it works like a 24-bit integer multiplier
and an 8-bit adder (assuming you split it as an 8-bit exponent and a
24-bit significand).
To get an idea of how that works, let's start with a _really_ simple
(and mostly useless) fixed-point format -- one with only 8 bits, all
after the decimal point. So, for example, we could represent .11
(hex) as 0x11, and .12 (hex) as 0x12. If we multiply those, we get
0x132. Now as we all know, if you multiply two-digit numbers, the
result can be up to four digits. Since this is all after the decimal
point, it means the result is really 0x0132 -- and all four digits
are after the decimal point. If we want to convert back to our
original format, we have to throw away the bottom byte, and keep only
the upper byte.
Now, consider extending that out to 16 bits instead of 8: we start
with 0x1100 and 0x1200, and our result is 0x01320000. This time to
get back to the original result, we keep the top 16 bits, and throw
away the bottom 16 (see a pattern starting to emerge?)
Now, let's think of a really simple floating point format: we'll take
that 16-bit number (still all after the decimal point), but add an 8-
bit exponent, so (for example) a value of 1 become a significand of
0x1000 (0.1 hex) and an exponent of 1. To multiply these numbers, we
have to multiply the significands, just like before, but also add the
exponents. We get the correct result, but instead of being .1 with an
exponent of 1, we get .01 with an exponent of 2. That's fine except
for one minor detail -- though it doesn't happen in this case, we
could lose some accuracy. If we kept multiplying this way, we'd keep
getting smaller and smaller significands with larger and large
exponents, until at some point our significand underflowed, and
(since in this case it has only one bit set) we were left with a
significand of 0.
We clearly don't want that; the cure is to normalize the number after
each operation -- shift the significand left until there's a digit in
the left-most position (and with each shift, subtract one from the
exponent, of course). Only after we've done that do we discard the
bottom bits of the result.
To implement something like that in assembly language, we could write
code something like this:
; warning: really just pseudo-code -- not tested.
..586P
..model flat, c
fp_num struc
significand dw ?
exponent db ?
fp_num ends
..data
a fp_num {01000h, 01h}
b fp_num {01000h, 01h}
res fp_num {0h, 0h}
..code
fp_mul proc
mov ebx, offset FLAT:a
mov esi, offset FLAT:b
mov edi, offset FLAT:res
assume ebx:ptr fp_num
assume esi:ptr fp_num
assume edi:ptr fp_num
movzx eax, [esi].significand
movzx ebx, [edi].significand
imul eax, ebx
mov cl, [esi].exponent
mov ch, [ebx].exponent
add cl, ch
normalize:
test eax, 0f0000000h
jnz done
shl eax, 4
dec cl
jmp normalize
done:
shr eax, 16
mov [edi].significand, ax
mov [edi].exponent, cl
ret
fp_mul endp
end
Now, as it stands this is hexadecimal floating point -- i.e. a '1' in
the exponent means one hexadecimal digit. I used that mostly because
it's easy when you're writing the numbers in hexadecimal. In reality,
you probably want to use binary floating point like everybody else
(though IBM mainframes have done hexadecimal FP for decades). As far
as the code above goes, about all that means is that when you're
normalizing you only shift left one bit at a time instead of four,
and you test for a value of 80000000h instead of 0f0000000h. Of
course, if you intended to really use this, you'd probably use BSR to
find how many bits of shifting were needed to normalize, and subtract
that from 16 to get the number of bits to shift right before storing
(i.e. combine the left shifts to normalize and the right shift prior
to storage into a single shift of the right distance). I haven't
messed with that, because in hardware you won't do it -- the final
"right shift" will be just a matter of connecting bits 47 downto 24
as your outputs.
There is one extra trick with binary though: since you're always
shifting left until there's a one in the MSB, you can take that MSB
for granted -- when you store your FP number, you don't bother
storing that bit, since it's always a 1. When you're going to operate
on a number, you hardwire the MSB to a one, and load the bits that
were stored into the bits below that.
Since you're working on FP multiplication you presumably already know
how to do integer multiplication (though, of course, I could be wrong
about that -- if so, just ask).
--
Later,
Jerry.
.
- Prev by Date: Re: floating point multiplier
- Next by Date: Converting goasm code to masm
- Previous by thread: Re: floating point multiplier
- Next by thread: Re: stack clobbering
- Index(es):
Relevant Pages
|