刷知乎的时候看到了这样一个提问《圆周率里是否包含所有可能的银行卡密码?》
这个问题看上去还挺有趣的,等价于圆周率里是否完整的包含000000-999999。
从数学角度上去证明貌似挺难的,根本不知道从何下手。还好现在是互联网时代,有计算机这样强大的工具。我们如果枚举出所有的可能性,就可以直接证明了。
π是无限不循环小数,但是我们如果取有限位出来,而这有限位又包含了000000-999999,那就可以证明结论成立了。
那么问题来了,怎么用计算机计算π的值呢,我们知道,一般在C语言里我们可以用double pi=acos(-1.0)或者double pi=atan(1.0)*4来表示π,由于计算机浮点数的精度是有限的,不可能精确地表示π。我们最多只能看到其后10位左右的精确值。假设π小数点后的每连续6位都是不同的,那我们也至少需要100000+6位π的精确值。所以,我们首先要能计算π的高精度值。
HDOJ上有一题就是算π的高精度值,题目链接,不过它只要求算到1500位,即使是这样,也让人头皮发麻,幸好有这么一个牛逼的网站存在,它叫做y-cruncher,专门计算π的高精度值的。是一个多线程计算π的程序。
- 22.4 trillion digits - November 2016 (Peter Trueb)
- 13.3 trillion digits - October 2014 (Sandon Van Ness "houkouonchi")
- 12.1 trillion digits - December 2013 (Shigeru Kondo)
- 10 trillion digits - October 2011 (Shigeru Kondo)
- 5 trillion digits - August 2010 (Shigeru Kondo)
上面是关于π计算的一些世界纪录。
没有超链接的那个是匿名的,后来作者还是把他公开了,这里有这么一个故事。
下面是这个网站作者的原话,下文中的我当然是指代作者。
“houkouonchi”和π:( 2018年3月14日)
对于那些一直关注Pi计算世界纪录的人,你会知道“houkouonchi”显然是一个假名。早在2014年,他以13.3万亿位数字创下了Pi世界纪录,他要求我不要透露他的真实姓名。他的理由:他不想别人通过他的脸书和个人电子邮件与他联系从而被打扰。
然而,houkouonchi在2015年不幸去世。
作为一个和我在互联网上联系的人,我近一年没有发现它的活动痕迹。此外,我也没有他的家人的联系信息。
在过去的两年中,我一直在考虑是否要透露他的真实姓名。一方面,他要求我不要透露他的名字。但另一方面,我强烈地渴望将他的名字写在他的世界纪录上。因为没有任何的联系信息,我一直无法联系到他的家人。也没有人去看他的电子邮件,因为我的所发的信息一直没有得到答复。
最后,我决定他的匿名原因不再适用。因此,我现在要为13.3万亿位数的记录命名。
他的名字是Sandon Van Ness。安息吧我的朋友。
言归正传,接下来我们可以去下载它的程序然后按照它程序上面的提示一步步运行,我选择运算出1亿位。如果不够,再继续加位数,幸好,事后证明,1亿位足矣。
程序最后跑出的数据存放在Pi - Dec - Chudnovsky.txt中。
接下来就是去枚举了。
从π的小数点后第一位开始,取其连续的6位数,构造出密码,然后用C++的STL库的map hash查询是否已经找到过这个密码了,没找到就标记找到了,并记录出现的位置。而且找到一个就统计一下已经找到的密码的个数。个数等于1000000就代表全找出来了,就结束程序。这样做的算法复杂度是O(n*log(m)),n是直到最后一个密码找到的字符串长度,m是密码个数。实践证明n是1千万的量级,m是1百万的量级。大概执行了10000000*log2(1000000)=200000000左右次,代码运行时间40s左右。
而如果直接按顺序构造密码,对于每次构造出的密码去π里面查询的话,效率非常低,算法复杂度是O(m*n),m=10^5,n=10^7。知乎上的那位答主用python写得代码效率很低。
接下来就是C++源代码了:
#include<bits/stdc++.h>
using namespace std;
int main()
{
map<string,int>mark;
freopen("Pi - Dec - Chudnovsky.txt","r",stdin);
freopen("res.txt","w",stdout);
string pi,cur;
int cnt=0;
cin>>pi;
for(int i=2;i<pi.size()-6;i++)
{
cur=pi.substr(i,6);
if(mark[cur]==0)
{
cout<<cur<<":"<<i<<"-"<<i+6<<endl;
mark[cur]=i;
cnt++;
}
if(cnt==1000000)
{
cout<<"end:"<<i<<endl;
break;
}
}
return 0;
}
最后的结果会保存在res.txt中。