为什么用这种方式进行乘法运算

Why perform multiplication in this way?

本文关键字:运算 方式进 为什么      更新时间:2023-10-16

我遇到了这个函数:

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

内联评论是我的。似乎它所做的只是将这两个论点相乘。这是对的吗,还是还有更多?为什么要这样做?

这些参数不代表整数。它们以定点格式表示实数,基点右侧有15位。例如,1.0由1<lt;15=0x8000,0.5表示0x4000,-0.5表示0xC000(或32位中的0xFFFFC000)。

添加定点数字很简单,因为您只需添加它们的整数表示即可。但是,如果你想相乘,你首先必须将它们作为整数相乘,但基数点右侧的位数是基数点右侧位数的两倍,所以你必须通过移位来丢弃多余的位数。例如,如果您想以32位格式将0.5本身相乘,请将0x00004000(1<<14)本身相乘,得到0x10000000(1<<28),然后右移15位,得到0x00002000(1><13)。为了获得更好的精度,当您丢弃最低的15位时,您希望四舍五入到最接近的数字,而不是向下四舍五舍五入。您可以通过添加0x4000=1<lt;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位中进行两次乘法运算,并组合结果。

这实现了定点数的乘法运算。这些数字被视为Q15格式(小数部分有15位)。

从数学上讲,该函数计算四舍五入到最接近的整数的(o16 * o32) / 2^15(因此,表示1/22^14因子被添加到一个数字上以进行四舍五舍五入)。它使用32位结果的无符号和有符号16位乘法,这可能是指令集所支持的。

请注意,存在一个角情况,其中每个数字都有一个最小值(-2^15和-2^31);在这种情况下,结果(2^31)在输出中是不可表示的,并且被包裹起来(变为-2^31)。对于o16o32的所有其他组合,结果是正确的。