可视化 π
说明
运行后观看可视化 π 的动画,动画结束后鼠标拖动可以拖动地图,鼠标滚轮可以缩放地图,回车退出。
这个可视化 π 是 π 的下一位数是奇数左转是偶数右转,一开始方向向下,第一位是 3,奇数,于是方向变成下的左边也就是右,并且移动一格从(0, 0)来到(1, 0),再下一位是 1,奇数,方向变成右的左边也就是上,并且移动一格从(1, 0)来到(1, 1),以此类推。
同时这个程序路径和节点的宽高的像素值可以设置,像素值越大放大后越清晰,像素值的比值也可以调为自己喜欢的比值,但是像素值越高绘画越慢,观看时间越长。
计算 π 的算法讲解
如果硬算 π 到小数点后一万位会花 7、8 秒的时间,影响可视化体验,所以可以设置一个辅助判断精度,这里用到的 π 级数是 π / 2 = 1 + 1 / 3 + 1 / 3 * 2 / 5 + 1 / 3 * 2 / 5 *3 / 7 + ……
越往后面要加的数越小,会逐渐变成 0.00000000……,当下一个要加的数过 n 位才有数字时,可以确定 n - k 的那个已经计算出来的值就是 π 的第 n - k 位,不过 n - k 越大则 n 越大,因为之前很多位的处理造成的精度缺失都集中作用在后面的位数上,比如 1 / 3 = 0.33333333……,2 / 3 = 0.66666……,1 / 3 + 2 / 3 = 0.99999……(有限不循环小数),那个缺失的精度就在最后那几位上,只要我们选择合适的 k 使得精度 n + k 能使得 π 后第 n 位值正确,就可以让下一个加进来的 0.00000……的第一个有效位位数减 k 为 π 已确定的位数,然后根据这个位数的奇偶判断当前地图向左向右。精度为小数点后一万位只用让 k = 10 就行了,其他精度就得另行测试。
于是我们可以一位一位地求 π 了。
这个项目基本解决大数据计算的精度问题,而且速度也是很可观的,以后算一万的阶乘也可以用到这个项目用到的方法,受益匪浅。
程序截图
代码实现
////////////////////////////////////////////
// 程序:可视化 π
// 作者:我想做三国志
// 编译环境:Visual Studio 2019,EasyX_20220901
// 编写日期:2023-2-22
#include <graphics.h>
const int WIDTH = 640; // 屏幕宽度
const int HEIGHT = 480; // 屏幕高度
const int UP = 0; // 上方向
const int DOWN = 2; // 下方向
const int LEFT = 1; // 左方向
const int RIGHT = 3; // 右方向
const int NONE = 4; // 空方向
const int COLNUM = 12; // 颜色数量,表示经过次数最大值
const int NODEWIDTH = 11; // 节点宽度
const int NODEHEIGHT = 11; // 节点高度
const int PATHWIDTH = 7; // 路径宽度
const int PATHLEN = 4; // 路径长度
const int adddigit = 10; // 辅助精度
const int LENGTH = 10000; // 精度,小数点后位数
char temp[LENGTH + adddigit + 1]; // 下一个要加的数
char last[LENGTH + adddigit + 1]; // 上一个数,累加至今的数
COLORREF ColArr[COLNUM + 2]; // 颜色数组,图例和节点用
// 初始化颜色数组,用于图例
void InitColArr(COLORREF* ColArr, int len)
{
ColArr[0] = BLACK;
ColArr[len - 1] = WHITE;
for (int i = 1; i < len - 1; i++)
{
ColArr[i] = HSLtoRGB(240 - (i - 1) * 20, 1, 0.5);
}
}
// 画节点
void DrawNode(DWORD* buf, int LeftEst, int RightEst, int UpEst, int DownEst, int x, int y, char element)
{
setfillcolor(ColArr[element]);
solidrectangle(x * (NODEWIDTH + PATHLEN), y * (NODEHEIGHT + PATHLEN), x * (NODEWIDTH + PATHLEN) + NODEWIDTH - 1, y * (NODEHEIGHT + PATHLEN) + NODEHEIGHT - 1);
if (element == COLNUM + 1)
{
int radius = min(NODEHEIGHT, NODEWIDTH) / 2;
setfillcolor(RED);
solidcircle(x * (NODEWIDTH + PATHLEN) + radius, y * (NODEHEIGHT + PATHLEN) + radius, radius);
}
}
// 画路径
void DrawPath(DWORD* buf, int LeftEst, int RightEst, int UpEst, int DownEst, int x, int y, char Dir)
{
int beginPX = x * (PATHLEN + NODEWIDTH), beginPY = y * (NODEHEIGHT + PATHLEN);
int height = PATHWIDTH, width = PATHWIDTH;
switch (Dir)
{
case UP: beginPX += (NODEWIDTH - PATHWIDTH) / 2; beginPY -= PATHLEN; height = PATHLEN; break;
case DOWN: beginPX += (NODEWIDTH - PATHWIDTH) / 2; beginPY += NODEHEIGHT; height = PATHLEN; break;
case LEFT: beginPY += (NODEHEIGHT - PATHWIDTH) / 2; beginPX -= PATHLEN; width = PATHLEN; break;
case RIGHT: beginPY += (NODEHEIGHT - PATHWIDTH) / 2; beginPX += NODEWIDTH; width = PATHLEN; break;
}
setfillcolor(WHITE);
solidrectangle(beginPX, beginPY, beginPX + width - 1, beginPY + height - 1);
}
// 画地图
void DrawMap(DWORD* buf, int LeftEst, int RightEst, int UpEst, int DownEst, char* AuxiliaryMap, char* PathHoriMap, char* PathVertMap)
{
// 画节点
for (int i = 0; i < DownEst - UpEst + 1; i++)
for (int j = 0; j < RightEst - LeftEst + 1; j++)
DrawNode(buf, LeftEst, RightEst, UpEst, DownEst, j, i, AuxiliaryMap[i * (RightEst - LeftEst + 1) + j]);
// 画通向右边的道路
for (int i = 0; i < DownEst - UpEst + 1; i++)
for (int j = 0; j < RightEst - LeftEst; j++)
if (PathHoriMap[i * (RightEst - LeftEst) + j] == 1)
DrawPath(buf, LeftEst, RightEst, UpEst, DownEst, j, i, RIGHT);
// 画通向下边的道路
for (int i = 0; i < DownEst - UpEst; i++)
for (int j = 0; j < RightEst - LeftEst + 1; j++)
if (PathVertMap[i * (RightEst - LeftEst + 1) + j] == 1)
DrawPath(buf, LeftEst, RightEst, UpEst, DownEst, j, i, DOWN);
}
// 画一条说明
void DrawReference(TCHAR* arr, int& addHeight, int linesize, int margin)
{
int height = textheight(arr);
int width = textwidth(arr);
outtextxy(margin, addHeight + margin, arr);
rectangle(margin - linesize / 2, addHeight + margin - linesize / 2, margin + width + linesize / 2, addHeight + margin + height + linesize / 2);
addHeight += height + margin;
}
// 画图例
void DrawGraphicSymbol(int confirmDig, int presentDis, int farestDis, double aveNum, int* timesArr, COLORREF* colArr, int len)
{
int addHeight = 0;
int addWidth = 0;
int margin = 5;
int height = 0;
int width = 0;
int maxWidth = 0;
int linesize = 4;
settextcolor(WHITE);
settextstyle(12, 0, _T("宋体"));
setlinecolor(BLACK);
setlinestyle(PS_SOLID, linesize);
TCHAR arr[128];
_stprintf_s(arr, _T("小数位数:%d"), confirmDig);
DrawReference(arr, addHeight, linesize, margin);
_stprintf_s(arr, _T("当前距离:%d"), presentDis);
DrawReference(arr, addHeight, linesize, margin);
_stprintf_s(arr, _T("最远距离:%d"), farestDis);
DrawReference(arr, addHeight, linesize, margin);
_stprintf_s(arr, _T("平均数字:%.2lf"), aveNum);
DrawReference(arr, addHeight, linesize, margin);
int AllTimesNum = 0;
for (int i = 0; i < len; i++)AllTimesNum += timesArr[i];
addHeight = 0;
for (int i = 0; i < len; i++)
{
if (timesArr[i] == 0) break;
if (i == len - 1) _stprintf_s(arr, _T("经过>=%d次 %.1f%c"), i + 1, timesArr[i] / (double)AllTimesNum * 100, L'%');
else _stprintf_s(arr, 128, _T("经过%d次 %.1f%c"), i + 1, timesArr[i] / (double)AllTimesNum * 100, L'%');
settextcolor(colArr[i]);
height = textheight(arr);
width = textwidth(arr);
maxWidth = max(width, maxWidth);
if (addHeight + height > HEIGHT)
{
addWidth = maxWidth + margin;
addHeight = 0;
}
outtextxy(WIDTH - margin - addWidth - width, addHeight + margin, arr);
rectangle(WIDTH - margin - addWidth - width - linesize / 2, addHeight + margin - linesize / 2,
WIDTH - margin - addWidth + linesize / 2, addHeight + margin + height + linesize / 2);
addHeight += height + margin;
}
}
// 主函数
int main()
{
InitColArr(ColArr, COLNUM + 2);
initgraph(WIDTH, HEIGHT);
setbkcolor(BLACK);
BeginBatchDraw();
HDC mainDC = GetImageHDC(NULL); // 主窗口的 DC
SetStretchBltMode(mainDC, HALFTONE); // 设置拉伸贴图模式
int beginX = 0, beginY = 0;
int LeftEst = -5, RightEst = 5, UpEst = -5, DownEst = 5;
int showWidth = WIDTH - 50, showHeight = HEIGHT - 50;
char* AuxiliaryMap = new char[(RightEst - LeftEst + 1) * (DownEst - UpEst + 1)];
char* PathHoriMap = new char[(RightEst - LeftEst) * (DownEst - UpEst + 1)];
char* PathVertMap = new char[(DownEst - UpEst) * (RightEst - LeftEst + 1)];
memset(AuxiliaryMap, 0, sizeof(char) * ((RightEst - LeftEst + 1) * (DownEst - UpEst + 1)));
memset(PathHoriMap, 0, sizeof(char) * (RightEst - LeftEst) * (DownEst - UpEst + 1));
memset(PathVertMap, 0, sizeof(char) * (DownEst - UpEst) * (RightEst - LeftEst + 1));
AuxiliaryMap[(beginY + abs(UpEst)) * (RightEst - LeftEst + 1) + beginX + abs(LeftEst)] = COLNUM + 1;
// 建立 img 对象
IMAGE img((PATHLEN + NODEWIDTH) * (RightEst - LeftEst + 1) - PATHLEN, (PATHLEN + NODEHEIGHT) * (DownEst - UpEst + 1) - PATHLEN);
HDC imgDC = GetImageHDC(&img);
DWORD* pBuf = GetImageBuffer(&img);
memset(pBuf, 0, sizeof(DWORD) * img.getwidth() * img.getheight());
SetWorkingImage(&img);
DrawMap(pBuf, LeftEst, RightEst, UpEst, DownEst, AuxiliaryMap, PathHoriMap, PathVertMap);
// 贴图到主窗口
double times = min(showHeight / (double)img.getheight(), showWidth / (double)img.getwidth());
int BeginShowPX = (WIDTH - showWidth) / 2 + (showWidth - img.getwidth() * times) / 2;
int BeginShowPY = (HEIGHT - showHeight) / 2 + (showHeight - img.getheight() * times) / 2;
SetWorkingImage(NULL);
cleardevice();
StretchBlt(mainDC, BeginShowPX, BeginShowPY, (int)(img.getwidth() * times + 0.5), (int)(img.getheight() * times + 0.5),
imgDC, 0, 0, img.getwidth(), img.getheight(), SRCCOPY);
FlushBatchDraw();
memset(temp, 0, sizeof(temp));
memset(last, 0, sizeof(last));
int a = 1, b = 3;
temp[0] = 2;
last[0] = 2;
int Rnt = 1;
int ConfirmDig = 0;
char Dir = DOWN;
int farestDis = 0;
int allNum = 0;
int timesArr[COLNUM + 1] = { 0 }; // 多的一个为起点准备
while (Rnt)
{
// temp * a
int C = 0;
for (int i = LENGTH + adddigit; i >= 0; i--)
{
C = temp[i] * a + C;
temp[i] = C % 10;
C = C / 10;
}
// temp / b
C = 0;
for (int i = 0; i <= LENGTH + adddigit; i++)
{
C *= 10;
int middle = (temp[i] + C) / b;
C = (temp[i] + C) % b;
temp[i] = middle;
}
// last + temp
C = 0;
Rnt = 0;
int lastCountDig = LENGTH + adddigit;
for (int i = LENGTH + adddigit; i >= 0; i--)
{
C = last[i] + temp[i] + C;
last[i] = C % 10;
C = C / 10;
if (temp[i] != 0)lastCountDig = i;
Rnt |= temp[i];
}
a += 1;
b += 2;
if (lastCountDig >= ConfirmDig + adddigit)
{
allNum += last[ConfirmDig]; // 用于计算平均数
if (last[ConfirmDig] % 2)// 奇数左转
{
Dir = (Dir + 1) % 4;
}
else// 偶数右转
{
Dir = (Dir + 3) % 4;
}
int newX = beginX, newY = beginY;
switch (Dir)
{
case UP: newY--; break;
case DOWN: newY++; break;
case LEFT: newX--; break;
case RIGHT: newX++; break;
}
if (abs(newY) + abs(newX) > farestDis)
farestDis = abs(newY) + abs(newX);
int oriX = abs(LeftEst), oriY = abs(UpEst), oriH = DownEst - UpEst + 1, oriW = RightEst - LeftEst + 1;
bool isRefresh = false;
if (newX < LeftEst)
{
LeftEst--;
isRefresh = true;
}
else if (newX > RightEst)
{
RightEst++;
isRefresh = true;
}
if (newY < UpEst)
{
UpEst--;
isRefresh = true;
}
else if (newY > DownEst)
{
DownEst++;
isRefresh = true;
}
SetWorkingImage(&img);
if (isRefresh)
{
char* tempAuxiliaryMap = new char[(RightEst - LeftEst + 1) * (DownEst - UpEst + 1)];
char* tempPathHoriMap = new char[(RightEst - LeftEst) * (DownEst - UpEst + 1)];
char* tempPathVertMap = new char[(DownEst - UpEst) * (RightEst - LeftEst + 1)];
memset(tempAuxiliaryMap, 0, sizeof(char) * ((RightEst - LeftEst + 1) * (DownEst - UpEst + 1)));
memset(tempPathHoriMap, 0, sizeof(char) * (RightEst - LeftEst) * (DownEst - UpEst + 1));
memset(tempPathVertMap, 0, sizeof(char) * (DownEst - UpEst) * (RightEst - LeftEst + 1));
int beginXX = abs(LeftEst) - oriX, beginYY = abs(UpEst) - oriY;
for (int i = 0; i < oriH; i++)
for (int j = 0; j < oriW; j++)
tempAuxiliaryMap[(beginYY + i) * (RightEst - LeftEst + 1) + beginXX + j] = AuxiliaryMap[i * oriW + j];
for (int i = 0; i < oriH; i++)
for (int j = 0; j < oriW - 1; j++)
tempPathHoriMap[(beginYY + i) * (RightEst - LeftEst) + beginXX + j] = PathHoriMap[i * (oriW - 1) + j];
for (int i = 0; i < oriH - 1; i++)
for (int j = 0; j < oriW; j++)
tempPathVertMap[(beginYY + i) * (RightEst - LeftEst + 1) + beginXX + j] = PathVertMap[i * oriW + j];
delete[] AuxiliaryMap;
delete[] PathHoriMap;
delete[] PathVertMap;
AuxiliaryMap = tempAuxiliaryMap;
PathHoriMap = tempPathHoriMap;
PathVertMap = tempPathVertMap;
img.Resize((PATHLEN + NODEWIDTH) * (RightEst - LeftEst + 1) - PATHLEN, (PATHLEN + NODEHEIGHT) * (DownEst - UpEst + 1) - PATHLEN);
imgDC = GetImageHDC(&img);
pBuf = GetImageBuffer(&img);
memset(pBuf, 0, sizeof(DWORD) * img.getwidth() * img.getheight());
DrawMap(pBuf, LeftEst, RightEst, UpEst, DownEst, AuxiliaryMap, PathHoriMap, PathVertMap);
times = min(showHeight / (double)img.getheight(), showWidth / (double)img.getwidth());
BeginShowPX = (WIDTH - showWidth) / 2 + (showWidth - img.getwidth() * times) / 2;
BeginShowPY = (HEIGHT - showHeight) / 2 + (showHeight - img.getheight() * times) / 2;
}
if (AuxiliaryMap[(newY + abs(UpEst)) * (RightEst - LeftEst + 1) + newX + abs(LeftEst)] < COLNUM)
{
AuxiliaryMap[(newY + abs(UpEst)) * (RightEst - LeftEst + 1) + newX + abs(LeftEst)]++;
timesArr[AuxiliaryMap[(newY + abs(UpEst)) * (RightEst - LeftEst + 1) + newX + abs(LeftEst)] - 1]++; // 记录节点经过该次数的数量
}
DrawNode(pBuf, LeftEst, RightEst, UpEst, DownEst, newX + abs(LeftEst), newY + abs(UpEst),
AuxiliaryMap[(newY + abs(UpEst)) * (RightEst - LeftEst + 1) + newX + abs(LeftEst)]);
DrawPath(pBuf, LeftEst, RightEst, UpEst, DownEst, beginX + abs(LeftEst), beginY + abs(UpEst), Dir);
switch (Dir)
{
case UP: PathVertMap[(newY + abs(UpEst)) * (RightEst - LeftEst + 1) + newX + abs(LeftEst)] = 1; break;
case DOWN: PathVertMap[(beginY + abs(UpEst)) * (RightEst - LeftEst + 1) + beginX + abs(LeftEst)] = 1; break;
case LEFT: PathHoriMap[(newY + abs(UpEst)) * (RightEst - LeftEst) + newX + abs(LeftEst)] = 1; break;
case RIGHT: PathHoriMap[(beginY + abs(UpEst)) * (RightEst - LeftEst) + beginX + abs(LeftEst)] = 1; break;
}
beginX = newX;
beginY = newY;
ConfirmDig++;
SetWorkingImage(NULL);
cleardevice();
StretchBlt(mainDC, BeginShowPX, BeginShowPY, (int)(img.getwidth()* times + 0.5), (int)(img.getheight()* times + 0.5),
imgDC, 0, 0, img.getwidth(), img.getheight(), SRCCOPY);
DrawGraphicSymbol(ConfirmDig - 1, abs(beginX) + abs(beginY), farestDis, allNum / (double)ConfirmDig, timesArr, (COLORREF*)ColArr + 1, COLNUM);
FlushBatchDraw();
Sleep(1);
}
}
delete[] AuxiliaryMap;
delete[] PathHoriMap;
delete[] PathVertMap;
double offsetX = 0, offsetY = 0;
bool isExit = false;
bool isPress = false;
int oriX = 0, oriY = 0;
double oriTimes = times;
ExMessage msg;
SetWorkingImage(NULL);
flushmessage(EX_MOUSE | EX_KEY);
while (!isExit)
{
msg = getmessage(EX_MOUSE | EX_KEY);
if (msg.message == WM_KEYDOWN && msg.vkcode == VK_RETURN)
isExit = true;
else if (msg.message == WM_MOUSEWHEEL)
{
double Addtimes = 1;
if (msg.wheel > 0)Addtimes = 1.1;
else if (msg.wheel < 0)Addtimes = 0.9;
times = max(0.25, min(min(HEIGHT / (NODEHEIGHT * 5), WIDTH / (NODEWIDTH * 5)), times * Addtimes));
BeginShowPX = (WIDTH - img.getwidth() * times) / 2;
BeginShowPY = (HEIGHT - img.getheight() * times) / 2;
msg.wheel = 0;
}
else
{
if (!isPress && msg.lbutton)
{
isPress = true;
oriX = msg.x, oriY = msg.y;
}
else if (isPress && msg.lbutton)
{
offsetX += (msg.x - oriX), offsetY += (msg.y - oriY);
oriX = msg.x, oriY = msg.y;
}
else if (isPress && !msg.lbutton)
{
isPress = false;
}
}
cleardevice();
StretchBlt(mainDC, BeginShowPX + offsetX, BeginShowPY + offsetY, (int)(img.getwidth()* times + 0.5), (int)(img.getheight()* times + 0.5),
imgDC, 0, 0, img.getwidth(), img.getheight(), SRCCOPY);
DrawGraphicSymbol(ConfirmDig - 1, abs(beginX) + abs(beginY), farestDis, allNum / (double)ConfirmDig, timesArr, (COLORREF*)ColArr + 1, COLNUM);
FlushBatchDraw();
}
closegraph();
return 0;
}
特别感谢
感谢喵爪大佬提供的计算 π 的算法,同时感谢村长提供了这个对我来说挺有意义的项目,还有村长对这个项目的帮助。
添加评论
取消回复