2012-10-12 2 views
9

Я бежал в эту функцию:Зачем выполнять умножение таким образом?

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; 
} 

инлайна комментарии мои. Кажется, что все, что он делает, - это умножение двух аргументов. Правильно ли это, или есть еще больше? Почему это должно быть сделано таким образом?

+4

Похоже, что это было написано на платформе, в которой нет встроенных 32-битных множителей. – Mysticial

+0

Это должно быть какое-то специализированное оборудование, верно? Будет ли работа с прямым умножением работать на обычных ПК? – misha

+0

@ user1708860: попробуйте googling имя функции. Это не домашнее задание. – misha

ответ

15

Эти параметры не представляют собой целые числа. Они представляют действительные числа в формате с фиксированной точкой с 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 битах и ​​объединяет результаты.

5

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

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

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

+0

И вы можете исправить краевой регистр, используя арифметику без знака после вычисления знака результата и принятия абсолютного значения операндов. –

+0

@JonathanLeffler При этом абсолютное значение будет переполняться в этом случае. Крайный край лучше фиксируется простой проверкой (если результат = -2^31, установите результат на + 2^31-1). Тем не менее, это добавляет по крайней мере 1 инструкцию для времени выполнения, и в случае, когда вы достаточно осторожны, угловой случай никогда не появляется, поэтому его лучше игнорировать. – anatolyg

Смежные вопросы