找回密码
 欢迎注册
查看: 6680|回复: 10

[原创] 大整数加法----(10进制,速度快)

[复制链接]
发表于 2016-12-11 00:24:16 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?欢迎注册

×
本帖最后由 只是呼吸 于 2016-12-11 02:26 编辑

  在大整数的运算程序中,无符号加法应该算最容易写的了。但要把加法的运算程序尽可能运行快一点,仍然要动一番脑筋。
      这个加法程序是我在本论坛学习的过程中,参照了管理员  liangbch  的两篇文章写成的。

                http://bbs.emath.ac.cn/thread-216-11-1.html     在这篇文章的第102楼,学习了汇编语言的接口技术。
               
                http://bbs.emath.ac.cn/thread-521-3-1.html       在这篇文章,学习了把单精度除法转化为乘法的技术。

                下面是我写的大整数加法程序:
  1. /*
  2. *  声明:本函数的接口技术、单精度乘法代替单精度除法技术,学习自管理员liangbch的两篇帖子:http://bbs.emath.ac.cn/thread-216-11-1.html第102楼 和 http://bbs.emath.ac.cn/thread-521-3-1.html
  3. *         liangbch依法享有著作权和解释权
  4. *\
  5. *  dst:   目的数组,存放和。
  6. *  src1:  第一个加数,(操作数)
  7. *  src2:  第二个加数,(操作数)
  8. *  size1; 第一个加数的长度。
  9. *  size2: 第二个加数的长度。
  10. *         所有数组均采用10^8进制,数组较大的下标对应10进制数的高位,数组较小的下标对应10进制数的低位。
  11. */


  12. _declspec(naked)////这个程序能处理任意长度的两个无符号大整数之和,速度快。
  13. void big_add_ALU(unsigned long  *dst,unsigned long *src1,unsigned long *src2,int size1,int size2)
  14. {
  15. #undef  BASE10000_MASK
  16. #define BASE10000_MASK BASE
  17.         _asm
  18.         {
  19.                 push                esi
  20.         push                edi
  21.         push                ebx
  22.         push                ebp
  23.         
  24.         mov         edi, dword ptr [esp+0Ch+16]
  25.                 mov         ebp, dword ptr [esp+04h+16]
  26.                 xor         eax,eax
  27.                 mov         ebx, BASE10000_MASK
  28.                 movd        mm4,eax
  29.         mov         esi, dword ptr [esp+08h+16]
  30.                 mov         eax, 0xabcc7712
  31.                 movd        mm7,ebx
  32.                 movd        mm5,eax
  33.                
  34.         xor         ebx,ebx
  35.                 mov         ecx, dword ptr [esp+14h+16]
  36.         mov         eax, dword ptr [esp+10h+16]
  37.                 cmp         eax, ecx
  38.                 jl          fffd
  39.                
  40.                 jmp         jjj        
  41. fffd:  
  42.                 mov         ecx,eax   //  < 则跳转
  43. jjj:
  44.        
  45. loop02:

  46.                 movd    mm1, dword ptr [esi]
  47.                 movd    mm2, dword ptr [edi]
  48.                
  49.                 paddq   mm1, mm2
  50.                 paddq   mm1, mm4
  51.                
  52.                 movq                mm2, mm1 // mm2, 总和
  53.                 pmuludq                mm1,mm5
  54.                 psrlq                mm1,58
  55.                 movq                mm4,mm1//   mm4  进位
  56.                 pmuludq                mm1,mm7
  57.                 psubq                mm2,mm1
  58.             
  59.                 movd                dword ptr[ebp],mm2
  60.                 lea                        edi, dword ptr [edi+4]
  61.         lea                        ebp, dword ptr [ebp+4]
  62.         lea                        esi, dword ptr [esi+4]
  63.         
  64.                 dec                        ecx
  65.         jnz                        loop02

  66. //--------以上是公共部分之和------------------//

  67.                 mov         ecx, dword ptr [esp+14h+16]
  68.         mov         eax, dword ptr [esp+10h+16]
  69.                 cmp         eax,ecx
  70.                 je          exit1
  71.                 jl          fffdd
  72.                 sub         eax,ecx
  73.                 mov         ecx,eax

  74. loop03:
  75.                 movd        eax,mm4
  76.         cmp         eax,ebx
  77.                 je          exit2

  78.                 movd                mm1, dword ptr [esi]
  79.                 paddq                mm1,mm4
  80.                 movq                mm2,mm1 // mm2,总和

  81.                 pmuludq                mm1,mm5
  82.                 psrlq                mm1,58
  83.                 movq                mm4,mm1//          mm4 进位
  84.                
  85.                 pmuludq                mm1,mm7
  86.                 psubq                mm2,mm1

  87.                 movd                dword ptr[ebp],mm2

  88.                 lea                        esi, dword ptr [esi+4]
  89.                 lea                        ebp, dword ptr [ebp+4]
  90.                 dec                        ecx
  91.                 jnz                        loop03//////
  92. exit2:
  93.                 cmp         ecx,ebx
  94.                 je          exit1
  95.                
  96. loop04:
  97.                 mov        eax,dword ptr [esi]
  98.                 mov        dword ptr[ebp],eax
  99.                
  100.                 lea                        esi, dword ptr [esi+4]
  101.                 lea                        ebp, dword ptr [ebp+4]
  102.                 dec                        ecx
  103.                 jnz                        loop04/////        
  104.                

  105.         jmp         exit1
  106. ///-----------------以上是第一个加数大于第二个加数的求和-------------//
  107. fffdd:       

  108.         sub         ecx, eax
  109. loop05:
  110.                 movd        eax,mm4
  111.         cmp         eax,ebx
  112.                 je          exit3

  113.                 movd                mm1,dword ptr [edi]
  114.                 paddq                mm1,mm4
  115.                
  116.                 movq                mm2,mm1 //mm2,总和
  117.                 pmuludq                mm1,mm5   
  118.                 psrlq                mm1,58
  119.                 movq                mm4,mm1 //   mm4 进位
  120.                 pmuludq                mm1,mm7
  121.                 psubq                mm2,mm1
  122.                

  123.             
  124.                 movd                dword ptr[ebp],mm2       

  125.                 lea                        edi, dword ptr [edi+4]
  126.                 lea                        ebp, dword ptr [ebp+4]
  127.                 dec                        ecx
  128.                 jnz                        loop05//////

  129. exit3:
  130.                 cmp         ecx,ebx
  131.                 je          exit1
  132.                
  133. loop06:
  134.                 mov        eax,dword ptr [edi]
  135.                 mov        dword ptr[ebp],eax
  136.                
  137.                 lea                        edi, dword ptr [edi+4]
  138.                 lea                        ebp, dword ptr [ebp+4]
  139.                 dec                        ecx
  140.                 jnz                        loop06/////        

  141. //--------------------------------------------//
  142. exit1:
  143.                
  144.         movd                dword ptr [ebp],mm4
  145.       
  146.         pop                        ebp
  147.         pop                        ebx
  148.         pop                        edi
  149.         pop                        esi
  150.                


  151.                 emms
  152.         ret
  153.         }
  154. }/////*///
复制代码



作为对照,我写了一个c语言的大整数加法函数
  1. /*
  2. *  这是c语言写的大整数加法,作为对照。
  3. *   
  4. *  为了简单一点:
  5. *
  6. *  1)要求第一个加数src1的实际长度大于等于第二个加数src2的实际长度。
  7. *  2)第二个加数的前面补零,与第一个加数的实际长度对齐。(实际处理中只需第二个加数src2的最大空间大于src1的实际长度即可)
  8. *  3)参数size1传递第一个加数的实际长度。
  9. */

  10. void big_add_c(unsigned long *dst,unsigned long *src1,unsigned long *src2,int size1)
  11. {
  12.         int i,temp=0;
  13.         for(i=0;i<size1;i++)
  14.         {
  15.                 dst[i]=src1[i]+src2[i]+temp;
  16.               temp=0;
  17.             if(dst[i]>=BASE)
  18.                 {
  19.                         dst[i]-=BASE;
  20.                         temp=1;
  21.                 }
  22.         }
  23.         dst[i]=temp;
  24. }
复制代码
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2016-12-11 01:05:34 | 显示全部楼层
本帖最后由 只是呼吸 于 2016-12-11 03:10 编辑

上面的两个程序,c语言的作为参考。输入两个大整数进行对比。用vc++2008在debug模式下进行实验。
                  
                      输入数字规模                                                  调用函数次数                                        c 语言运行时间                            汇编语言运行时间
             两个14700位的随机数相加                                       1000 000 次                                          19.157秒                                      5.944秒
             两个14700位的9(全是9)相加                                 1000 000 次                                          15.616秒                                     5.881秒
             30000位 和 14700位的随机数相加                             1000 000 次                                          30.888秒                                     7.800秒
             30000位 和 14700位的9(全是9)相加                       1000 000 次                                          31.800秒                                     12.324秒
   
上面的数据只记录函数的运行时间,不计输出时间,这是我在电脑上的实测结果,如果机器的性能不同,数字会有变化。

输入两个全是9的大整数相加,主要是考验程序的连续进位。函数big_add_ALU()采用的是两次乘法一次减法来代替判断语句,综合性能较好,在汇编语言中如果采用判断语句来进位,只有在连续进位或连续不进位的情况下占优。例如全部是9相加(连续进位),或者全部是3相加,(连续不进位)这两种情况下的实际运行时间比big_add_ALU()要好得多。而对随机数来说,采用判断语句来进位的函数就比不过big_add_ALU()了。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2016-12-11 01:16:05 | 显示全部楼层
本帖最后由 只是呼吸 于 2016-12-11 02:32 编辑

这里给出这个函数的调用方法,用两个很小的数字来说明。
这里我用的是vc++2008,(vc++6.0不能编译)。其它的编译器不知能不能用。

  1. #include <stdio.h>

  2. #define  BASE 100000000 //10^8进制。(共8个零),32位系统中不能超过10^9。


  3. //char a[210000]={0},b[200000]={0};
  4. unsigned long  c[30000]={0},e[30000]={0},result[2000000]={0};
  5. /*
  6. //---------------------------------------
  7. 在这里粘一楼的两个函数

  8. //----------------------------------------
  9. */
  10. int main()
  11. {
  12.         int  i,m1,m2;
  13.        
  14.     // 大数:c[]  138850000000090000099934222009345001230023908770390239
  15. // 大数:e[]                        38462690000660060508019900031415
  16.                  
  17.         c[0]=70390239;  // 数组较小的下标对应大整数的低位,采用10^8进制,(可以修改为10^9进制,这样效率更高一些)。
  18.         c[1]=239087;
  19.         c[2]=34500123;
  20.         c[3]=34222009;
  21.         c[4]=999;
  22.         c[5]=9;
  23.         c[6]=138850;

  24.         e[0]=31415;
  25.         e[1]=5080199;
  26.         e[2]=66006;
  27.         e[3]=38462690;

  28.         m1=7;// c[]的长度
  29.         m2=4;// e[]的长度

  30.        
  31.         //for(i=0;i<1000000;i++) // 调用函数100万次,用这种方法估计时间。虽不很准,但可以说明问题。       
  32.                 //big_add_c(result,c,e,m1);// c  语言
  33.                 big_add_ALU(result,c,e,m1,m2);

  34.         m1=m1+m2+3;
  35.     while((result[m1]==0)&&(m1>0))//寻找不为零的第一个数
  36.         {
  37.                 m1--;
  38.         }

  39.        

  40.         printf("%d",result[m1]);//输出语句。
  41.         for(i=m1-1;i>=0;i--)//输出语句。把相加的结果输出。
  42.                 printf("%08d",result[i]);//输出语句。

  43.         printf("\n");


  44.   // 计算的结果是: 138850000000090000099972684699345661290531928670421654

  45.   

  46.         system( "pause" );

  47.         return 0;

  48. }
复制代码
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2016-12-11 10:40:39 | 显示全部楼层
关于加法运算的正常化

1. RAD表示进制,若 a < RAD, b< RAD,c<=1, (a是被加数,b是加数,c是进位)
   则, s0=a+b+c, 这时s0<RAD*2, 将s0拆为本位s和进位c的方法(即求s和c,使得s0=c*RAD+s)称为规约,英文直译为“正常化”。
   在这里,规约的过程是不需要使用乘法的,一个显然的实现是
  1.    if (s0>=RAD)
  2.    {  s=s0-RAD; c=1; }
  3.    else
  4.    {  s=s0; c=0;  }
复制代码

上面的实现用到了分支指令,由于两个分支的执行机会均等,CPU分支预测机构无论预测那个分支,预测失败的概率总是50%,所以效率不高。一个好的实现方法应该完全不使用分支指令
  1.   s= s0- RAD;
  2.   tmp = -(s>>31);
  3.   c= tmp+1;
  4.   s += (tmp & RAD);
复制代码
         
下面给出使用MMX指令的规约的代码
若 MM_RES= a+b+c, MM_RAD=R, MM_RAD_S1= R-1, 当执行完下面的指令序列后,MM_RES 为本位,MM_CARRY为进位。
说明,
   1. 这里的进位为负数,所以在求 RES=RES+c时,使用的是PSUBD指令而不是PADDD指令,即PSUBD MM_RES,MM_CARRY
   2. 为了便于代码阅读,将寄存器起了一个新的名字。
在C语言中,使用下面的定义
  1.   #define   MM_RAD    MM7
  2.   #define   MM_RAD_S1 MM6
  3.   #define   MM_CARRY  MM5
  4.   #define   MM_RES    MM1
  5.   #define   MM_TMP    MM2
复制代码

在汇编语言中,应该这样定义
  1.    MM_RAD    EQU   MM7
  2.    MM_RAD_S1 EQU   MM6
  3.    MM_CARRY  EQU   MM5
  4.    MM_RES    EQU   MM1
  5.    MM_TMP    EQU   MM2
复制代码

  1. MOVQ    MM_TMP,MM_RES
  2. PCMPGTD MM_TMP,MM_RAD_S1        ; if res>=RAD, tmp=-1, else tmp[i]=0
  3. MOVQ    MM_CARRY,MM_TMP                ; if res>=RAD, carry[i]= -1, else carry[i]=0
  4. PAND    MM_TMP,MM_RAD                ; if res[i]>=RAD, tmp[i]= RAD, else tmp[i]=0
  5. PSUBD   MM_RES,MM_TMP                ; res -= RAD or res-=0
复制代码

点评

终于搞清楚这里面的原理了,确实精辟!看来各位大神的脑神经都已经深入到每一个bit中了!  发表于 2016-12-16 14:45
学习+ing  发表于 2016-12-11 11:24
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2016-12-11 10:42:05 | 显示全部楼层
我写了8个版本的基于10^9的大数加法,包括1个c语言版本,和7个汇编语言版本
  mpn_D9_add_n_C         : 消除分支的c语言版本
  mpn_D9_add_n_ALU1:    :消除分支的ALU版本
  mpn_D9_add_n_ALU2             :消除分支的ALU另一个版本,规约方法和ALU1不同
  mpn_D9_add_n_CMOV1         :使用CMOVCC指令消除分支
  mpn_D9_add_n_CMOV2         ;另一种使用CMOVCC指令的版本
  mpn_D9_add_n_MMX              :使用MMX指令的版本,一次计算2个DWORD
  mpn_D9_add_n_SSE2             :使用SSE2指令的版本,一次计算4个DWORD
  mpn_D9_add_n_SSE4             ;在SSE2指令的版本的基础上,使用了一条SSE4指令 PTEST 来加速。


c语言语言版的源代码
  1. DWORD mpn_D9_add_n_C(DWORD *rp,const        DWORD *s1p, const DWORD *s2p, DWORD        len)
  2. {
  3.         const DWORD *p1End=s1p+len-1;

  4.         DWORD carry=0;
  5.         DWORD res;

  6.         int tmp;
  7.         while (s1p <= p1End)
  8.         {
  9.                 res = *s1p + *s2p +carry;
  10.                 res -= DEC9_RADIX;
  11.                 tmp = -(res>>31);
  12.                 carry= tmp+1;
  13.                 res += (tmp & DEC9_RADIX);
  14.                 *rp=res;
  15.                 s1p++;        s2p++;         rp++;
  16.         }

  17.         return carry;
  18. }

复制代码

点评

to liangbch ,这个c版的加法我就直接拿来用了。  发表于 2016-12-11 11:56
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2016-12-11 10:43:19 | 显示全部楼层
性能测试报告:

blk_len mpn_D9_add_n_C mpn_D9_add_n_ALU1 mpn_D9_add_n_ALU2 mpn_D9_add_n_CMOV1 mpn_D9_add_n_CMOV2 mpn_D9_add_n_MMX mpn_D9_add_n_SSE2 mpn_D9_add_n_SSE4
8192    4.602   4.670   3.582   4.642   3.850   5.337   1.445   1.285
16384   4.609   4.640   3.575   4.582   3.831   5.331   1.438   1.271
32768   4.590   4.643   3.549   5.172   3.836   5.333   1.446   1.298
65536   4.600   4.660   3.550   4.586   3.835   5.333   1.449   1.293

说明:
  大整数采用10^9进制,每个DWORD(32bit整数)可表示9位10进制数,上表列出当被加数和加数的长度为8K,16K,32K和64K个DWORD时,平均计算每个DWORD所耗费的时间,单位为时钟周期,数字越小表示性能越好。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2023-11-23 16:28:50 | 显示全部楼层
如果单纯是用mmx寄存器,而不是因为机器太老,用不了sse指令的话,建议mm寄存器换成xmm寄存器,
理论上,现在的CPU,并不存在物理的mm寄存器的,都是SIMD寄存器堆,所以,mm和xmm寄存器很可能物理上是一个东西
两者在早期CPU上确实存在速度差异,现在CPU已经没区别了
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2023-11-24 21:22:26 | 显示全部楼层
看不懂
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 欢迎注册

本版积分规则

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

GMT+8, 2024-4-18 21:04 , Processed in 0.047199 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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