The answers to this SO question are pretty comprehensive in terms of implementation. Here's a bit more of an explanation than I saw over there:
One approach is to force all your numbers into a range, say [-1.0,1.0). Then you map those numbers into the range [-2^15,(2^15)-1]. For instance,
Half = round(0.5*32768); //16384
Third = round((1.0/3.0)*32768); //10923
When you multiply these two numbers you get
Temp = Half*Third; //178962432
Result = Temp/32768; //5461 = round(1.0/6.0)*32768
Dividing by 32768 in the last line is the point Patros made about multiplies needing an extra scaling step. This makes more sense if you write the 2^N scaling explicitly:
x1 = x1Float*(2^15);
x2 = x2Float*(2^15);
Temp = x1Float*x2Float*(2^15)*(2^15);
Result = Temp/(2^15); //get back to 2^N scaling
So that's the arithmetic. For the implementation, note that the multiply of two 16-bit integers needs a 32-bit result, so Temp should be 32-bit. Also, 32768 isn't representable in a 16-bit variable, so be aware that the compiler will make 32-bit immediates. And as you've already noted, you can shift to multiply/divide by powers of 2 so you can write
N = 15;
SInt16 x1 = round(x1Float * (1 << N));
SInt16 x2 = round(x2Float * (1 << N));
SInt32 Temp = x1*x2;
Result = (SInt16)(Temp >> N);
FloatResult = ((double)Result)/(1 << N);
But suppose [-1,1) isn't the right range? If you'd rather limit your numbers to, say, [-4.0,4.0), you can use N = 13. Then you have 1 sign bit, two bits before the binary point, and 13 after. These are called 1.15 and 3.13 fixed point fractional types respectively. You trade precision in the fraction for headroom.
Adding and subtracting fractional types works fine as long as you look out for saturation. For divide, as Patros said, the scaling actually cancels out. So you have to do
Quotient = (x1/x2) << N;
or, to preserve precision
Quotient = (SInt16)(((SInt32)x1 << N)/x2); //x1 << N needs wide storage
Multiplying and dividing by whole numbers works normally. For instance, to divide by 6 you can simply write
Quotient = x1/6; //equivalent to x1Float*(2^15)/6, stays scaled
And in the case of dividing by a power of 2,
Quotient = x1 >> 3; //divides by 8, can't do x1 << -3 as Patros pointed out
Adding and subtracting whole numbers, though, doesn't work naively. You have to first see if the integer fits in your x.y type, make the equivalent fractional type, and proceed.
I hope this helps with the idea, look at the code in that other question for clean implementations.