1056. 组合数的和(15)

本文介绍了一种算法,该算法接受N个非0个位数字,并计算所有可能的两个数字组合成的两位数之和。例如,对于输入2、5、8,它将计算25、28等所有组合的总和。

给定N个非0的个位数字,用其中任意2个数字都可以组合成1个2位的数字。要求所有可能组合出来的2位数字的和。例如给定2、5、8,则可以组合出:25、28、52、58、82、85,它们的和为330。

输入格式:
输入在一行中先给出N(1< N< 10),随后是N个不同的非0个位数字。数字间以空格分隔。

输出格式:

输出所有可能组合出来的2位数字的和。
输入样例:

3 2 8 5

输出样例:

330

#include <stdio.h>
int main()
{
    int a[10],n,i,j,sum=0;
    scanf("%d",&n);
    for(i=0;i<n;i++)
        scanf("%d",&a[i]);
    for(i=0;i<n;i++)
    {
        for(j=0;j<n;j++)
        {
            if(j!=i)
                sum+=a[i]*10+a[j];
        }
    }
    printf("%d",sum);
}
#include <stdint.h> #include <math.h> // 滤波器参数配置 #define SAMPLE_RATE_HZ 8000.0f // 采样频率 #define CUTOFF_FREQ_HZ 720.0f // 截止频率 #define FIR_TAP_NUM 16 // 滤波器阶数+1 // 优化后的滤波器系数 (Q15格式,已放大32768倍) const int16_t fir_coeffs[FIR_TAP_NUM] = { 106, 367, 284, -263, -1056, -1270, 0, 2526, 2526, 0, -1270, -1056, -263, 284, 367, 106 }; // 滤波器状态变量 typedef struct { int16_t buffer[FIR_TAP_NUM]; // 环形缓冲区 uint8_t index; // 当前写入位置 } FIR_Filter; // 初始化滤波器 void FIR_Init(FIR_Filter *fir) { for(int i=0; i<FIR_TAP_NUM; i++) fir->buffer[i] = 0; fir->index = 0; } // FIR滤波处理函数 (定点整数运算) int16_t FIR_Process(FIR_Filter *fir, int16_t input) { // 更新缓冲区 fir->buffer[fir->index] = input; // 对称计算优化 (减少乘法次数) int32_t acc = 0; uint8_t i, j; for(i=0, j=fir->index; i<FIR_TAP_NUM/2; i++) { // 利用系数对称性:coeff[i] == coeff[N-1-i] int16_t sum = (int16_t)(fir->buffer[j] + fir->buffer[(j + FIR_TAP_NUM - 1) % FIR_TAP_NUM]); acc += (int32_t)sum * fir_coeffs[i]; // 更新环形缓冲区索引 j = (j == 0) ? FIR_TAP_NUM-1 : j-1; } // 更新缓冲区索引 fir->index = (fir->index + 1) % FIR_TAP_NUM; // 结果缩放 (Q15 -> Q0) 并饱处理 acc = (acc + 0x4000) >> 15; // 四舍五入 if(acc > 32767) return 32767; if(acc < -32768) return -32768; return (int16_t)acc; } // 示例使用 int main() { FIR_Filter filter; FIR_Init(&filter); while(1) { // 获取ADC采样值 (假设12位ADC) int16_t adc_value = ADC_Read() - 2048; // 转换为有符号 // FIR滤波处理 int16_t filtered = FIR_Process(&filter, adc_value); // 输出处理结果 (DAC或后续处理) DAC_Write(filtered + 2048); // 转换回无符号范围 } }
最新发布
08-07
<think>我们有一个用C语言实现的FIR滤波器代码。该代码使用定点整数运算,并利用了滤波器系数的对称性来减少乘法次数。 代码中定义了一个FIR滤波器结构,包含一个环形缓冲区一个索引。初始化函数将缓冲区清零,索引置0。 处理函数中,每次输入一个新样本,更新缓冲区,然后利用系数的对称性,将对称位置的两个样本相加后再乘以系数(系数也是对称的),从而将乘法次数减少一半。 最后对累加结果进行缩放(从Q15格式转换回Q0处理,然后返回滤波后的值。 但是,我们注意到代码中有一个小问题:在对称计算循环中,索引j的更新可能导致访问到错误的数据。让我们仔细析: 原始代码中,我们使用: j = fir->index // 当前写入位置 然后从j开始递减(环形)遍历前一半的缓冲区。但是,由于我们存储新样本后立即计算,此时新样本在buffer[fir->index]中,而最老的样本在buffer[fir->index+1](模运算)位置。 然而,在对称计算中,我们使用: for(i=0, j=fir->index; i<FIR_TAP_NUM/2; i++) { int16_t sum = (int16_t)(fir->buffer[j] + fir->buffer[(j + FIR_TAP_NUM - 1) % FIR_TAP_NUM]); ... j = (j == 0) ? FIR_TAP_NUM-1 : j-1; 这个循环试图将缓冲区中对称位置的两个样本相加。注意,系数数组fir_coeffs的前一半系数对应的是对称位置的乘以系数。 但是,这里有一个问题:当我们更新缓冲区后,当前的索引位置fir->index是最新的样本,而最老的样本应该是(fir->index+1) % FIR_TAP_NUM(因为环形缓冲区,新样本覆盖了最老的样本)。 然而,在循环中,我们是从当前写入位置j(即最新样本)开始,然后依次往前取(递减)。这样,我们组合的样本对是: (最新样本, 最新样本的前一个) -> 对应系数0 (最新样本的前一个, 最新样本的前两个) -> 对应系数1 ... 但是,FIR滤波器应该是从最新的样本到最老的样本,按顺序与系数相乘。而系数是对称的,所以系数0系数15相同,系数1系数14相同,等等。 因此,正确的对称组合应该是: 第0个系数对应:buffer[newest] buffer[newest - (N-1)](即最老的样本)相加,然后乘以系数0(也是系数15)。 第1个系数对应:buffer[newest-1] buffer[newest - (N-2)] 相加,乘以系数1(也是系数14)。 然而,在代码中,我们是从最新样本开始,然后取jj-1位置?不对,注意代码中第二个缓冲区索引是 (j + FIR_TAP_NUM - 1) % FIR_TAP_NUM,这实际上是j的前一个位置(因为环形缓冲区,所以用模运算)。 所以,实际上,在循环的第一次迭代中,我们取的是: j = fir->index -> 最新样本的位置 (j + FIR_TAP_NUM -1) % FIR_TAP_NUM -> 上一个样本(即新样本之前的一个样本) 这并不对称!对称的位置应该是:最新样本的位置(最新样本位置 - (FIR_TAP_NUM-1))的位置(模FIR_TAP_NUM)对应。 因此,正确的做法应该是:对于第i次迭代,两个样本的位置应该是: pos1 = (fir->index - i) mod FIR_TAP_NUM pos2 = (fir->index - (FIR_TAP_NUM - 1 - i)) mod FIR_TAP_NUM [即对称位置] 或者,我们可以这样:从当前索引开始,我们取对称位置的两个点。但是,注意我们的缓冲区是环形的,新样本覆盖的是最老的样本。所以,在存储新样本后,缓冲区中从当前索引开始递减(模)就是按时间从新到旧。 所以,正确的对称位置应该是: 第一个对称对:位置0位置15(当FIR_TAP_NUM=16) 第二个对称对:位置1位置14 ... 因此,在循环中,我们应该从0到N/2-1,然后取: sample1 = buffer[(index - i) mod N] // 最新的第i个样本(i=0就是最新) sample2 = buffer[(index - (N-1-i)) mod N] // 最老的对应样本(即对称位置) 但是,注意:index指向的是最新样本,那么最老的样本应该是 (index+1) mod N? 不对,因为环形缓冲区,最老的样本在index+1(因为新样本覆盖了最老的,所以覆盖之前,最老的位置是index+1,覆盖后,这个位置变成了最新,而原来的最老位置是index+2,直到index+15(模16)变成了次新的?) 实际上,环形缓冲区的逻辑是:每次写入新样本后,index指向新样本,然后index-1(模)是前一个样本,index-2是前两个,...,index-15是最老的样本(因为缓冲区大小为16,所以最老的样本就是当前写入位置的前一个位置?不对,因为环形缓冲区,最老的样本应该是下一个被覆盖的位置,即(index+1) mod 16)。 因此,如果我们按时间顺序排列(从新到旧): 位置index: 最新样本(时间n) 位置index-1: 时间n-1 ... 位置index-15: 时间n-15(最老) 所以,对称位置应该是: 第0个系数:时间nn-15 第1个系数:时间n-1n-14 ... 因此,在循环中,我们可以这样: for(i=0; i<FIR_TAP_NUM/2; i++) { int16_t sample_new = buffer[(index - i + FIR_TAP_NUM) % FIR_TAP_NUM]; int16_t sample_old = buffer[(index - (FIR_TAP_NUM-1-i) + FIR_TAP_NUM) % FIR_TAP_NUM]; // 或者,因为对称性,我们可以用:index-i index-(FIR_TAP_NUM-1-i) 模FIR_TAP_NUM // 但是注意,index-i可能为负数,所以模运算前加FIR_TAP_NUM确保负。 sum = sample_new + sample_old; acc += sum * fir_coeffs[i]; } 然而,原代码中并没有这样计算,而是从j=index开始,然后每次递减j,并且取jj-1位置?这显然不对。 所以,原代码中的对称计算有误。我们需要修正。 修正后的循环可以这样写: for (i=0; i<FIR_TAP_NUM/2; i++) { int16_t sample1 = fir->buffer[(fir->index - i + FIR_TAP_NUM) % FIR_TAP_NUM]; int16_t sample2 = fir->buffer[(fir->index - (FIR_TAP_NUM-1-i) + FIR_TAP_NUM) % FIR_TAP_NUM]; acc += (int32_t)(sample1 + sample2) * fir_coeffs[i]; } 但是,这样每次循环都要计算两个位置,且需要模运算(取余),可能效率不高。原代码试图通过一个递减的索引j来避免模运算,但计算错了。 我们可以优化:使用环形缓冲区的特性,我们可以维护一个递减的索引,并在每次循环中做模运算,或者用条件判断(就像原代码中那样)。但是,原代码中的模运算实际上是错误的,因为它只是取了相邻的两个样本。 因此,我们重新设计循环: 方法1:使用两个指针,一个从最新样本开始递减,另一个从最老样本(即index+1 mod N)开始递增?但这样并不直观。 方法2:我们直接按上述公式计算,但注意模运算在嵌入式系统中可能较慢,我们可以用条件判断来代替模运算(当索引为负时加上FIR_TAP_NUM)。 这里,我们采用方法2,并优化索引计算: for (i=0; i<FIR_TAP_NUM/2; i++) { int idx1 = fir->index - i; if (idx1 < 0) idx1 += FIR_TAP_NUM; int idx2 = fir->index - (FIR_TAP_NUM-1-i); if (idx2 < 0) idx2 += FIR_TAP_NUM; int16_t sample1 = fir->buffer[idx1]; int16_t sample2 = fir->buffer[idx2]; acc += (int32_t)(sample1 + sample2) * fir_coeffs[i]; } 但是,这样每次循环有两次条件判断,而且索引计算稍微复杂。 另一种思路:我们注意到,在环形缓冲区中,从当前索引开始,最老的样本位置实际上是 (fir->index+1) % FIR_TAP_NUM,然后从最新到最老的顺序是: buffer[fir->index] -> 最新 buffer[fir->index-1] -> 次新(注意负数要处理) ... buffer[fir->index-15] -> 最老(相当于buffer[(fir->index+1) % FIR_TAP_NUM]) 因此,对称位置就是: 第0个:buffer[fir->index] buffer[(fir->index+1) % FIR_TAP_NUM] ?不对,应该是: 最新样本(位置index)最老样本(位置index-15,但index-15可能为负,所以等价于index+1 mod 16)?不对,因为index-15+16=index+1,所以最老样本位置是index+1 mod 16。 所以,对称位置: 第0个:index index+1 mod 16? 不对,应该是indexindex-15 mod 16(即index+1 mod16)? 不对,因为index-15 mod16 = (index+1) mod16,所以确实是最新样本index+1位置的样本(即最老样本)相加。 但是,注意:在存储新样本后,缓冲区中: buffer[index] = 新样本 buffer[index-1] = 前一个样本(时间n-1) ... buffer[index-15] = 时间n-15的样本(最老样本),而index-15等于index+1 mod16(因为16个位置,index-15=index+1 mod16)? 不对,模运算:index-15 mod16 = index-15+16=index+1 mod16? 是的。 因此,对称位置是: i=0: 位置index 位置index+1 mod16 -> 最新最老 i=1: 位置index-1 位置index+2 mod16 -> 次新次老 ... 所以,我们可以这样: for (i=0; i<FIR_TAP_NUM/2; i++) { int idx1 = (fir->index - i + FIR_TAP_NUM) % FIR_TAP_NUM; // 从最新开始递减 int idx2 = (fir->index + 1 + i) % FIR_TAP_NUM; // 从最老开始递增 int16_t sample1 = fir->buffer[idx1]; int16_t sample2 = fir->buffer[idx2]; acc += (int32_t)(sample1 + sample2) * fir_coeffs[i]; } 这样,我们每次循环中,idx1从index开始递减(每次减1,但用模处理),idx2从index+1(最老)开始递增(每次加1,模处理)。这样,我们就得到了对称位置的两个样本。 注意:因为系数数组fir_coeffs只存储了前一半的系数(0到7),所以这样是匹配的。 因此,我们修改FIR_Process函数中的循环部: 但是,原代码中系数数组fir_coeffs的长度是FIR_TAP_NUM(16),但这里我们只用了前8个系数(0到7)?实际上,原系数数组有16个系数,但利用对称性,我们只需要乘8次(每次用两个样本的乘以一个系数,这个系数是对称的两个位置共享的)。 所以,我们修改后的循环: uint8_t i; int idx1, idx2; for (i=0; i<FIR_TAP_NUM/2; i++) { idx1 = (fir->index - i + FIR_TAP_NUM) % FIR_TAP_NUM; idx2 = (fir->index + 1 + i) % FIR_TAP_NUM; // 或者,为了避免负数模运算,我们可以用: // idx1 = fir->index - i; // if (idx1 < 0) idx1 += FIR_TAP_NUM; // idx2 = fir->index + 1 + i; // if (idx2 >= FIR_TAP_NUM) idx2 -= FIR_TAP_NUM; int16_t sum = fir->buffer[idx1] + fir->buffer[idx2]; acc += (int32_t)sum * fir_coeffs[i]; // fir_coeffs[i] 对应第i个系数 } 注意:原系数数组fir_coeffs有16个元素,但我们只使用前8个(0..7),因为对称性,后8个系数前8个对称(但顺序相反)。而我们的系数数组在定义时,前8个系数就是对称轴前面的系数,所以这样使用是正确的。 另外,原代码中在循环之后更新了索引:fir->index = (fir->index+1) % FIR_TAP_NUM; 这个更新应该放在循环之后,因为循环中还要使用当前的index。 因此,我们修改FIR_Process函数如下: 但是,我们还要注意:原代码中有一个j的循环变量,我们不再需要。我们改用i。 另外,原代码中在循环之前更新了缓冲区:fir->buffer[fir->index] = input; 这是正确的,因为我们需要将新样本放入缓冲区。 所以,完整修改后的FIR_Process函数: ```c int16_t FIR_Process(FIR_Filter *fir, int16_t input) { // 更新缓冲区 fir->buffer[fir->index] = input; // 对称计算优化 (减少乘法次数) int32_t acc = 0; uint8_t i; // 遍历前一半的系数 for(i=0; i<FIR_TAP_NUM/2; i++) { // 计算两个对称位置的索引 int idx1 = fir->index - i; if (idx1 < 0) idx1 += FIR_TAP_NUM; // 处理负索引 int idx2 = fir->index + 1 + i; if (idx2 >= FIR_TAP_NUM) idx2 -= FIR_TAP_NUM; int16_t sum = fir->buffer[idx1] + fir->buffer[idx2]; acc += (int32_t)sum * fir_coeffs[i]; } // 更新缓冲区索引(指向下一个写入位置) fir->index = (fir->index + 1) % FIR_TAP_NUM; // 结果缩放 (Q15 -> Q0) 并饱处理 acc = (acc + 0x4000) >> 15; // 四舍五入:加0.5(Q150.5是0x4000)然后右移15位 if(acc > 32767) return 32767; if(acc < -32768) return -32768; return (int16_t)acc; } ``` 这样修改后,我们利用了系数的对称性,并且正确计算了对称位置的样本。 注意:系数数组fir_coeffs中,我们只使用了前一半(8个)系数,因为系数是对称的(fir_coeffs[i]等于fir_coeffs[FIR_TAP_NUM-1-i])。因此,在累加时,我们只需要将对称位置的样本相加,然后乘以对应的系数(前一半的系数)。 另外,我们使用条件判断来处理环形缓冲区的索引,避免模运算(模运算在嵌入式系统中可能较慢)。 最后,我们更新索引(循环后更新),并处理累加结果的缩放。 我们还可以考虑另一种优化:不使用条件判断,而用指针循环展开,但这里为了清晰,我们使用条件判断。 现在,我们重新检查整个代码。 另外,注意系数数组是Q15格式(即系数乘以32768后取整),所以两个Q0的样本相加(得到Q0)乘以Q15的系数,得到的是Q15的乘积,然后累加。累加器acc是32位整数,最后我们右移15位(并四舍五入)得到Q0的结果。 四舍五入的方法:加0x4000(即0.5的Q15表示)然后右移15位。 最后,我们进行饱处理,防止溢出。 因此,修改后的代码应该可以正确工作。 但是,我们还需要注意:在初始化时,我们清空了缓冲区,所以初始状态全零,不会产生错误索引。 示例使用部不变。 另外,系数数组定义为: const int16_t fir_coeffs[FIR_TAP_NUM] = { ... }; 但我们只使用前8个系数(0..7),所以实际上我们可以将系数数组定义为长度为8。但是,原定义是16个,我们为了保持原样,就不改了。或者我们可以修改为只存储一半的系数,以节省空间。 不过,原代码中系数数组有16个元素,但利用对称性,我们只使用前8个。所以,我们可以将系数数组减半: const int16_t fir_coeffs_half[FIR_TAP_NUM/2] = { 106, 367, 284, -263, -1056, -1270, 0, 2526 }; 注意:原系数数组是对称的,即第8个系数(索引7)是2526,而第9个(索引8)也是2526(对称),所以如果我们只存储一半,那么前8个系数就是原系数数组的前8个(0..7)。 因此,我们可以修改系数数组为一半大小,并修改代码中使用系数的部。 但原代码中系数数组已经定义为16个,为了不改变外部依赖,我们暂时不修改。 所以,我们使用fir_coeffs[i](i从0到7)即可。 综上所述,我们修改FIR_Process函数。 下面给出完整的修正代码(包括注释):</think>### 优化后的FIR滤波器实现 下面是修正后的FIR滤波器实现代码,主要改进了对称计算部的索引处理逻辑,确保正确利用系数的对称性: ```c #include <stdint.h> #include <math.h> // 滤波器参数配置 #define SAMPLE_RATE_HZ 8000.0f // 采样频率 #define CUTOFF_FREQ_HZ 720.0f // 截止频率 #define FIR_TAP_NUM 16 // 滤波器阶数+1 // 优化后的滤波器系数 (Q15格式,已放大32768倍) const int16_t fir_coeffs[FIR_TAP_NUM] = { 106, 367, 284, -263, -1056, -1270, 0, 2526, 2526, 0, -1270, -1056, -263, 284, 367, 106 }; // 滤波器状态变量 typedef struct { int16_t buffer[FIR_TAP_NUM]; // 环形缓冲区 uint8_t index; // 当前写入位置 } FIR_Filter; // 初始化滤波器 void FIR_Init(FIR_Filter *fir) { for(int i=0; i<FIR_TAP_NUM; i++) fir->buffer[i] = 0; fir->index = 0; } // FIR滤波处理函数 (修正后的对称计算) int16_t FIR_Process(FIR_Filter *fir, int16_t input) { // 更新缓冲区 fir->buffer[fir->index] = input; // 对称计算优化 (正确实现) int32_t acc = 0; // 计算对称位置样本并累加 for(uint8_t i=0; i<FIR_TAP_NUM/2; i++) { // 计算当前样本位置 int idx1 = (fir->index - i + FIR_TAP_NUM) % FIR_TAP_NUM; // 计算对称样本位置 int idx2 = (fir->index - (FIR_TAP_NUM-1-i) + FIR_TAP_NUM) % FIR_TAP_NUM; // 对称位置样本相加后乘以系数 int16_t sum = fir->buffer[idx1] + fir->buffer[idx2]; acc += (int32_t)sum * fir_coeffs[i]; } // 更新缓冲区索引 fir->index = (fir->index + 1) % FIR_TAP_NUM; // 结果缩放 (Q15 -> Q0) 并饱处理 acc = (acc + 0x4000) >> 15; // 四舍五入 if(acc > 32767) return 32767; if(acc < -32768) return -32768; return (int16_t)acc; } // 示例使用 int main() { FIR_Filter filter; FIR_Init(&filter); while(1) { // 获取ADC采样值 (假设12位ADC) int16_t adc_value = ADC_Read() - 2048; // 转换为有符号 // FIR滤波处理 int16_t filtered = FIR_Process(&filter, adc_value); // 输出处理结果 (DAC或后续处理) DAC_Write(filtered + 2048); // 转换回无符号范围 } } ``` ### 代码解释 1. **对称计算优化**: - 原始代码中的索引计算有误,未正确处理环形缓冲区的对称位置 - 修正后使用公式: - `idx1 = (index - i + N) % N` 当前样本位置 - `idx2 = (index - (N-1-i) + N) % N` 对称样本位置 - 这样能正确配对`buffer[0]`与`buffer[15]`、`buffer[1]`与`buffer[14]`等对称位置 2. **Q15格式处理**: - 系数使用Q15格式(乘以32768的整数值) - 累加结果右移15位实现Q15→Q0转换 - 添加`0x4000`实现四舍五入(相当于加0.5) 3. **环形缓冲区管理**: - `index`指向最新样本位置 - 每次处理时覆盖最老的样本 - 索引更新使用模运算实现环形缓冲 4. **饱处理**: - 结果限制在16位有符号整数范围(-32768~32767) - 防止算术溢出导致的信号失真 5. **ADC/DAC接口**: - 假设12位ADC(0-4095),转换为有符号数(-2048~2047) - 输出时转换回无符号范围(0-4095) ### 性能优化说明 1. **乘法次数减半**: - 利用系数对称性,乘法次数从16次减少到8次 - 每个循环处理一对对称样本 2. **避免条件支**: - 使用模运算替代条件判断,提高流水线效率 - 适合嵌入式系统等资源受限环境 3. **定点数优化**: - 全部使用整数运算,避免浮点开销 - 四舍五入处理提高精度
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值