Зачем выполнять умножение таким образом?

Я столкнулся с этой функцией:

static inline INT32 MPY48SR(INT16 o16, INT32 o32)
{
UINT32   Temp0;
INT32    Temp1;
// A1. get the lower 16 bits of the 32-bit param
// A2. multiply them with the 16-bit param
// A3. add 16384 (TODO: why?)
// A4. bitshift to the right by 15 (TODO: why 15?)
Temp0 = (((UINT16)o32 * o16) + 0x4000) >> 15;
// B1. Get the higher 16 bits of the 32-bit param
// B2. Multiply them with the 16-bit param
Temp1 = (INT16)(o32 >> 16) * o16;
// 1. Shift B to the left (TODO: why do this?)
// 2. Combine with A and return
return (Temp1 << 1) + Temp0;
}

Встроенные комментарии мои. Кажется, что все, что он делает, умножает два аргумента. Это правильно, или есть что-то еще? Зачем это нужно делать таким образом?

9

Решение

Эти параметры не представляют целые числа. Они представляют собой реальные числа в фиксированная точка формат с 15 битами справа от точки ось. Например, 1.0 представлен 1 << 15 = 0x8000, 0,5 — 0x4000, -0,5 — 0xC000 (или 0xFFFFC000 в 32 битах).

Добавить числа с фиксированной запятой просто, потому что вы можете просто добавить их целочисленное представление. Но если вы хотите умножить, вы сначала должны умножить их на целые числа, но затем у вас будет в два раза больше битов справа от точки отсчета, поэтому вы должны отбросить излишки путем сдвига. Например, если вы хотите умножить 0,5 на себя в 32-битном формате, вы умножаете 0x00004000 (1 << 14) само по себе, чтобы получить 0x10000000 (1 << 28), затем сдвиньте вправо на 15 бит, чтобы получить 0x00002000 (1 << 13). Чтобы получить более высокую точность, когда вы отбрасываете младшие 15 бит, вы хотите округлить до ближайшего числа, а не округлить вниз. Вы можете сделать это, добавив 0x4000 = 1 << 14. Затем, если отброшенные 15 битов меньше 0x4000, он округляется в меньшую сторону, а если 0x4000 или более, он округляется в большую сторону.

 (0x3FFF + 0x4000) >> 15 = 0x7FFF >> 15 = 0
(0x4000 + 0x4000) >> 15 = 0x8000 >> 15 = 1

Подводя итог, вы можете сделать умножение следующим образом:

 return (o32 * o16 + 0x4000) >> 15;

Но есть проблема. В C ++ результат умножения имеет тот же тип, что и его операнды. Так o16 продвигается до того же размера, что и o32затем они умножаются, чтобы получить 32-битный результат. Но это отбрасывает верхние биты, потому что продукт требует 16 + 32 = 48 бит для точного представления. Один из способов сделать это — привести операнды к 64 битам, а затем умножить, но это может быть медленнее, и это поддерживается не на всех машинах. Так что вместо этого он ломается o32 на две 16-битные части, затем выполняет два умножения на 32-битные и объединяет результаты.

15

Другие решения

Это реализует умножение числа с фиксированной запятой. Числа рассматриваются как имеющие формат Q15 (имеющий 15 бит в дробной части).

Математически эта функция вычисляет (o16 * o32) / 2^15округляется до ближайшего целого числа (отсюда 2^14 фактор, который представляет 1/2, добавленный к числу, чтобы округлить его). Он использует 16-разрядные умножения без знака и со знаком с 32-разрядным результатом, которые предположительно поддерживаются набором команд.

Обратите внимание, что существует угловой случай, где каждое из чисел имеет минимальное значение (-2 ^ 15 и -2 ^ 31); в этом случае результат (2 ^ 31) не может быть представлен в выходных данных и оборачивается (вместо него становится -2 ^ 31). Для всех других комбинаций o16 а также o32, результат правильный.

5

По вопросам рекламы [email protected]