4. linux调用文件计算阶乘前5项和_怎样在一分钟内算出1000万的阶乘(一):高精度计算...

话说前几天 Panda 在网上看到一个帖子,有人问怎么算  万的阶乘。一开始 Panda 觉得挺无聊的,一个数学符号就能搞定的事情,干嘛要算呢?用数学符号表示不就是  嘛。

518c0cad0cd333a1e39dba81382eeafb.png

这时Ian再次对Panda投来了鄙视的目光,说道:“你难道不知道高精度计算也是信息学里面一个很重要的内容吗?”

听 Ian 这么一说,Panda大概明白了,就是说阶乘的增长很快,用一般的数据类型根本存不下。例如

左右滑动查看

一共有  位,已经远超 unsigned long long 甚至是__uint128_t 的最大取值范围了,__uint128_t 类型是目前 c++编译器支持的取值范围最大的内置整数类型,有 128 位,因此最大可以等于

左右滑动查看

不过才  位。

这么看来这个问题还是挺有意思的,为了早日摆脱自己信息学蒟蒻的称号,Panda 决定好好研究一下。

1、 斯特林公式

研究阶乘不得不说的就是斯特林(Stirling)公式,可以用来求阶乘的近似值。斯特林公式其实应该叫斯特林级数,和  函数有关,用它算阶乘的话前几项长这样

左右滑动查看

我们一般见到的斯特林公式,只取了第一项,也就是

用斯特林公式估算阶乘位数的方法也是信息学必学的内容。(幸亏我知道,不然又要被Ian鄙视了)

斯特林公式虽然不能帮我们算出阶乘的精确值,但是对于分析算法还是很有帮助的。

2、 boost 库

简单做了一下调研,居然有一个半官方的 c++库,叫 boost,里面有内置的高精度整数类型 cpp_int,照着示例快乐的写完了简单的测试代码。

41f37e7c25d85245e13be86b99ba18f9.png
28f73dbcc81e551dbfaa812c4bc6656f.png

上面的代码很简单,就是输入一个正整数 n,然后计算阶乘、统计时间,并且将阶乘输出到一个文本文件中(直接输出在屏幕上实在太长了)

2c2083046cc82b15344b93866192e3d4.png

可以看到用单线程计算的话,  万的阶乘只用了 s,但是计算加上输出却用了接近 s。

一开始 Panda 怀疑自己的硬盘是不是坏了

970df7fe2bd52f1b0668c1c0f5e90387.png

但是看了一下输出的文件,只有 446KB,所以

a3d06a24cd5b2aa0574f52fd16f5bd05.png

那就是输出确实用了这么长的时间。

其实 cpp_int 计算用的 2 进制,但是输出时却是 10 进制,因此先要进制转换才能输出,而进制转换非常耗时(后面会分析)。

0b93b7e4737c9bc8f1f16a0bf7e24432.png

本来想投机取巧解决这个问题,但是现在脸有点疼,看来要另想办法。

3、 十亿进制高精度计算

其实在一般的信息学的教材里[1,2],都有一种 10 进制高精度整数计算的方法,就是用一个 int 类型的数组表示高精度整数,一个元素代表一位。

可想而知,这种做法非常浪费,int 类型的取值范围是 , 进制高精度计算每一位仅需要  十个数,不过好处就是输出的时候不需要进制转换。

因此Panda想到了一种兼顾两者的办法,就是采用十亿进制(你没听错就是十亿进制)

还是用一个 int 类型的数组,一个元素代表一位,但是每一位的取值范围从 。

好了,废话不多说了,先给出高精度自然数类的主要代码

//头文件,定义成员变量、函数以及操作符重载c0bed866851e4085a1a4fb51b2451f64.png

//输出,通过 setfill 和 setw 函数保证输出正确27ebcb2b4d0b34b0b7810eac6cd98342.png

//高精度乘以低精度,用一个 long long 型的整数 r 辅助进位2ab7ed5ccb5f2d2a1c592c8dbb00ff00.png

//删除前导 0,做四则运算有时会有多余的前导 0abcb48e7bd0ccfc1b1d0e7e8cf6e0578.png

//计算位数,计算数在十进制下的位数2816860076cabd0a06e4cd9daa8c4669.png

//求阶乘9e55b2b9c5e2aa80a9cfe5e7c4b47b48.png有一些本次内容用不到的代码没有贴出来,后面再和大家分享。

//最后是主函数4d6b2d0d6ffeb4a6c98d80b7ff2fe930.png

主函数比较简单,就是输入 ,计算阶乘,将结果输出为文件,在控制台里输出用时和阶乘的位数。 万的阶乘有  位。

可以看到这种十亿进制的方法,如果用单线程计算的话, 万的阶乘用了 s,而 cpp_int 只需要 s。

但是由于不需要做进制转换,输出的时间几乎可以忽略。因此计算加上输出的用时反而比 cpp_int 少了 40%。

510c6b3a5c8d5978ac7ad8b2e0d95e3b.png

取得了一些进展,但是 Panda 的心情反而更加非常复杂,因为这个方法的时间复杂度真的不好!!!

4、时间复杂度分析

不是说 Panda 写的高精度计算的类时间复杂度不好。

Panda的意思是在算  万阶乘的时候,从  一直乘到  万这样的方法,时间复杂度真的不好。

无论是Panda的方法还是 boost 库的代码,采用的方法都是先建立一个高精度数 ,然后用低精度数不断的去乘以这个高精度数。

我们来分析一下,高精度乘以低精度,时间复杂度是 , 是高精度数的长度。

根据斯特林公式,当  比较大的时候

因此计算 ,时间复杂度就是

cpp_int 如果要做进制转换,需要用高精度数不断的除以低精度数 (也可能是  的某次方),然后得到余数。

高精度数除以低精度数的时间复杂度也是 ,因此将  进制的  转为  进制,时间复杂度也是  。

按照这样的估算,用 cpp_int 计算阶乘并输出应该至少需要  万秒,也就是接近  个小时。用Panda自己写的高精类,也应该至少需要  万秒。

d60b5c4e5eb9e5dd117dc031885b914e.png

5、小结

从  万秒也就是  分钟降到  分钟以内,看似是个不可能完成的任务,肯定需要新的秘密武器。

不过今天内容写的够多了,这里先分享一下结果吧,最终Panda还是完成了这个挑战。

99a324bcf4864bcfd98bf4789e88d429.png

成功用  秒的时间算出了  万的阶乘,Panda还是用的自己 6 核的游戏本做的多线程并行计算(贫穷逼我只能去优化算法), 万的阶乘有  位。

Panda还成功挑战了  亿的阶乘,仅用了  秒。 亿的阶乘有  位。

究竟Panda找到什么办法完成这个挑战的呢,请期待下一期:

怎样在一分钟内算出一千万的阶乘(二):数论变换乘法

如果觉得有趣或者对你有帮助的话,请关注“黑眼圈信息学”专栏,下期不见不散。

参考文献

[1] 林厚从. 信息学奥赛课课通(C++). 高等教育出版社

[2] 董永建. 信息学奥赛一本通(C++). 科学技术文献出版社

a251d4df3d381c939494460373a64745.png
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值