妙妙小豆子

胆子大一点,想开一点。长风破浪会有时,直挂云帆济沧海

素数螺旋线 银牌收录

程序简介

素数螺旋线,是对阿基米德螺旋线的改造,他只绘制整数点,并排除了所有合数点。视频中绘制了一张完整的点图,并通过 44k+b,710k+b,阐释了这些点为何在视觉效果上会组成一条条错落有致的螺旋线。在程序中构造 1 倍的倍数螺旋线即是该份完整点图。

倍数螺旋线,是对这张完整点图的改造,例如 3 倍的倍数螺旋线就是每隔 2 个点会留下绘制 1 个点,13 倍的倍数螺旋线就是每隔 12 个点会留下绘制 1 个点。专栏中通过绘制了 2,3,5,7,11 等倍数螺旋线,阐释了素数螺旋线中的相邻螺旋线(放大会呈现直线,再放大又会呈现螺旋线)间隔大小不一致的原因。专栏中的讨论是从大于 5000 开始的,所以他的倍数螺旋线是空心的。

程序中,(小写)w 是拉远视角,s 是拉近视角,a 是顺时针旋转,d 是逆时针旋转。
我暂时没有做到准确预测对不同视角尺度下某数的倍数螺旋线有几条悬臂,预测算法较为简陋。

最开始的算法,考虑到悬臂的数量,本质是绘制多少个点,更接近 2π 的倍数,故设计成:

  1. 某数除以 2π,之后与周围两个整数的距离取较小的那个,记作 A;
  2. A 乘自然数 1,之后与周围两个整数的距离取较小的那个,结果为 B,若 B<A,则 A=B,存入该自然数;
  3. A 乘自然数 2...
    ....
    这样的结果是有时候准,例如 11;有时候不准,例如 7。
    不准的情况多是小范围内连续出现自然数满足 B<A,准确的悬臂数会出现在这堆自然数中非首项的某个值。
    例如 5,会出现结果 4,5;准确数是 5;
    例如 7,会出现结果 8,9;准确数是 9;
    例如 8,会出现结果 3,4,7,11;准确数是 4,11;

显示每一步的结果会发现,A 乘这些数,比较取小后的结果虽然都小于 A,但是差得很多,似乎都有数量级的区分。

于是改进了算法,引入了迭代倍数 C,存入条件变成 B<A*C,C 小于等于 1。C 较大时就会出现上面的预测不准情况,C 较小时也会有问题。所以这样的改进也只适合预测视角尺寸较小时的前几次悬臂数量。

程序运行展示

完整源代码

////////////////////////////////////////
// 程序:素数螺旋线
// 作者:Gary
// 编译环境:Visual C++ 2010,EasyX_20211109
// 编写日期:2021-1-24

# include <math.h>
# include <graphics.h>
# include <string>
# include <conio.h>

static double pi = acos (-1.0);			// 圆周率 π
static HWND hOut;						// 画布

// 定义一个结构体,按钮
struct Node1
{
	int posx1, posy1, posx2, posy2;		// 坐标
	LPTSTR text;						// 文字
};

// 定义一个类
class Gary
{
public:
	void carry ();						// 主进程
	void initialization ();				// 初始化
	void draw_scene ();					// 绘制界面函数
	void draw_helical_line ();			// 绘制螺旋线函数
	void move ();						// 窗口主视角
	void check ();						// 悬臂预测函数

	int num_button;						// 按钮数量参数
	int exit_carry;						// 主循函数控制参数
	int exit_move;						// 开始界面控制参数

	double num_size, num_I;				// 尺寸变量
	double num_reduction_multiple;		// 悬臂预测中的迭代倍数
	double num_b;						// 估测悬臂所需变量
	double num_c;						// 估测悬臂所需变量
	double num_e;						// 估测悬臂所需变量

	int num_perspective_change;			// 视角变化参数变量
	int num_perspective_radius;			// 视角半径参数变量
	int num_a;							// 间隔变量,除数变量
	int num_d;							// 余数变量,实现旋转
	int num_cantilever[3];				// 悬臂数量
	int flag_cantilever;				// 悬臂数量编号
	Node1 boxm[30];						// 按钮,预制 30 个	
};

// 悬臂预测函数
void Gary::check ()
{
	int i, k;
	// 悬臂预测
	// 每一圈所含点数
	num_b = double (num_a) / 2.0 / pi;
	// 四舍五入,保留小数
	if (10.0 * (num_b - double (int (num_b))) >= 5) { num_b = 1.0 - (num_b - double (int (num_b))); }
	else { num_b = num_b - double (int (num_b)); }
	// 点数的存储
	num_e = num_b;
	// 临时变量
	int k1;
	// 初始化
	num_d = 0;
	k1 = 1;
	// 寻找结果维度,当前是三维
	for (i = 0; i < 3; i++)
	{
		// 单调连续递增
		for (k = k1;; k++)
		{
			// 点数乘圈数,判断是否接近整数
			num_c = num_b * double (k);
			// 四舍五入,保留小数
			if (10.0 * (num_c - double (int (num_c))) >= 5.0) { num_c = 1.0 - (num_c - double (int (num_c))); }
			else { num_c = num_c - double (int (num_c)); }
			// 判断是否接近整数,并与上一维结果比较
			if (num_c <= num_reduction_multiple * num_e)
			{
				// 存储
				num_cantilever[i] = k;
				// 递增
				k1 = k + 1;
				// 覆盖
				num_e = num_c;
				// 退出
				break;
			}
		}
	}
}

// 绘制螺旋线函数
void Gary::draw_helical_line ()
{
	int k, i;
	double j;
	int exit3 = 0;

	// 清空绘制区
	setfillcolor (BLACK);
	fillrectangle (0, 0, 400, 400);
	// 初始点
	j = 4;

	// 螺旋线绘制
	while (exit3 == 0)
	{
		// 第 j 个点
		j++;
		i = int (j);
		// 素数判断
		if (num_a == 0)
		{
			for (k = 2; k <= sqrt (j); k++)
			{
				if (i % k == 0)
				{
					k = 0;
					break;
				}
			}
		}
		// 余数判断
		else if (num_a != 0)
		{
			k = 1;
			if (i % num_a != num_d)k = 0;
		}
		// 判定,并绘制
		if (k != 0)
		{
			// 从界面中心,绘制阿基米德螺旋线
			putpixel (int(200.0 + (num_I + 1.0 / num_size * j) * cos (j)), int(200.0 + (num_I + 1.0 / num_size * j) * sin (j)), WHITE);
		}
		// 达到设定半径则结束
		if ((num_I + 1.0 / num_size * j) >= num_perspective_radius)
		{
			exit3 = 1;
			break;
		}
	}
}

// 场景绘制函数
void Gary::draw_scene ()
{
	TCHAR s[15];
	int i, j;
	// 背景绘制
	setbkcolor (WHITE);
	cleardevice ();
	setfillcolor (WHITE);
	// 主界面
	fillrectangle (0, 0, 650, 400);
	// 根据按钮数量绘制
	setfillcolor (WHITE);
	settextcolor (BLACK);
	for (i = 0; i < num_button; i++)
	{
		// 边框
		fillrectangle (boxm[i].posx1, boxm[i].posy1, boxm[i].posx2, boxm[i].posy2);
		// 文字
		outtextxy (boxm[i].posx1 + (boxm[i].posx2 - boxm[i].posx1) / 2 - textwidth (boxm[i].text) / 2, boxm[i].posy1 + 4, boxm[i].text);
	}

	// 设置参数
	setbkcolor (WHITE);
	settextcolor (BLACK);
	setlinecolor (BLACK);

	// 变量绘制
	j = 25;
	// 倍数螺旋线的倍数
	i = 1;
	_stprintf_s (s, _T ("%0.0d"), num_a);
	outtextxy (boxm[i].posx1 + (boxm[i].posx2 - boxm[i].posx1) / 2 - textwidth (boxm[i].text) / 2, boxm[i].posy1 + j, s);
	// 视角变化的跨度
	i = 2;
	_stprintf_s (s, _T ("%0.0d"), num_perspective_change);
	outtextxy (boxm[i].posx1 + (boxm[i].posx2 - boxm[i].posx1) / 2 - textwidth (boxm[i].text) / 2, boxm[i].posy1 + j, s);
	// 视角半径的大小
	i = 3;
	_stprintf_s (s, _T ("%0.0d"), num_perspective_radius);
	outtextxy (boxm[i].posx1 + (boxm[i].posx2 - boxm[i].posx1) / 2 - textwidth (boxm[i].text) / 2, boxm[i].posy1 + j, s);
	// 当前视角尺寸相对参数
	i = 4;
	_stprintf_s (s, _T ("%0.0f"), num_size);
	outtextxy (boxm[i].posx1 + (boxm[i].posx2 - boxm[i].posx1) / 2 - textwidth (boxm[i].text) / 2, boxm[i].posy1 + j, s);
	// 迭代倍数
	i = 6;
	_stprintf_s (s, _T ("%0.2f"), num_reduction_multiple);
	outtextxy (boxm[i].posx1 + (boxm[i].posx2 - boxm[i].posx1) / 2 - textwidth (boxm[i].text) / 2, boxm[i].posy1 + j, s);
	// 预测悬臂数量
	i = 8;
	_stprintf_s (s, _T ("%0.0d"), num_cantilever[flag_cantilever]);
	outtextxy (boxm[i].posx1 + (boxm[i].posx2 - boxm[i].posx1) / 2 - textwidth (boxm[i].text) / 2, boxm[i].posy1 + j, s);
}

// 初始化函数
void Gary::initialization ()
{
	// 按钮的初始化
	num_button = 10;

	int i;
	// 按钮坐标
	for (i = 0; i < 10; i++)
	{
		boxm[i].posx1 = 410 + 120 * (i % 2);
		boxm[i].posy1 = 25 + 75 * (i / 2);
		boxm[i].posx2 = 520 + 120 * (i % 2);
		boxm[i].posy2 = 75 + 75 * (i / 2);
	}

	// 按钮内容
	boxm[0].text = _T ("素数螺旋线");	boxm[1].text = _T ("倍数螺旋线");
	boxm[2].text = _T ("变化速率");		boxm[3].text = _T ("生成半径");
	boxm[4].text = _T ("视角尺寸");		boxm[5].text = _T ("截图");
	boxm[6].text = _T ("迭代倍率");		boxm[7].text = _T ("重置");
	boxm[8].text = _T ("悬臂数量");		boxm[9].text = _T ("退出");

	// 参数初始化
	num_a = 0;							// 倍数螺旋线的倍数
	num_perspective_change = 10;		// 视角远近变化速率
	num_perspective_radius = 200;		// 视角半径
	num_reduction_multiple = 0.5;		// 悬臂预测中的迭代倍数
	flag_cantilever = 0;				// 预测悬臂数量编号
	num_size = 11;						// 视角尺寸
	num_I = 1;							// 视角尺寸

	// 绘制参数初始化
	setlinecolor (BLACK);
	setlinestyle (PS_SOLID, 1);
	settextstyle (20, 0, _T ("Consolas"));
	// 第一次绘制
	check ();
	draw_scene ();
	draw_helical_line ();
}

// 窗口主视角函数,获取用户操作
void Gary::move ()
{
	// 鼠标定义
	ExMessage m;
	TCHAR ss[10];
	int i;
	float j;
	char h;
	exit_move = 0;
	while (exit_move == 0)
	{
		// 键盘信息
		if (_kbhit ())
		{
			h = _getch ();
			switch (h)
			{
					// 拉远视角
			case 'w': {if (num_size + num_perspective_change <= 0xffffff) { num_size += num_perspective_change; }break; }
					// 拉近视角
			case 's': {if (num_size - num_perspective_change > 1) { num_size -= num_perspective_change; }break; }
					// 逆时针旋转
			case 'a': {num_d++; if (num_d == num_a) { num_d = 0; }break; }
					// 顺时针旋转
			case 'd': {if (num_d == 0) { num_d = num_a; }num_d--; break; }
			default:break;
			}
			draw_scene ();
			draw_helical_line ();
		}
		// 鼠标信息
		if (peekmessage (&m, EM_MOUSE | EM_KEY))
		{
			// 左键单击判断
			if (m.message == WM_LBUTTONDOWN)
			{
				// 判断是否点击了按钮
				for (i = 0; i < num_button; i++)
				{
					if (m.x > boxm[i].posx1 && m.y > boxm[i].posy1 && m.x < boxm[i].posx2 && m.y < boxm[i].posy2)
					{
						break;
					}
				}
				// 点击矩形按钮
				switch (i)
				{
				// 素数螺旋线:num_a
				case 0:
				{
					num_a = 0;
					// 不预测悬臂数量
					for (i = 0; i < 3; i++)
					{
						num_cantilever[i] = 0;
					}
					draw_scene ();
					break;
				}

				// 倍数螺旋线:num_a
				case 1:
				{
					// 输入
					InputBox (ss, 10, _T ("输入倍数(1 ~ 9999)"));
					_stscanf_s (ss, _T ("%d"), &i);
					if (i > 0 && i <= 9999)
					{
						num_a = i;
						// 悬臂预测参数编号初始化
						flag_cantilever = 0;
					}
					else { MessageBox (hOut, _T ("输入错误,不在范围内"), _T ("来自小豆子的提醒"), MB_OK); }
					check ();
					draw_scene ();
					break;
				}
				// 变化速率:num_perspective_change
				case 2:
				{
					InputBox (ss, 10, _T ("输入变化速率(1 ~ 9999)"));
					_stscanf_s (ss, _T ("%d"), &i);
					if (i > 0 && i <= 9999) { num_perspective_change = i; }
					else { MessageBox (hOut, _T ("输入错误,不在范围内"), _T ("来自小豆子的提醒"), MB_OK); }
					draw_scene ();
					break;
				}
				// 视角半径:num_perspective_radius
				case 3:
				{
					InputBox (ss, 10, _T ("输入视角半径(20 ~ 280)"));
					_stscanf_s (ss, _T ("%d"), &i);
					if (i >= 20 && i <= 280) { num_perspective_radius = i; }
					else { MessageBox (hOut, _T ("输入错误,不在范围内"), _T ("来自小豆子的提醒"), MB_OK); }
					draw_scene ();
					break;
				}
				// 视角尺寸:num_size
				case 4:
				{
					InputBox (ss, 10, _T ("输入视角尺寸(1 ~ 100000)"));
					_stscanf_s (ss, _T ("%d"), &i);
					if (i >= 1 && i <= 100000) { num_size = i; }
					else { MessageBox (hOut, _T ("输入错误,不在范围内"), _T ("来自小豆子的提醒"), MB_OK); }
					draw_scene ();
					break;
				}
				// 截图
				case 5:
				{
					saveimage (_T ("image.png"));
					break;
				}
				// 迭代倍数:num_reduction_multiple
				case 6:
				{
					InputBox (ss, 10, _T ("输入迭代倍数(0 ~ 1)"));
					_stscanf_s (ss, _T ("%f"), &j);
					if (j > 0 && j <= 1) { num_reduction_multiple = j; }
					else { MessageBox (hOut, _T ("输入错误,不在范围内"), _T ("来自小豆子的提醒"), MB_OK); }
					check ();
					draw_scene ();
					break;
				}
				// 重置
				case 7:
				{
					exit_move = 1;
					break;
				}
				// 悬臂数量:num_cantilever[flag_cantilever]
				case 8:
				{
					// 悬臂预测参数编号变化
					flag_cantilever++;
					if (num_cantilever[flag_cantilever] == 3)
					{
						num_cantilever[flag_cantilever] = 0;
					}
					draw_scene ();
					break;
				}
				// 退出
				case 9:
				{
					exit_move = 1;
					exit_carry = 1;
					break;
				}
				default:break;
				}
				draw_helical_line ();
			}
		}
	}
}

// 主进程
void Gary::carry ()
{
	// 窗口定义
	hOut = initgraph (651, 401);
	SetWindowText (hOut, _T ("素数螺旋线"));
	// 进程控制
	exit_carry = 0;
	while (exit_carry == 0)
	{
		initialization ();
		move ();
	}
	closegraph ();
}

// 主函数
int main (void)
{
	Gary G;
	G.carry ();
	return 0;
}

参考资料

视频:为什么素数会形成这些螺旋

文章:极坐标表示 5000 到 50000 之间的素数为什么会形成一条螺旋线

添加评论