找回密码
 欢迎注册
楼主: medie2005

[讨论] 阶乘和圆周率

  [复制链接]
发表于 2019-11-3 10:15:44 | 显示全部楼层
刚反复的测试,觉得结果推进到3.1415926535897,需要的精度是1.388E-14
所以,每次误差不能超过5E-15
我无论怎么测试,gcc每次浮点误差都大于1E-17,所以斯特拉公式调整频率得降低到100次1调整才行
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2019-11-3 10:27:49 | 显示全部楼层
无心人 发表于 2019-11-3 10:15
刚反复的测试,觉得结果推进到3.1415926535897,需要的精度是1.388E-14
所以,每次误差不能超过5E-15
我 ...


找到原因了,把double改long double, log10改log10l, 相应的输入输出格式化字符串%lf改%Lf
编译参数加-m128bit-long-double
后,测试精度高于5E-19,已经满足1000次修正后,计算到3.1415926535897精度大于1E-14的要求

现在重新复核上次计算到22.8万亿的过程

点评

你可以先将误差小于E-14的结果全记录下来,致于精度是不是可以达到更高,可以事后逐一验证。逐一验证是很容易的。  发表于 2019-11-3 14:02
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2019-12-11 00:12:04 | 显示全部楼层
如果我们写一个十进制定点数的乘法算法,只保留十进制前几十位数,使得从1乘到很大的N都不会产生严重误差,是不是可以避免浮点斯特令公式?

点评

我还是觉得这是个好办法,我们可以先用mpfr的loggamma算出某初始数的一个精确的初值,然后在一段区间上应用定点乘法,这样也可以并行。  发表于 2019-12-11 00:45
好像没有什么好处,反而让算法不能并行了。忽略我吧。  发表于 2019-12-11 00:14
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2019-12-11 02:27:39 | 显示全部楼层
我写了一个算法,可以单线程在18秒内搜索10亿的范围(测试起点 0xdeadbeefbadcab1e , 测试长度 10^9 ),可以任意并行化,最高可以搜索到 2^64 - 2 的阶乘。
但我的电脑正在全线程计算海绵球的体积,所以这个项目得过几天开算。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2019-12-11 02:33:20 | 显示全部楼层
我先把我的代码贴出来吧。
使用到了mpfrc++,可以在 http://www.holoborodko.com/pavel/mpfr/ 下载。
用64位无符号定点数存储阶乘的十进制的开头,具体算法见 einit。
然后用emul逐项乘下一个数进行递推(可以每递推百万次用einit校正一次)。
搜索比较接近 pi/10*2^64 的值就行了。

  1. #include<mpreal.h>
  2. #pragma comment(lib,"mpfr-4.0.1.lib")
  3. #include<iostream>
  4. #include<intrin.h>

  5. typedef mpfr::mpreal real;

  6. //10^frac(log10(factorial(n)))*2^64/10
  7. uint64_t einit(uint64_t n){
  8.     static const real log10e=1/mpfr::log(10);
  9.     static const real p264=mpfr::pow(2,64);
  10.     return (uint64_t)(p264*mpfr::pow(10,mpfr::frac(mpfr::lngamma(n+1)*log10e)-1));
  11. }

  12. //2^64/10 ~< e*n/10^k < 2^64
  13. uint64_t emul(uint64_t e,uint64_t n){
  14.     //10^pos
  15.     static uint64_t pow10[]={
  16.         1,10,100,1000,10000,
  17.         100000,1000000,10000000,100000000,1000000000,
  18.         10000000000,100000000000,1000000000000,10000000000000,100000000000000,
  19.         1000000000000000,10000000000000000,100000000000000000,1000000000000000000,10000000000000000000
  20.     };
  21.     static int shrn[]={3,6,9,13,16,19,23,26,29,33,36,39,43,46,49,53,56,59,63,66};
  22.     //round(2^(64+shrn[pos])/10^(pos+1))
  23.     static uint64_t inv10[]={
  24.         0xcccccccccccccccd,0xa3d70a3d70a3d70a,0x83126e978d4fdf3b,0xd1b71758e219652c,
  25.         0xa7c5ac471b478423,0x8637bd05af6c69b6,0xd6bf94d5e57a42bc,0xabcc77118461cefd,
  26.         0x89705f4136b4a597,0xdbe6fecebdedd5bf,0xafebff0bcb24aaff,0x8cbccc096f5088cc,
  27.         0xe12e13424bb40e13,0xb424dc35095cd80f,0x901d7cf73ab0acd9,0xe69594bec44de15b,
  28.         0xb877aa3236a4b449,0x9392ee8e921d5d07,0xec1e4a7db69561a5,0xbce5086492111aeb
  29.     };

  30.     //res = e*n
  31.     uint64_t res[2];
  32.     res[0]=_umul128(e,n,res+1);

  33.     if(res[1]==0)return res[0];

  34.     //shrt = res >> shrn[pos]
  35.     int pos=0;
  36.     uint64_t shrt;
  37.     if(res[1]>=pow10[19]){
  38.         pos=19;
  39.         shrt=res[1]>>2;
  40.         res[0]=0;
  41.     }
  42.     else{
  43.         while(res[1]>=pow10[pos+1])++pos;
  44.         shrt=res[1]<<64-shrn[pos]|res[0]>>shrn[pos];
  45.         res[0]=res[1]>>shrn[pos]?inv10[pos]:0;
  46.     }
  47.    
  48.     _umul128(shrt,inv10[pos],res+1);
  49.     return res[0]+res[1];
  50. }

  51. int main(){
  52.     mpfr::mpreal::set_default_prec(192);
  53.     std::cout.precision(39);

  54.     uint64_t iinit=0xdeadbeefbadcab1e,isize=1000000000;
  55.     uint64_t eval=einit(iinit);
  56.     for(uint64_t i=iinit+1;i<=iinit+isize;++i){
  57.         eval=emul(eval,i);
  58.     }
  59.     std::cout<<eval<<std::endl;
  60.     std::cout<<einit(iinit+isize)<<std::endl;
  61.     return 0;
  62. }
复制代码


点评

死的牛肉,坏的车,挺讲究的,哈哈哈  发表于 2019-12-11 06:59
注意!刚刚发现这个代码有一个致命BUG!emul里的inv10[5]必须减去1,改成 0x8637bd05af6c69b5 ,否则emul可能返回2^64,溢出成0!  发表于 2019-12-11 02:45
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2019-12-11 06:27:36 来自手机 | 显示全部楼层
最关键是误差分析,乘百万次再调整精度应该不够了

点评

我做了严格的误差分析,每次迭代最多会造成 (-3.191,+0.909) 的定点误差,10亿次迭代后误差不超过32亿,相当于 1.73*10^-9 的浮点圆周率误差。  发表于 2019-12-11 14:10
64位定点数有19位精度多。就算留一位余裕,迭代10亿次之后仍然剩余 9 位精度,也就是只要高精度检测前9位能对准的数就行,这个概率恰好也是10亿分之一,应该是最优的了。所以我现在决定每迭代10亿次检查一次。  发表于 2019-12-11 12:25
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2019-12-11 09:38:55 | 显示全部楼层
Mark, 以后有时间看
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2019-12-11 16:44:41 | 显示全部楼层
本帖最后由 Ickiverar 于 2019-12-11 18:18 编辑

2019.12.11 16:42,在我的老电脑上四线程并行开算。
16秒40亿,一天应该能算21万亿。
更新:
18:18算出前12个数,验证了20楼的结果:
d =  0, n = 9
d =  1, n = 62
d =  2, n = 62
d =  3, n = 10044
d =  4, n = 50583
d =  5, n = 1490717
d =  6, n = 5573998
d =  7, n = 65630447
d =  8, n = 688395641
d =  9, n = 5777940569
d = 10, n = 77773146302
d = 11, n = 1154318938997
d = 12, n = 1544607046599
done to n =        1552000000000, maxerr = 0.109788

点评

换到新电脑上算,4核8线程,大概10秒能算80亿。目前为止,104.6万亿了,没有d=13的结果。  发表于 2019-12-17 23:50
我真是*了微软他*了,我把电脑放办公室准备算一周末,结果周一来发现这傻*windows自动更新给我在周五下午七点重启了我*他**  发表于 2019-12-16 16:37
超过50万亿了,没有d=13的结果。  发表于 2019-12-13 15:19
现在已经到23万亿了,没有发现d=13的结果。  发表于 2019-12-12 14:13
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2019-12-13 15:03:58 | 显示全部楼层
我尝试了把105#的代码修改为可在64位Linux下可用gcc编译通过的代码。
1. 在GCC下,不需要这句 “#pragma comment(lib,"mpfr-4.0.1.lib")”
2. 在GCC下,没有"intrin.h"这个文件,所以也就没有_umul128这个函数。
3. 在GCC下,10000000000000000000要改为 10000000000000000000ULL

修改思路,
首先可实现 c语言版的_umul128。修改后的部分代码如下。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <stdint.h>
  4. #include <iostream>
  5. #include <mpreal.h>

  6. typedef unsigned __int128  uint128_t;

  7. uint64_t _umul128( uint64_t x,  uint64_t y,  uint64_t *HighPart)
  8. {
  9.     uint128_t __res = (uint128_t)x * y;
  10.     *HighPart = (uint64_t) (__res >> 64);
  11.      return (uint64_t) __res;
  12. }
复制代码


代码通译通过后,可考虑改为汇编语言的实现。
假设上面的包含 _umul128的源代码编译为 test.o(目标文件,非可执行文件),执行下面的命令可看到这个函数汇编后的代码。
  1. objdump -d test.o -M intel
复制代码


稍加修改,改为nasm格式的汇编文件,保存为umul128.asm
  1. global _umul128

  2. section .text
  3. _umul128:
  4.      mov    rax,rdi
  5.      mov    rcx,rdx
  6.      mul    rsi
  7.      mov    QWORD [rcx],rdx
  8.      ret
复制代码


3. 将_umul128的c语言的实现删除,改为声明
  1. extern "C" uint64_t _umul128( uint64_t x,  uint64_t y,  uint64_t *HighPart);
复制代码


4. 现在,就可以使用汇编版的_umul128了,下面的编译链接命令
  1. g++ -m64 -c test.cpp -O2 -o test.o
  2. nasm -f elf64 -o umul128.o umul128.asm
  3. g++ test.o umul128.o -lmpfr -o test
复制代码

点评

既然gcc有uint128_t,那就可以直接写uint128的乘法啊,不需要写个_umul128然后调用啊?  发表于 2019-12-13 15:24
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2019-12-13 15:38:39 | 显示全部楼层
liangbch 发表于 2019-12-13 15:03
我尝试了把105#的代码修改为可在64位Linux下可用gcc编译通过的代码。
1. 在GCC下,不需要这句 “#pragma c ...

从性能上将,c语言的版本并不比汇编版本差。不过,将此函数替换为nasm格式的汇编代码,使得代码在跨平台(windows/Liunx)方面,更有优势,因为nasm本身就是跨平台的。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
您需要登录后才可以回帖 登录 | 欢迎注册

本版积分规则

小黑屋|手机版|数学研发网 ( 苏ICP备07505100号 )

GMT+8, 2024-11-24 08:18 , Processed in 0.024981 second(s), 16 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表