C语言 乘法前后的字宽

zte4gxcn  于 5个月前  发布在  其他
关注(0)|答案(2)|浏览(82)

我不知道之前是否有人问过这个问题,这个问题背后的根源是标准C或C++中的错误(在我看来),即整数乘积相乘后的隐式字宽与整数乘数和被乘数的字宽中的较大者相同。
所以,如果在C中,int32_t乘以int32_t会隐式地产生int64_t,这不会是一个问题。但事实并非如此。我所知道的几乎每个整数CPU和定点DSP都将两个N位操作数相乘到一个2N位宽的寄存器(或寄存器对)中,我希望C能做到这一点(没有某种内联汇编)。
现在我知道这是可行的(这是一个简单的定点直接形式1双二阶滤波器,在唯一的量化点周围保存分数):

int64_t accumulator;

int64_t a1, a2, b0, b1, b2;   /* 2nd-order IIR coefficients */

int32_t state_x1, state_x2, state_y1, state_y2, state_fraction;

int32_t x, y;

int32_t get_input(void);

void put_output(int32_t y);

int i, num_samples;
         /* num_samples is the number of samples per block */

/*  load up your coefficients, cast all to int64_t */

/*  load up your states from wherever */

accumulator = (int64_t)state_fraction;

for (i=0; i<num_samples; i++)
    {
    x = get_input();

    accumulator += b0*x;
    accumulator += b1*state_x1;
    accumulator += b2*state_x2;
    accumulator += a1*state_y1;
    accumulator += a2*state_y2;

    if (accumulator > 0x1FFFFFFFFFFFFFFF)
       {
       accumulator = 0x1FFFFFFFFFFFFFFF;     /* clip value */
       }
    if (accumulator < -0x2000000000000000)
       {
       accumulator = -0x2000000000000000;    /* clip value */
       }

    y =  (int32_t)(accumulator>>30);   /* always rounding down */

    state_x2 = state_x1;             /* bump the states over */
    state_x1 = x;
    state_y2 = state_y1;
    state_y1 = y;

    accumulator = accumulator & 0x000000003FFFFFFF;
    /* keep the fractional bits that you dropped for the next sample
       otherwise clear the accumulator */

    put_output(y);
    }

state_fraction = (int32_t)accumulator;

/*  save your states back to wherever  */

字符串
但是我不想浪费MIPS来乘以int64_t乘以int32_t,因为我知道这两个值都是32位的数字。但是我也知道结果 * 必须 * 是64位宽的,否则会有麻烦。
假设我这样做:

int64_t accumulator;

int32_t a1, a2, b0, b1, b2;   /* 2nd-order IIR coefficients */

int32_t state_x1, state_x2, state_y1, state_y2, state_fraction;

int32_t x, y;

int32_t get_input(void);

void put_output(int32_t y);

int i, num_samples;
         /* num_samples is the number of samples per block */

/*  load up your coefficients, (leaving as int32_t) */

/*  load up your states from wherever */

accumulator = (int64_t)state_fraction;

for (i=0; i<num_samples; i++)
    {
    x = get_input();

    accumulator += (int64_t)b0*x;
    accumulator += (int64_t)b1*state_x1;
    accumulator += (int64_t)b2*state_x2;
    accumulator += (int64_t)a1*state_y1;
    accumulator += (int64_t)a2*state_y2;

    if (accumulator > 0x1FFFFFFFFFFFFFFF)
       {
       accumulator = 0x1FFFFFFFFFFFFFFF;     /* clip value */
       }
    if (accumulator < -0x2000000000000000)
       {
       accumulator = -0x2000000000000000;    /* clip value */
       }

    y =  (int32_t)(accumulator>>30);   /* always rounding down */

    state_x2 = state_x1;             /* bump the states over */
    state_x1 = x;
    state_y2 = state_y1;
    state_y1 = y;

    accumulator = accumulator & 0x000000003FFFFFFF;
    /* keep the fractional bits that you dropped for the next sample
       otherwise clear the accumulator */

    put_output(y);
    }

state_fraction = (int32_t)accumulator;

/*  save your states back to wherever  */


编译器是否足够聪明,能够理解我想要的是什么?将两个32位有符号整数相乘,并将64位结果添加到64位累加器 * 中,而不将任何内容 * 转换到64位寄存器中?(即没有符号扩展操作,也没有64 x 64位乘法?)
我希望我可以用C语言编写代码,而不用担心ARM编译器会产生非最优的代码。我是不是必须用汇编代码来确保它是正确的?

7xzttuei

7xzttuei1#

如果在C中,int32_t乘以int32_t会隐式地产生int64_t,那么这不会是一个问题。
你说得对,它不会,除非你的int恰好和int64_t是同一类型,而且你的实现也提供了int32_t
但我不想浪费MIPS将int64_t乘以int32_t,因为我知道这两个值都是32位数字
C语言没有提供一种用64位的结果来表示32位x 32位乘法的方法,我所知道的其他几种静态类型的高级语言也没有。
我也知道结果必须是64位宽的,否则会有麻烦。
其结果必然是(至少)64位宽,以避免溢出的可能性。对于任何特定程序中的任何特定乘法是否会实际发生溢出是另一个问题,我不想在这方面分析所给出的代码,所以我规定,如果不考虑溢出,它将产生错误的结果。32位整数乘法的溢出。
编译器是否足够聪明,能够理解我想要的是什么?将两个32位有符号整数相乘,并将64位结果添加到64位累加器中,而不将任何内容转换到64位寄存器中?(即没有符号扩展操作,也没有64 x 64位乘法?)
我认为你是在问这些台词:

accumulator += (int64_t)b0*x;
    accumulator += (int64_t)b1*state_x1;
    accumulator += (int64_t)b2*state_x2;
    accumulator += (int64_t)a1*state_y1;
    accumulator += (int64_t)a2*state_y2;

字符串
我不能向你保证编译器会生成你所要求的代码。在语言的形式语义中,每个乘法的 both 因子都被转换为int64_t,并执行64位乘法。然而,编译器可能会识别这种模式并按照你的要求执行乘法。如果你在编译时启用优化,机会会更好。
我是否必须在汇编代码中这样做,以确保它是正确的?
“做对了”是相当固执己见的,但是,是的,如果你想绝对确定乘法是使用你选择的特定机器指令序列完成的,那么这正是汇编的目的。
但是,在此之前,我建议您首先测试代码,以确定它是否需要性能改进,并分析代码,以了解哪些部分最能从手动调优中受益。

hmmo2u0o

hmmo2u0o2#

所以,如果在C中,int32_t乘以int32_t会隐式地产生int64_t,这不会是一个问题。
不,如果你调用这个函数,很容易检查:

void foo(int32_t a, int32_t b)
{
    printf("%zu\n", sizeof(a * b));
}

字符串
编译器是否足够聪明,能够理解我想要什么?
不,编译器不必预测你想要的,它必须遵循C标准中的指令。需要学习和理解语言,以了解数据库在代码中的行为。
我希望我能写这段C代码而不用担心ARM编译器会产生非最优的代码。我是不是被迫在汇编代码中这样做以确保它是正确的?
相信你的编译器。它会为你的程序生成一个非常有效的机器码。你需要告诉编译器你想要什么。
在编程中,你不应该试图编写尽可能短的源文件,你需要精确。此外,代码应该易于理解。
另外,不要担心过早的优化。在99.99%的代码效率不高是因为算法不好(这是程序员的错误),而不是编译器工作不好。
范例:

int64_t foo(int32_t b0, 
        int32_t b1,
        int32_t b2,
        int32_t a1,
        int32_t a2,
        int32_t state_x1,
        int32_t state_x2,
        int32_t state_y1,
        int32_t state_y2,
        int32_t x,
        int64_t accumulator)
{
    accumulator += (int64_t)b0*x;
    accumulator += (int64_t)b1*state_x1;
    accumulator += (int64_t)b2*state_x2;
    accumulator += (int64_t)a1*state_y1;
    accumulator += (int64_t)a2*state_y2;
    return accumulator;
}


ARM(Cortex-m 32位拇指)

foo:
        push    {r4, lr}
        mov     ip, r1
        mov     lr, r0
        ldrd    r0, r1, [sp, #32]
        ldr     r4, [sp, #28]
        smlal   r0, r1, lr, r4
        ldr     r4, [sp, #12]
        smlal   r0, r1, ip, r4
        ldr     r4, [sp, #16]
        smlal   r0, r1, r2, r4
        ldr     r2, [sp, #20]
        smlal   r0, r1, r3, r2
        ldr     r3, [sp, #24]
        ldr     r2, [sp, #8]
        smlal   r0, r1, r2, r3
        pop     {r4, pc}

相关问题