话说前几天 Panda 在网上看到一个帖子,有人问怎么算 万的阶乘。一开始 Panda 觉得挺无聊的,一个数学符号就能搞定的事情,干嘛要算呢?用数学符号表示不就是 嘛。
这时Ian再次对Panda投来了鄙视的目光,说道:“你难道不知道高精度计算也是信息学里面一个很重要的内容吗?”
听 Ian 这么一说,Panda大概明白了,就是说阶乘的增长很快,用一般的数据类型根本存不下。例如
左右滑动查看一共有 位,已经远超 unsigned long long 甚至是__uint128_t 的最大取值范围了,__uint128_t 类型是目前 c++编译器支持的取值范围最大的内置整数类型,有 128 位,因此最大可以等于
左右滑动查看不过才 位。
这么看来这个问题还是挺有意思的,为了早日摆脱自己信息学蒟蒻的称号,Panda 决定好好研究一下。
1、 斯特林公式
研究阶乘不得不说的就是斯特林(Stirling)公式,可以用来求阶乘的近似值。斯特林公式其实应该叫斯特林级数,和 函数有关,用它算阶乘的话前几项长这样
左右滑动查看我们一般见到的斯特林公式,只取了第一项,也就是
用斯特林公式估算阶乘位数的方法也是信息学必学的内容。(幸亏我知道,不然又要被Ian鄙视了)
斯特林公式虽然不能帮我们算出阶乘的精确值,但是对于分析算法还是很有帮助的。
2、 boost 库
简单做了一下调研,居然有一个半官方的 c++库,叫 boost,里面有内置的高精度整数类型 cpp_int,照着示例快乐的写完了简单的测试代码。
上面的代码很简单,就是输入一个正整数 n,然后计算阶乘、统计时间,并且将阶乘输出到一个文本文件中(直接输出在屏幕上实在太长了)
可以看到用单线程计算的话, 万的阶乘只用了 s,但是计算加上输出却用了接近 s。
一开始 Panda 怀疑自己的硬盘是不是坏了
但是看了一下输出的文件,只有 446KB,所以
那就是输出确实用了这么长的时间。
其实 cpp_int 计算用的 2 进制,但是输出时却是 10 进制,因此先要进制转换才能输出,而进制转换非常耗时(后面会分析)。
本来想投机取巧解决这个问题,但是现在脸有点疼,看来要另想办法。
3、 十亿进制高精度计算
其实在一般的信息学的教材里[1,2],都有一种 10 进制高精度整数计算的方法,就是用一个 int 类型的数组表示高精度整数,一个元素代表一位。
可想而知,这种做法非常浪费,int 类型的取值范围是 , 进制高精度计算每一位仅需要 十个数,不过好处就是输出的时候不需要进制转换。
因此Panda想到了一种兼顾两者的办法,就是采用十亿进制(你没听错就是十亿进制)
还是用一个 int 类型的数组,一个元素代表一位,但是每一位的取值范围从 。
好了,废话不多说了,先给出高精度自然数类的主要代码
//头文件,定义成员变量、函数以及操作符重载
//输出,通过 setfill 和 setw 函数保证输出正确
//高精度乘以低精度,用一个 long long 型的整数 r 辅助进位
//删除前导 0,做四则运算有时会有多余的前导 0
//计算位数,计算数在十进制下的位数
//求阶乘有一些本次内容用不到的代码没有贴出来,后面再和大家分享。
//最后是主函数
主函数比较简单,就是输入 ,计算阶乘,将结果输出为文件,在控制台里输出用时和阶乘的位数。 万的阶乘有 位。
可以看到这种十亿进制的方法,如果用单线程计算的话, 万的阶乘用了 s,而 cpp_int 只需要 s。
但是由于不需要做进制转换,输出的时间几乎可以忽略。因此计算加上输出的用时反而比 cpp_int 少了 40%。
取得了一些进展,但是 Panda 的心情反而更加非常复杂,因为这个方法的时间复杂度真的不好!!!
4、时间复杂度分析
不是说 Panda 写的高精度计算的类时间复杂度不好。
Panda的意思是在算 万阶乘的时候,从 一直乘到 万这样的方法,时间复杂度真的不好。
无论是Panda的方法还是 boost 库的代码,采用的方法都是先建立一个高精度数 ,然后用低精度数不断的去乘以这个高精度数。
我们来分析一下,高精度乘以低精度,时间复杂度是 , 是高精度数的长度。
根据斯特林公式,当 比较大的时候
因此计算 ,时间复杂度就是
cpp_int 如果要做进制转换,需要用高精度数不断的除以低精度数 (也可能是 的某次方),然后得到余数。
高精度数除以低精度数的时间复杂度也是 ,因此将 进制的 转为 进制,时间复杂度也是 。
按照这样的估算,用 cpp_int 计算阶乘并输出应该至少需要 万秒,也就是接近 个小时。用Panda自己写的高精类,也应该至少需要 万秒。
5、小结
从 万秒也就是 分钟降到 分钟以内,看似是个不可能完成的任务,肯定需要新的秘密武器。
不过今天内容写的够多了,这里先分享一下结果吧,最终Panda还是完成了这个挑战。
成功用 秒的时间算出了 万的阶乘,Panda还是用的自己 6 核的游戏本做的多线程并行计算(贫穷逼我只能去优化算法), 万的阶乘有 位。
Panda还成功挑战了 亿的阶乘,仅用了 秒。 亿的阶乘有 位。
究竟Panda找到什么办法完成这个挑战的呢,请期待下一期:
怎样在一分钟内算出一千万的阶乘(二):数论变换乘法。
如果觉得有趣或者对你有帮助的话,请关注“黑眼圈信息学”专栏,下期不见不散。
参考文献
[1] 林厚从. 信息学奥赛课课通(C++). 高等教育出版社
[2] 董永建. 信息学奥赛一本通(C++). 科学技术文献出版社