我不知道以前是否有人问过这个问题。这个问题背后的根源是标准 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 */
但是当我知道两个值都是 32 位数字时,我不想浪费 MIPS 将
int64_t
乘以 int32_t
。但我也知道结果必须是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 编译器会创建非最佳代码。我是否被迫在汇编代码中执行此操作以确保其正确完成?
编译器是否足够聪明,能够理解我想要什么?
不,编译器不必预测你想要什么,它必须遵循 C 标准中编写的指令。 您需要足够好地学习和理解该语言,才能了解数据类型在代码中的行为方式。
我希望我可以编写这个 C 代码而不必担心 ARM 编译器将创建非最佳代码。我是否被迫这样做 汇编代码以确保其正确完成?
相信你的编译器。它将为“您的”程序生成非常高效的机器代码。你需要告诉编译器你想要什么。 在编程中,您不应该试图编写尽可能短的源文件,您需要
精确。此外,代码应该易于人类理解。 另外,不要担心过早的优化。 99.99% 的低效代码与糟糕的算法有关(这是程序员的错误),而不是糟糕的编译器工作。
int32_t
乘以
会隐式得到int32_t
,那么这就不成问题了。但事实并非如此。 你是对的,事实并非如此,除非你的int64_t
int
恰好与
int64_t
类型相同,并且你的实现也提供了 int32_t
。我不知道任何具有这些特征的实现,但可能有一个。
但是当我知道这两个值都是 32 位数字时,我不想浪费 MIPS 将 int64_t
乘以
C 没有提供表达这一点的方法。我所知道的其他几种静态类型高级语言也没有。int32_t
我也知道结果必须是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位乘法。然而,您的编译器可能会识别该模式并根据您的需要执行乘法。如果您在启用优化的情况下进行编译,机会会更好。
“正确完成”是相当固执己见的,但是,是的,如果您想要绝对确定乘法是使用特定的机器指令序列完成的,那么这正是汇编的目的。
但是,在执行此操作之前,我鼓励您测试代码以确定其是否需要改进性能,并分析代码以了解哪些部分最能从手动调整中受益。