基于时间序列的 基-2 FFT算法程序

news2024/11/25 2:27:27

gitee链接 :基于时间序列的 基-2 FFT算法程序
我的 gitee 程序目前没有公开,目前仅是给自己的程序做一个备份的目的。
但是大家可以使用我博客贴出来的程序,二者是一样的。

文章目录

  • 1.程序使用方法
  • 2.代码
  • 3.验证

1.程序使用方法

1.先补零至2的整数幂次,调用 AddZero
2.再进行码位倒序,调用 Reverse
3.最后进行 fft,调用 fft

本人先是浏览了其他人在网上的实现,没有搞懂。
于是,在自己独立完成 补零AddZero函数,以及 码位倒序 Reverse函数之后,使用了大量的时间,将DFT公式进行分解了如下的分解和验证:
首先,将 N点的DFT 的公式 分解为 2个 N/2点的DFT之和,并画了蝶形图,以及使用 4个真实的数字验证正确。
然后,将 2个 N/2点的DFT之和 分解为 4个 N/4点的DFT之和,并画了蝶形图,以及使用 8个真实的数字验证正确。
最后,将 4个 N/4 点的DFT之和 分解为 8 个 N/8点的DFT之和,并画了蝶形图,以及使用 16个真实的数字验证正确。
通过研究 3 幅鲽形图,找出其中的规律,完成了 fft 函数,并使用很多组数字进行验证,与 matlab 的结果,以及自己手算的结果均一致。

2.代码

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

#define PI 3.141592654
typedef struct
{
	float real;
	float img;
}complex;

/*复数加法*/
complex add(complex a, complex b)
{
	complex c;
	c.real = a.real + b.real;
	c.img = a.img + b.img;
	return c;
}
/*复数减法*/
complex sub(complex a, complex b)
{
	complex c;
	c.real = a.real - b.real;
	c.img = a.img - b.img;
	return c;
}
/*复数乘法*/
complex mul(complex a, complex b)
{
	complex c;
	c.real = a.real * b.real - a.img * b.img;
	c.img = a.real * b.img + a.img * b.real;
	return c;
}

// 码位倒序
void Reverse(int* arr, int n)
{
	if (arr == NULL || n <= 1)
		return;

	// 计算 n-1 的二进制位数
	int bitsCount = 0;//二进制位数计数器
	for (int i = 0; i < 32; i++)
	{
		if ((((n - 1) >> i) & 1) == 1)
			bitsCount = i + 1;
	}

	// 重排序后的下标
	int* reorderIndex = (int*)malloc(n * 4);
	// 重排序后的序列
	int* reorderIndexArray = (int*)malloc(n * 4);

	// 计算原下标的倒序,存入 reorderIndex
	for (int i = 0; i < n; i++)
	{
		int index = i;
		int newIndex = 0;
		for (int j = 0; j < bitsCount; j++)
		{
			// 如果下标的这一位为1,则构造一个 第(bitsCount - j)位为 1 的数字,或到 newIndex,达到 newIndex 的倒数第 j 位为1的目的
			if (((index >> j) & 1) == 1)
			{
				int tmp = (1 << (bitsCount - 1 - j));
				newIndex |= tmp;
			}
			else// 如果下标的这一位为0,则不用管
			{

			}
		}
		reorderIndex[i] = newIndex;
	}

	/*printf("\n 重排序后的下标:");
	for (int i = 0; i < n; i++)
	{
		printf("%d ,", reorderIndex[i]);
	}*/


	/*printf("\n 重排序后的序列:");*/
	// 重排序后的序列
	for (int i = 0; i < n; i++)
	{
		reorderIndexArray[i] = arr[reorderIndex[i]];
		//printf("%d ,", reorderIndexArray[i]);
	}

	// 把重排序后的序列覆盖到原数组
	for (int i = 0; i < n; i++)
	{
		arr[i] = reorderIndexArray[i];
	}
	free(reorderIndex);
	reorderIndex = NULL;

	free(reorderIndex);
	reorderIndexArray = NULL;
}

int newLength = 0;//新数组的长度
//为原数组补零
int* AddZero(int* arr, int n)
{
	if (n <= 0 || arr == NULL)
	{
		return NULL;
	}

	int* newArr = NULL;

	if (n == 1)
	{
		newArr = (int*)malloc(2 * 4);
		newArr[0] = arr[0];
		newArr[1] = 0;
		newLength = 2;
		return newArr;
	}

	// 计算 n-1 的二进制位数
	int bitsCount = 0;//二进制位数计数器
	for (int i = 1; i < 32; i++)
	{
		//判断第 i 位是否是1,如果是1,则最高位起码有 i+1 位
		if ((((n - 1) >> i) & 1) == 1)
			bitsCount = i + 1;
	}

	// 知道了有多少个二进制位后,再判断第1位到最高位之间是否有1
	// 如果第 1 到 bitsCount-1 位有1,说明不是 2 的整数幂
	int num = n;
	int mi = 0;// 存储2的幂次
	for (int i = 0; i < bitsCount - 1; i++)
	{
		if (((num >> i) & 1) == 1)
		{
			// 说明不是 2 的整数幂
			// 将 num 的最高位往前进一位,然后后面的 1 全部置为0,以此得到比它大的最近的2的整数幂
			num = num & (1 << bitsCount);
			mi = bitsCount;
			break;
		}
	}

	// mi != 0说明原数组长度不为 2 的整数幂次,因此需要补零
	if (mi != 0)
	{
		// 补零后的数组长度
		newLength = pow(2, mi);//补零到 2 的 mi次
		newArr = (int*)malloc(newLength * 4);
		for (int i = 0; i < n; i++)
		{
			newArr[i] = arr[i];
		}

		// 补零后的数组的长度与原数组的长度之差
		int diff = newLength - n;
		// 在数组元素末尾补零
		for (int i = 0; i < diff; i++)
		{
			newArr[n + i] = 0;
		}
	}
	return newArr;
}

complex** fft(int* arr, int n)
{
	if (n <= 0 || arr == NULL)
		return NULL;

	// 先计算数组长度为 2 的多少次幂,这个幂次代表有FFT的蝶形运算有多少级
	// 因为在调用这个函数之前,要调用 Reverse 和 AddZero 这两个函数,已经保证了 n 为 2 的整数次幂
	// 2的整数次幂的数字,除了最高位为1,其余位都为0,且最高位是第几位,它就是2的多少次幂
	// 所以我们要知道n的二进制中1的位置
	int mi = 0;// 2 的 mi 次等于n
	for (int i = 0; i < 32; i++)
	{
		if (((n >> i) & 1) == 1)
		{
			mi = i;
			break;
		}
	}

	if ((pow(2, mi) != n) || (mi == 0))
	{
		printf("n=%d 不是2的整数次幂,无法进行FFT!!!\n", n);
		return NULL;
	}

	// 开辟一个存放 频谱 的数组
	complex** a = (complex**)malloc(n * sizeof(complex*));
	// 将数组的元素拷贝进去,数组 a 就是存放蝶形运算的输入的元素,运算的结果也会覆盖数组 a
	for (int i = 0; i < n; i++)
	{
		a[i] = (complex*)malloc(sizeof(complex));
		a[i]->real = (float)arr[i];
		a[i]->img = 0;
	}

	// level 代表一共有多少级
	// 第一步:先移动到第 level 级
	for (int level = 0; level < mi; level++)
	{
		// 这一级中包含的大蝶的数量, 使用的公式为 n / pow(2, level+1),下面代码优化为了移位运算
		int bigButterFlyCount = n >> (level + 1);
		// 每个大蝶中包含的小蝶的数量
		int smallButterFlyCount = pow(2, level);
		// 计算每个大蝶中包含的小蝶的数量的另一个简单的方法如下面这行代码:
		// int smallButterFlyCount = (n / bigButterFlyCount) >> 1;
		// 每个小蝶两个输入元素下标的距离
		int smallButterFly_Input_Index_Distance = pow(2, level);
		// 第二步:移动到第 level 级的第 i 个大蝶
		for (int i = 0; i < bigButterFlyCount; i++)
		{
			// 这个大碟的第一个输入元素的下标
			int bigButterFly_Input_First_Index = smallButterFlyCount * i * 2;
			// 第三步:移动到第 level 级的第 i 个大蝶中的第 j 个小蝶
			for (int j = 0; j < smallButterFlyCount; j++)
			{
				// 这个大碟中的第 j 个小蝶的第一个输入元素的下标
				int smallButterFly_Input_First_Index = bigButterFly_Input_First_Index + j;
				// 这个大碟中的第 j 个小蝶的第二个输入元素的下标
				int smallButterFly_Input_Second_Index = smallButterFly_Input_First_Index + pow(2, level);
				// 第 j 个小蝶的第一个输入元素
				complex* start_input = a[smallButterFly_Input_First_Index];
				// 第 j 个小蝶的第二个输入元素
				complex* end_input = a[smallButterFly_Input_Second_Index];

				// 计算第 j 个小蝶的旋转因子
				complex w;
				w.real = cos(2*PI / n * j * (pow(2, mi - level - 1)));
				w.img = -sin(2 * PI / n * j * (pow(2, mi - level - 1)));

				// 进行蝶形运算,上加下减
				complex first = add(*first_input, mul(*second_input, w));
				complex second = sub(*first_input, mul(*second_input, w));

				// 将蝶形运算的输出覆盖回原来位置的元素上
				first_input->real = first.real;
				first_input->img = first.img;
				second_input->real = second.real;
				second_input->img = second.img;
			}
		}
	}
	return a;
}


void main()
{
	int arr[] = {1,357,7,8,2,3,4,5};
	// 对原数组进行补零
	int* newArr = AddZero(arr, sizeof(arr) / sizeof(int));
	
	if (newArr == NULL)
	{
		// 说明原数组长度为 2 的整数幂次,不需要补零
		printf("不需要补零\n");
		int length = sizeof(arr) / sizeof(int);
		printf("数组如下:\n");
		for (int i = 0; i < length; i++)
		{
			printf("%d ", arr[i]);
		}

		// 码位倒序
		Reverse(arr, length);
		printf("\n码位倒序后的数组如下\n");
		for (int i = 0; i < length; i++)
		{
			printf("%d ", arr[i]);
		}

		// 进行 fft 蝶形运算
		complex** c = fft(arr, length);

		printf("\nfft 的结果如下:\n");
		for (int i = 0; i < length; i++)
		{
			printf("%f, %fi\n", c[i]->real, c[i]->img);
		}

		for (int i = 0; i < length; i++)
		{
			free(c[i]);
			c[i] = NULL;
		}
		free(c);
		c = NULL;
	}
	else
	{
		printf("补零后的新数组如下:\n");
		for (int i = 0; i < newLength; i++)
		{
			printf("%d ", newArr[i]);
		}

		// 码位倒序
		Reverse(newArr, newLength);
		printf("\n码位倒序后的数组如下:\n");
		for (int i = 0; i < newLength; i++)
		{
			printf("%d ", newArr[i]);
		}

		// 进行 fft 蝶形运算
		complex** c = fft(newArr, newLength);

		printf("\nfft 的结果如下:\n");
		for (int i = 0; i < newLength; i++)
		{
			printf("%f, %fi\n", c[i]->real, c[i]->img);
		}

		free(newArr);
		newArr = NULL;

		for (int i = 0; i < newLength; i++)
		{
			free(c[i]);
			c[i] = NULL;
		}
		free(c);
		c = NULL;
	}
}

3.验证

可以对比 matlab 程序和例子进行验证

xn=[1;357;7;8];
Xk=fft(xn);
disp(Xk);

结果对比
在这里插入图片描述

xn=[1;357;7;8;2;3;0;0];
Xk=fft(xn);
disp(Xk);

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/336846.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

html(二)基础标签

一 HTML中的注释 重点&#xff1a; 在哪写注释? 注释的形式? vs code和webstorm都可以通过 ctrl / 进行单行注释和取消注释 ① html中注释的形式 1) html文档中单行和多行注释是"<!-- -->" -->html2) 在html文档中,script标签…

volatile 关键字

1.volatile 能保证内存可见性 volatile 修饰的变量, 能够保证 "内存可见性". 代码在写入 volatile 修饰的变量的时候, 改变线程工作内存中volatile变量副本的值将改变后的副本的值从工作内存刷新到主内存 代码在读取 volatile 修饰的变量的时候 从主内存中读取vol…

为什么B站中的弹幕可以不遮挡人物

上班逛B站时摸鱼时&#xff0c;看到了满屏的弹幕&#xff0c;而且还不挡脸&#xff0c;突然心血来潮来看看它是怎么实现的&#xff1f; 不难发现弹幕其实它就是有一个蒙版层div&#xff0c;遮挡在视频组件的上方&#xff0c;z-index层级设置的比较高&#xff08;这里是11&…

史上最全最详细的Instagram 欢迎消息引流及示例

史上最全最详细的Instagram 欢迎消息引流及示例&#xff01;关键词&#xff1a; Instagram 欢迎消息SaleSmartly&#xff08;ss客服&#xff09; 寻找 Instagram 欢迎消息示例&#xff0c;您可以用于您的业务。在本文中&#xff0c;我们将介绍Instagram欢迎消息的基础知识和好处…

window11安装node、nvm、nrm

一、安装nvm 下载nvm安装包&#xff0c;window11建议使用exe安装包 Releases coreybutler/nvm-windows GitHub 下载后双击安装 切记&#xff01;切记&#xff01;切记&#xff01; 安装nvm和nodejs的目录设置一定不要有特殊符号或者空格&#xff0c;设置一个连续的只有英文…

UMI 创建react目录介绍及配置

UMI 生成react项目目录介绍及配置 react项目目录介绍umi多种配置方案运行时配置app.ts 的使用 1、umi创建的项目目录大致如下 ├─package.json 配置依赖以及启动打包所需的命令 ├─.umirc.ts 配置文件&#xff0c;包含 umi 内置功能和插件的配置 ├── dist 打包后生成的…

情人节送什么礼物?四款情人节潮流数码好物推荐

情人节是一个特别的日子&#xff0c;是表达爱意和祝福的机会&#xff0c;如果您正在寻找一件特别的礼物&#xff0c;下面这篇文章不容错过。 推荐1&#xff1a;南卡小音舱蓝牙耳机&#xff08;299元&#xff09; 作为最能表达仪式感和诚意的礼物&#xff0c;精致和实用是很重要…

Spring中Bean的作用域问题

文章目录一、通过案例来简单体会一下Bean的作用域问题二、作用域定义三、Bean的作用域分类singletonprototyperequestsessionapplication&#xff08;了解&#xff09;singleton&#xff08;单例作用域&#xff09; 和 application &#xff08;全局作用域&#xff09;的区别we…

马尔科夫预测

一、模型介绍 天气有以下几种状态&#xff1a;晴天、雨天、阴天若已知天气当前处于某种状态&#xff0c;则天气未来的状态只与现在有关&#xff0c;与过去无关注意&#xff0c;天气的状态是随机的&#xff0c;只能求明天处于某一种状态的概率描述这种随机现象的模型&#xff0…

Visual Commonsense R-CNN 实现和代码

这篇文章比较早&#xff0c;但是对于因果介绍的比较详细&#xff0c;很值得学习。 代码&#xff1a;https://github.com/Wangt-CN/VC-R-CNN 代码花了挺长时间总算跑通了&#xff0c;在 3080 上调真是错误不断&#xff0c;后来换到 2080 又是一顿调才好。这里跑通的主要环境为 u…

代理模式详解

本文首更于《从零开始手把手教你实现一个简单的RPC框架》 。 1. 代理模式2. 静态代理3. 动态代理 3.1. JDK 动态代理机制 3.1.1. 介绍3.1.2. JDK 动态代理类使用步骤3.1.3. 代码示例 3.2. CGLIB 动态代理机制 3.2.1. 介绍3.2.2. CGLIB 动态代理类使用步骤3.2.3. 代码示例 3.3. …

win10系统安装Nginx

Nginx是一款自由的、开源的、高性能的HTTP服务器和反向代理服务器&#xff0c;同时也提供了IMAP/POP3/SMTP服务。 Nginx可以进行反向代理、负载均衡、HTTP服务器&#xff08;动静分离&#xff09;、正向代理等操作。因为最近在公司使用到了Nginx 第一步&#xff1a;下载Nginx …

想找大数据工作需要学些什么

大数据开发做什么&#xff1f; 大数据开发分两类&#xff0c;编写Hadoop、Spark的应用程序和对大数据处理系统本身进行开发。大数据开发工程师主要负责公司大数据平台的开发和维护、相关工具平台的架构设计与产品开发、网络日志大数据分析、实时计算和流式计算以及数据可视化等…

12_FreeRTOS任务相关API函数

目录 FreeRTOS任务相关API函数介绍 获取任务优先级函数 设置任务优先级函数 获取任务数量函数 获取所有任务状态信息 获取指定的单个任务的状态信息 获取当前任务句柄 通过任务名获取任务句柄 获取任务栈历史最小剩余推栈 以表格的形式获取系统中任务信息 实验源码 …

【虹科】防止PCB组装过程出现质量错误的5种方法

质量问题和错误时有发生&#xff0c;尤其是在涉及PCB和电子产品制造的复杂人为操作任务中。通常情况下&#xff0c;企业可能会配备自动光学检测&#xff08;AOI&#xff09;等系统&#xff0c;这些系统通常用于制造过程中“中间”阶段的检测。尽管AOI系统为质量控制创造价值&am…

Jmeter in Linux - 如何在Linux系统使用Jmeter压测?

Jmeter in Linux - 如何在Linux系统使用Jmeter压测&#xff1f;Jmeter in Linux系列目录&#xff1a;1. 在windows创建好一个测试计划&#xff1a;2. 保存后&#xff0c;将jmx后缀的文件上传至Linux服务器3. 执行jmeter命令4. 根据执行日志分析压测报告5. 解析压测报告Jmeter i…

有效的括号-力扣20-java

一、题目描述给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。有效字符串需满足&#xff1a;左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。每个右括号都有一个对应的相同类…

【huggingface系列学习】Using Transformers

文章目录前言Using Transformers使用tokenizer预处理Tokenizer详解Loading and saving加载保存EncodingDecodingModel创建一个Transformer不同的加载方法模型保存使用模型进行推理前言 因实验中遇到很多 huggingface-transformers 模型和操作&#xff0c;因此打算随着 course …

剖析字节案例,火山引擎 A/B 测试 DataTester 如何“嵌入”技术研发流程

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 日前&#xff0c;在 WOT 全球创新技术大会上&#xff0c;火山引擎 DataTester 技术负责人韩云飞做了关于字节跳动 A/B 测试平台的分享。DataTester 是字节跳动内部应…

Roboguide与TIA V16通讯

软件需求:1. roboguide;2. TIA V16;3. KEPServer; 在之前的文章中介绍过KEPServer与TIA V16的通讯,此处不再介绍。接下来,介绍roboguide与KEPServer的仿真通讯。 创建一个roboguide项目。选择【外部设备】➡【添加外部设备】 选择【OPC Server】➡【OK】 OPC服务器名称命…