One mathematical approach would be to build a matrix representation for the multiplication algorithm.
As we know from elementary school when multiplying numbers of different digits, we can do this as a convolution. For example:
$$123*234 = 3*234 + 20*234 + 100*234$$
Each digit in first number multiplied by the other number. We can determine exactly the position of the resulting products.
Now let's try and treat the range for a floating point number the same way as a "digit".
With matrices we represent convolution with Toeplitz matrices:
$${\bf M_1} = \left[\begin{array}{ccccc}3&2&1&0&0\\0&3&2&1&0\\0&0&3&2&1\\0&0&0&3&2\\0&0&0&0&3\end{array}\right], {\bf M_2} = \left[\begin{array}{ccccc}4&3&2&0&0\\0&4&3&2&0\\0&0&4&3&2\\0&0&0&4&3\\0&0&0&0&4\end{array}\right]$$
Now $$ {\bf M_1M_2} = \left[\begin{array}{ccccc}12&17&16&7&2\\0&12&17&16&7\\0&0&12&17&16\\0&0&0&12&17\\0&0&0&0&12
\end{array}\right]$$
We see we get values larger than 9, and that's when a carry digit comes into play. We can implement carry as an exercise and check with the real result $123\times 234 = 28782$.
Now all we need to do is translate all of this into double precision arithmetics.
EDIT: Implementing this in octave for decimal digits, and trying out the following calculations:
$$3^{32} \cdot 3 \textrm{ ( no error ) }\\3^{33} \cdot 3 \textrm{ ( error last digit )}$$
This is reasonable because: $$\log_2(3^{33}) = 52.3\\\log_2(3^{34}) = 53.9$$
And the mantissa of a double precision number is just about 54 bits and the number 3 has a rather nasty representation in the binary base.
Here the numbers are:
$$3^{33} : 5 5 5 9 0 6 0 5 6 6 5 5 5 5 2 3\\
3^{34} : 1 6 6 7 7 1 8 1 6 9 9 6 6 6 5 6 (9/\color{red}{8})$$
So the first time a digit differs it is by 1 from 9 to 8. And our approach gets the correct digit.