[摘要] 大数运算不仅仅运用在密码学中,还运用在一些物理学研究、生物学,化学等科目中。大数运算,意味着参加的值和计算结果通常是以上百位数,上千位数以及更大长度之间的整数运算。例如大家所熟知圆周率π的值,在一般的数值计算中用到圆周率的不须要多大的精度,但在计算一些星球或是星系上的体积面积时便显的误差很大了,这就要求π值计算的精度达到几百万位甚至更高,才能缩小误差。人工计算是远远不行了,而且本身误差也无法估计。只有在计算机中用大数运算求π值了。又如,考古学家计算石头内的碳元素衰变来考证地球形成的时间,更是将计算的结果精确到了百年以内。所以说大数的运算是涉及领域多,应用范广,与我们生活息息关。在此,我采用一个在C语言下实现计算大数运算的一个程序为例,讲解包括了大数的加法,减法,乘法和除法及求幂运算的算法及代码。
[关键词] 大数计算 网络安全 密码学
随着计算机网络技术的发展和因特网的广泛普及,网络安全事故逐年增加,黑客的攻击已经和病毒并列成为对信息安全影响最严重的两大危害。其很大程度上是被黑客破解了用户的计算机名及登陆密码及资料的加密较差,而使得黑客来对网民的资料如同自己般的随意更改和破坏。而安全的密码和账号成为了网民的安全之本,怎么才能提高安全问题成为的人们和社会关注的问题。而加密大部又是以大素数的计算为基础的,如非对称密码体制RSA的安全性依赖于对大数进行因数分解的耗时性。一个二进制数n的因数分解所需的机器周期大约是exp{[ln(n)ln(ln(n))]1/2}。若机器周期为1μs,b为二进制数的位数,分解 n=2b 所需时间如下表所示:
位数 | 100 | 200 | 300 | 500 | 750 | 1000 |
时间 | 30秒 | 3天 | 9年 | 1兆年 | 2*109年 | 6*1015年 |
实际应用中,p、q的选择都在10200 数字以上,这样每个明文块的二进制位数可达664比特,即83个字节。而DES只有8个字符。一般认为,对于当前的计算机水平,选择1024位长的密钥就可认为是无法攻破的了。
1. 数字存储的实现
大数计算的因数和结果精度一般是少则数十位,多则几万位。在C语言中定义的类型中精度最多只有二十多位,因而我们采取用链表存贮的方式来存放大数。在计算中会用到从高位开始计算,和从低位开始计算数值的两种情况。所以我们将链表定义为双向链表,其中为一个单元来存贮数据,一个指针指向前方的数据,另一个指向后的数据。其结构为:
2. 各种算法
2.1 加法运算的实现
加法计算还是比较容易的,我们也是先从低位算起,因为只须要对应的位相加,再加上前一位的进位,再去判断是否本位是否有进位, 有则把本位数字改为减去它的权,也就是10,再置进位为1。如果没有进位,则给进位赋值0。
void subtract(char *numa, char *numb,char *result)//计算减 { char *pna = findend(numa);//指向numa的一个指针。point numa pna 指向减数的最低位, char *pnb = findend(numb);//指向numb的一个指针 //pnb 指向被减数的最低位, int along=(int)strlen(numa);//标记数字a的长度; int blong=(int)strlen(numb);//标记数字b的长度; int times = 0; // 标记要计算多少次。 int carry=0; //存贮借位 int clear0=0; //消除结果最前面无用的'0' 13-5 = 08 的效果!! int isnegative=0; //用来加上被减数大于减数时补上最后一个负号 Node *head, // 用于存贮头指针 *pstart, // 用于存贮计算时的首指针 *pnew; //作于申请新结点 head = pstart =new Node;//初始化首结点和头结点。 pstart -> data = 0; pstart -> next = NULL; pstart -> ahead = NULL; if (abigerb(numa ,numb)) times = strlen(numa);//比较两个字符串长度,以大的作为循环次数 else //交换位置以降低难度 { times = strlen(numb);//让数(字符串)长的减去数(字符串)短的 pna = findend(numb);//交换指针 pnb = findend(numa); along=(int)strlen(numb);//标记数字a的长度; blong=(int)strlen(numa);//标记数字b的长度; isnegative=1;//标记最后要加上负号 } while ((times-- && (times>=0))|| carry != 0)//carry != 0 说没有借位时 { if(!pstart->next)//如果当前为空结点,则申请新结点 { pnew = new Node; pnew -> data = 0; pnew -> next = NULL; pnew -> ahead = pstart; pstart -> next = pnew; } if(times<0)//如果计算完之后,借位等于1,,说明必然为负值; { pstart -> data = -3 ;//让它等于负号 '-'//-3来源于负号与0相差3。。 break; } else { if ( *pna == *pnb )//减数等于被减数时。结果等于直截相减的结果;并置借位为0 { if(carry==0)pstart -> data = (*pna-48)-(*pnb-48); //111-11的情况 else { pstart->data = (*pna-48)-(*pnb-48)+10 -carry;//1121-1112 carry=1; } } if( *pna > *pnb )//减数大于被减数时。结果等于直截相减的结果;并置借位为0 { pstart -> data = (*pna-48)-(*pnb-48)-carry; //存贮个位 carry=0; } else if( *pna < *pnb )//说明被减数大于减数,让结果加10,相当于借位 (carry)为1 { if(times>0) pstart->data = (*pna-48)-(*pnb-48)+10 -carry;//13-5的情况作为新值 else pstart->data = (*pnb-48)-(*pna-48) -carry; //3-5 作为当前的新值 carry=1; } } pstart = pstart -> next; //结点移动 blong--; if(blong>0)pnb--;//指针移向被减数高位 else *pnb=48;//之后相减就变为了0不作任何运算,其实可以优化的。但代码会长!而且还需要重新开结点。所以放弃; pna--;//被数指针移动, } if(isnegative==1)////加上负号处理。增加一长度并置为负号 { pnew = new Node; pnew -> data = 0; pnew -> next = NULL; pnew -> ahead = pstart; pstart -> next = pnew; pstart->data=-3;//因为寻找链表的结尾点要统一加48。又因为‘-’是45。所以等于‘-3’ } pstart =head;//寻找链表的结尾点 while(pstart->next != 0) { pstart->data += 48;//!!<<<因为我们的输出是字符。所以再此加上48>>>> 逆顺输出 pstart = pstart->next ; } int tip = 0;//转为字符串用 clear0=0;// 消除结果最前面无用的'0' 13-5 = 08 的效果 ..>>修改字符串的首指针 pstart = pstart->ahead ;//找有效字 while(pstart != 0)//输出正序的结果; { if (clear0==0 && ((int)pstart->data)==48&&pstart->ahead!=0)// 消除结果最前面无用的'0' ;//不输出任何东西 else result[tip++] = pstart->data; if(((int)pstart->data)!=48&&((int)pstart->data)!=45)clear0=1;//'-'号 pstart = pstart->ahead ; } result[tip] = '/0'; pstart =head; //释放空间 while(pstart->next != 0) { pnew = pstart->next ;delete pstart; pstart =pnew; } return ; }
void multiply(char *numa, char *numb ,char *result)//用来储结果的)//计算乘积 { char *pna = findend(numa);//指向numa的一个指针。point numa pna 指向乘数的最低位, char *pnb = findend(numb);//指向numb的一个指针 //pnb 指向被乘数的最低位, int along=(int)strlen(numa);//标记数字a的长度; int blong=(int)strlen(numb);//标记数字b的长度; int carry=0,temp_result;//存贮进位 和临时结果的 Node *head, // 用于存贮头指针 *pstart, // 用于存贮计算时的首指针 *pnew, //作于申请新结点 *pgo; //作为每计算完一行时,回到下一行起始节点用,移位标致来用 head = pstart =new Node;//初始化首结点和头结点。 pstart -> data = 0; pstart -> next = NULL; pstart -> ahead = NULL; while (along--) { pgo = pstart;//保存进位点 blong = (int)strlen(numb);//初始化长度 pnb = findend(numb); //初始化指针 while ((blong-- && (blong>=0))|| carry != 0) { if(!pstart->next)//如果当前为空结点,则申请新结点 { pnew = new Node; pnew -> data = 0; pnew -> next = NULL; pnew -> ahead = pstart; pstart -> next = pnew; } if(blong<0)temp_result = carry ;//处理只有进位的情况 else temp_result =(pstart->data+(*pna-48)*(*pnb-48)+carry);//自身值+新值+进位作为新值 pstart -> data = temp_result%10; //存贮个位 carry = temp_result/10; //存贮进位 pstart = pstart -> next; //结点移动 pnb--; //指针移向被乘数高位 } pstart = pgo->next; //前进一个位置; pna--; //指针移向乘数高位 } pstart =head;//寻找链表的结尾点 while(pstart->next != 0) { pstart->data += 48;//!!<<<因为我们的输出是字符。所以再此加上48>>>> 逆顺输出 pstart = pstart->next ; } int tip = 0;//转为字符串用 pstart = pstart->ahead ;//找有效字 while(pstart != 0)//输出正序的结果; { result[tip++] = pstart->data; pstart = pstart->ahead ; } result[tip] = '/0'; pstart =head; //释放空间 while(pstart->next != 0) { pnew = pstart->next ;delete pstart; pstart =pnew; } return ; }
void divide2( char *numa, char *numb,char *result,char *remainder)//计算除法2 { char one[]="1";//临时字符串.... char one2[]="1";// char zero[]="0";// char numb2[6048];// char tempstr[6018]="";//临时字符串 int ia=0,ia2=0;//tempstr的指示器 bool moveon=false;//翻转牌 char *pna = numa;//指向numa的一个指针。point numa pna 指向减数的最低位, char *pnb = findend(numb);//指向numb的一个指针 //pnb 指向被减数的最低位, Node *head, // 用于存贮头指针 *pstart, // 用于存贮计算时的首指针 *pnew; //作于申请新结点 head = pstart =new Node;//初始化首结点和头结点。 pstart -> data = 0; pstart -> next = NULL; pstart -> ahead = NULL; moveon = false; while(*pna) { if(!pstart->next)//如果当前为空结点,则申请新结点 { pnew = new Node; pnew -> data = 0; pnew -> next = NULL; pnew -> ahead = pstart; pstart -> next = pnew; } ia=(int)strlen(tempstr);//取的长度 tempstr[ia++] = *(pna++); tempstr[ia] ='/0'; //转换为字符串 if(tempstr[0]=='0')//处理高位也是0的那种 如00 { ia2=0; while(tempstr[ia2]=='0')++ia2; while(ia2>=1)//清除无用的0 { ia=ia2-1; tempstr[ia]=tempstr[ia2]; --ia2; } tempstr[++ia2]='/0'; } while(abigerb(tempstr,numb)>0)//如果tempstr大于等于numb { if(tempstr[0]=='0')//处理高位也是0的那种 如00----此乃冗余代码,留做记念用 { ia2=0; while(tempstr[ia2]=='0')++ia2; if(ia==ia2 ) { moveon = true; break; } } strcpy(numb2,numb); subtract(tempstr, numb,tempstr);//A-B strcpy(numb,numb2); if(tempstr[0]=='-')//若判断的不准,变为了负数就再加上B。。 { strcpy(numb2,numb); addition(tempstr, numb,tempstr);//A-B strcpy(numb,numb2); ia2=0; //修正之后的长度。因为加法中未做负数运算 ia=0; //为了消除最后的那一个负号,整体向前移动。 while(tempstr[ia2]!='/0')++ia2; while(ia2>=1)//清除无用的0 { tempstr[ia]=tempstr[ia+1]; ++ia; --ia2; } tempstr[ia]='/0'; moveon = true; break; } pstart->data++ ; //结果自加 moveon = true; } if(moveon) pstart = pstart -> next; //结点移动 } strcpy(remainder,tempstr);//存贮余数 int tip = 0;//转为字符串用 pstart =head;//寻找链表的结尾点 while(pstart->next != 0) { pstart->data += 48;//!!<<<因为我们的输出是字符。所以再此加上48>>>> 逆顺输出 result[tip++] = pstart->data; pstart = pstart->next ; } result[tip] = '/0';//存贮结果 pstart =head; //释放空间 while(pstart->next != 0) { pnew = pstart->next ;delete pstart; pstart =pnew; } return ; }
3. 程序运行介面
程序的介面制作就不多加描述了,以下是我的大数程序的运行介面。
5. 软件下载演示
点击下载大数运算软件
6. 参考文献
1. 李艺《网络安全讲义》PPT文档
2. 谭浩强《C程序设计》第二版,清华大学出版社
3. Adam Drozdek《数据结构与算法》第三版,清华大学出版社
4. 何德全《网络安全》清华大学出版社
5. http://www.tup.com.cn