Ответы на этот вопрос довольно полны с точки зрения реализации. Вот немного больше объяснения, чем я видел там:
Один из подходов состоит в том, чтобы ввести все ваши числа в диапазон, скажем, [-1.0,1.0). Затем вы отображаете эти числа в диапазоне [-2 ^ 15, (2 ^ 15) -1]. Например,
Half = round(0.5*32768); //16384
Third = round((1.0/3.0)*32768); //10923
Когда вы умножаете эти два числа, вы получаете
Temp = Half*Third; //178962432
Result = Temp/32768; //5461 = round(1.0/6.0)*32768
В последней строке делится на 32768 - это точка Patros , рассчитанная для умножений, нуждающихся в дополнительном шаге масштабирования. Это имеет больше смысла, если вы напишите масштабирование 2 ^ N явно:
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
Так что это арифметика. Для реализации обратите внимание, что умножение двух 16-разрядных целых чисел требует 32-разрядного результата, поэтому Temp должен быть 32-разрядным. Кроме того, 32768 не может быть представлено в 16-битной переменной, поэтому следует помнить, что компилятор будет выполнять 32-битные операции немедленно. И, как вы уже заметили, вы можете перейти к умножению / делению на степени 2, чтобы вы могли написать
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);
Но предположим, что [-1,1) не правильный диапазон? Если вы предпочитаете ограничить свои числа, скажем, [-4.0,4.0), вы можете использовать N = 13. Тогда у вас есть 1 знаковый бит, два бита перед двоичной точкой и 13 после. Они называются 1.15 и 3.13 дробными типами с фиксированной точкой соответственно. Вы торгуете точностью во фракции для запаса.
Сложение и вычитание дробных типов работает нормально, если вы смотрите на насыщенность. Для разделения, как сказал Патрос, масштабирование фактически сводится на нет. Так что вы должны сделать
Quotient = (x1/x2) << N;
или, чтобы сохранить точность
Quotient = (SInt16)(((SInt32)x1 << N)/x2); //x1 << N needs wide storage
Умножение и деление на целые числа работает нормально. Например, чтобы разделить на 6, вы можете просто написать
Quotient = x1/6; //equivalent to x1Float*(2^15)/6, stays scaled
А в случае деления на степень 2,
Quotient = x1 >> 3; //divides by 8, can't do x1 << -3 as Patros pointed out
Однако сложение и вычитание целых чисел не работает наивно. Сначала вы должны проверить, подходит ли целое число вашему типу x.y, сделать эквивалентный дробный тип и продолжить.
Я надеюсь, что это поможет с идеей, посмотрите код в этом другом вопросе для чистых реализаций.