大整数乘法的详解

article/2025/9/14 6:46:56

一.问题

由于编程语言提供的基本数值数据类型表示的数值范围有限,不能满足较大规模的高精度数值计算,因此需要利用其他方法实现高精度数值的计算,于是产生了大数运算。尤其是乘法运算,下面就是大整数的乘法的过程(加 减法都一样的原理)。

二.解决问题的方法

方法一(传统的相乘逐步相加)

乘法规律,一个数的第i位和另一个数的第j位相乘,一定会累加到结果的第i+j位,结果的数组一个数组元素存2位数,最后对结果整除得到进位,mod得到余数就是i+j位的数字,最后打印出来。

 对于大整数比较方便的输入方法是,①按字符型处理,存储在字符串数组s1、s2中,计算结果存储在整型数组ans中。 ②通过字符的ASCII码,数字字符可以直接参与运算,i位数字与j位数字相乘的表达式为:(s1[i]-‘0’)*(s2[j]-‘0’)。 ③每一次数字相乘的结果位数是不固定的,而结果数组中每个元素只存储一位数字,所以用变量t暂存结果,对t mod运算得到的就是ans[i+j]的值,若超过1位数则进位,用变量b存储。

这种做法的时间复杂度为o(n^2)

c语言源码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
int max(int a,int b){return a>b?a:b;
}int main(){char s1[205], s2[205],ans[1000];int str1[205],str2[205];int len1, len2,i;while( scanf("%s%s", s1, s2)!=EOF){len1 = strlen(s1), len2 = strlen(s2);memset(str1,0,205);//初始化0memset(str2,0,205);memset(ans,0,1000);int len = 0;for(i = 0; i < len1; ++ i)str1[i] = s1[len1 - 1 - i] - '0';for(i = 0; i < len2; ++ i)str2[i] = s2[len2 - 1 - i] - '0';for( i = 0; i < len1; ++ i){int b = 0;   //每遍历完数组a的一个数,进位b都要初始化为0for(int j = 0; j < len2 || b; ++ j)//当str[j]没遍历完,或者最高位满十需要进位,进位不为0{int t = ans[i + j] + str1[i]*str2[j] + b;ans[i + j] = t%10;  //余数就是该ans[i+j]位置的数b = t/10;//进位//len = max(len, j + i);}len = i+j-1  //最终的位数}for( i = len; i >= 0; -- i)  //倒置输出printf("%d", ans[i]);printf("\n");}return 0;
}

方法二(分治法)

分治算法解题的一般步骤:

  • 分解:将要解决的问题划分为若干个规模较小的同类问题
  • 求解:当子问题划分的足够小时,用较简单的方法解决
  • 合并:按原问题的要求,将子问题的解逐层合并构成原问题的解

①两个大整数在理想状态下:就是两个大整数的位数相同

现在有两个大整数X,Y;   设X, Y是n位十进制整数,分段表示如下:

即 X=A*10^(n/2)+B,   Y=C*10^(n/2)+D  则:

本来可以直接算AD+BC,但是这样效率变低了,所以对AD+BC进行分解优化后得:

计算成本:3次n/2位乘法,6次不超过n位加减法,2次移位,所有加法和移位共计O(n)次运算。由此可得

理想状态下c语言代码:(不超过long long 型,后面做法会用字符串接收大整数

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#define sign(x) ((x>0)?1:-1)//_int64等同于long long 
//%I64d等同于%lld_int64 mutipy(_int64 a,_int64 b,int num){  //两个long long 类型的大整数相乘,后面会用字符串做法解决int s=sign(a)*sign(b);a=(a>0)?a:-a ;b=(b>0)?b:-b ;if(num==0)  //递归出口,return 0;else if(num==1){  //当a,b只有一位数时,直接相乘return s*a*b;  }else{_int64 A=a/(int)pow(10,(int)(num/2));  //分离大整数a的高位_int64 B=a%(int)pow(10,(int)(num/2)); //分离大整数a的低位_int64 C=b/(int)pow(10,(int)(num/2));   //分离大整数b的高位_int64 D=b%(int)pow(10,(int)(num/2));     //分离大整数b的低位_int64 AC=mutipy(A,C,(int)(num/2));  //分治计算AC_int64 BD=mutipy(B,D,(int)(num/2));  //分治计算BD_int64 ABCD=mutipy((A-B),(D-C),(int)(num/2))+AC+BD;  //计算(A-B)(D-C)+AC+BDreturn s*(_int64)(AC*pow(10,(int)(num/2)+(int)(num/2))+ABCD*pow(10,(int)(num/2))+BD);  //返回结果}
} 
int main(){_int64 a,b,c,temp;  //long long a,b,c;int len1=0,len2=0;while(scanf("%I64d %I64d",&a,&b)){temp=a;while(temp){  //计算a的位数len1++;temp=temp/10;}	c=mutipy(a,b,len1);printf("%I64d\n",c);	}return 0;
}

其实上述有个缺陷:那就是只能是long long 类型的大整数相乘,超出了long long 型就会报错。解决方法看下面的做法

②两个大整数在非理想状态下:就是两个大整数的位数不相同

我们还是假设有两个大整数X、Y,它们的位数不相同,现在要求X*Y的乘法,我们采用分治的算法,将X、Y分别拆分为A与B、C与D,如下图:

上式一共需要进行2次xn0的乘法(AC、AD各一次)、2次yn0的乘法(AC、BC各一次)和3次加法,因而该算法的时间复杂度为

跟上面一样,对AD+BC进行分解优化得:

修改后的时间复杂度:

由于T(min(m,n))<T(m)+T(n),所以修改后的算法更好,时间复杂度:T(m+n)=O(nlog3)=O(n1.59)

非理想状态下的c语言代码:(不超过long long 型,后面做法会用字符串接收大整数

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#define sign(x) ((x>0)?1:-1)
//_int64等同于long long 
//%I64d等同于%lld_int64 mutipy(_int64 a,int numa,_int64 b,int numb){ //两个long long 类型的大整数相乘,后面会用字符串做法解决int s=sign(a)*sign(b);a=(a>0)?a:-a ;b=(b>0)?b:-b ;if(numa==0||numb==0)  //递归出口,return 0;else if((numa==1&&numb==1)||numa==1||numb==1){  //当a,b只有一位数时,直接相乘return s*a*b;  }else{int num1=numa/2;  //定义了大整数a的低位的位数x0int num2=numa-num1;  //定义了大整数a的高位的位数x1int num3=numb/2;   //定义了大整数b的低位的位数x2int num4=numb-num3; //定义了大整数b的高位的位数x3_int64 A=a/(int)pow(10,num1);  //分离大整数a的高位_int64 B=a%(int)pow(10,num1); //分离大整数a的低位_int64 C=b/(int)pow(10,num3);   //分离大整数b的高位_int64 D=b%(int)pow(10,num3);     //分离大整数b的低位_int64 AC=mutipy(A,num2,C,num4);  //分治计算AC_int64 BD=mutipy(B,num1,D,num3);  //分治计算BD_int64 ABCD=mutipy(((_int64)(A*pow(10,num1))-B),num2,(D-(_int64)(C*pow(10,num1))),num4);  //计算(A*10^num1-B)(D-C*10^num3)return s*(_int64)(2*AC*pow(10,(num1+num3))+ABCD+2*BD);  //返回结果	 }
}
int main(){_int64 a,b,c,temp;  //long long a,b,c;int len1=0,len2=0;while(scanf("%I64d %I64d",&a,&b)){temp=a;while(temp){  //计算a的位数len1++;temp=temp/10;}temp=b;while(temp){      //计算b的位数len2++;temp=temp/10;}	c=mutipy(a,len1,b,len2);printf("%I64d\n",c);	}  return 0;
}

非理想状态下的c语言代码:(任意位数相乘,可以超出longlong类型)

#include<stdio.h>
#include<string.h>
int result[255];void cal( char a[],int numa,char b[],int numb,int s){int num1=numa/2;     //num1为B的位数,B属于低位的那一部分int num2=numa-num1;  //num2为A的位数,A属于高位的那一部分int num3=numb/2;    //num3为D的位数,D属于低位的那一部分int num4=numb-num3;  //num4为C的位数,C属于高位的那一部分char A[255],B[255],C[255],D[255];int k=0;if(numa==0||numb==0)  //当位数为0时return ;else if(numa==1&&numb==1){  //分治递归到当数组a和b的位数全为1时result[s]+=(a[0]-'0')*(b[0]-'0');  //直接计算,把他的结果直接加到对应数组result的位置return;}else{  //当数组a和b的位数至少有一个不为1时for(int i=0;i<num2;i++) //获取数组a的高位部分AA[k++]=a[i];k=0;for(i=num2;i<numa;i++)  //获取数组a的低位部分BB[k++]=a[i];k=0;for(i=0;i<num4;i++)  //获取数组b的高位部分CC[k++]=b[i];k=0;for(i=num4;i<numb;i++)  //获取数组b的高位部分DD[k++]=b[i];cal(A,num2,C,num4,s+num1+num3);  //AC ,在result[s+num1+num3]的位置存储AC,也是说偏移s+num1+num3位,s初始化为0cal(B,num1,C,num4,num3+s);  //BC,偏移num3+s位cal(A,num2,D,num3,num1+s);  //AD,偏移num1+s位cal(B,num1,D,num3,s);  //BD,偏移s位}}void exchange(int result[],int len){ //将result数组中的值进行整理,转化输出int i = 0;int t,p1,p2;while(i<=len){    //以特殊数最大位数为终止条件 if(result[i] < 10);      //当result[i]的值小于10,就不处理,直接保存在result[i]中else if(result[i] < 100){  //当0<=result[i]<100时t = result[i] % 10;  //余数就是该位置result[i]的值p1 = result[i] / 10;  //result[i]进位result[i] = t;       //将余数t保存至result[i]中result[i+1] += p1;   //进位把他加到result[i+1]中	}else{   //当result[i]>=100时t = result[i] % 10;     //余数就是该位置result[i]的值p1 = result[i] / 10 % 10;  //这是result[i]满十进位p2 = result[i] / 100;       //这是result[i+1]满十进位 result[i] = t;         //将余数t保存至result[i]中result[i+1] += p1;     //进位把他加到result[i+1]中result[i+2] += p2;     //进位把他加到result[i+2]中}i++;  //位数加1} 
}int main(){char a[100],b[100];int j,i=0,len1,len2;while(scanf("%s %s",a,b)!=EOF){len1=strlen(a);len2=strlen(b);cal(a,len1,b,len2,0);  exchange(result,len1+len2); for(i=len1+len2-1;i>=0;){  //去高位的0去掉if(result[i]==0){  while(result[i]==0){i--;}}elsebreak;}for(j=i;j>=0;j--) //从非零的位置打印printf("%d",result[j]);		 printf("\n");}return 0;}


http://chatgpt.dhexx.cn/article/urdnAPvK.shtml

相关文章

Ubuntu server树莓派版本默认用户名密码及密码修改

树莓派安装的Ubuntu server镜像&#xff0c; 默认的初始用户及密码&#xff1a; ubuntu # user ubuntu # passwd默认信息查看 在烧入镜像的内存卡中&#xff0c; 可以查看到默认的用户信息 默认密码修改 在登录界面 输入初始化的用户名和密码后&#xff0c; 会提示是第一…

2022.04.04树莓派最新镜像问题,树莓派如何设置初始化的账户和密码

树莓派最新的arm64位系统&#xff0c;更新时间是2022年4月4日&#xff0c;这个版本的树莓派取消了默认的账户密码&#xff0c;也就是原来一直使用的pi和对应的默认密码raspberry被取消了&#xff0c;现在如果想要使用的话必须自己设置&#xff0c;下面有两种方法可以设置自己的…

树莓派启用无密码 sudo

启用无密码 sudo&#xff0c;可以在不提供密码的情况下在树莓派上运行程序。 登录 Raspberry Pi 命令行界面。假设 Raspberry Pi 的默认用户名和密码分别为 pi 和 raspberry。在命令行界面中&#xff0c;键入以下命令&#xff1a; sudo nano /etc/sudoers 3. 通过添加以下行启…

最新树莓派系统PUTTY用默认用户名和密码登录不上的解决方法

最近我在树莓派配置深度学习环境&#xff0c;然后直接载了别的博主的树莓派镜像&#xff0c;发现博主给的用户名&#xff0c;密码登不上&#xff0c;于是乎&#xff0c;就打算自己配置深度学习环境&#xff0c;结果我下在了最新版本的树莓派镜像系统&#xff08;2022-04-04-ras…

树莓派默认账号密码串口登录不了的Bug解决

一、问题总结 我用的树莓派型号是树莓派Pi3,刷机用的镜像是2023版的&#xff0c;23版的镜像文件较大8G的SD卡不够用 问题描述&#xff1a;刷完机在用串口登录树莓派的时候登录不了 &#xff01;&#xff01;&#xff01; 默认账号&#xff1a;pi 默认密码&#xff1a;raspb…

树莓派raspberry pi 4 SSH默认密码无法登录解决办法

以前玩过一段时间树莓派&#xff0c;只要开通ssh就可以&#xff0c;默认用户pi,默认密码: raspberry。 远程连接就可以。 但今天再玩却死活无法登录&#xff0c;如下&#xff1a; 出了什么幺蛾子哦&#xff1f;&#xff01;&#xff01; 上网一查&#xff0c;才知道pi账号在…

【入坑树莓派】烧录系统都烧录了三次(树莓派默认账户密码错误/已删除)

为什么会烧录了三次&#xff1f;我因为实在不知道问题出在哪&#xff0c;所以只能推倒重来三次。。。 三次&#xff0c;两次连接不上WiFi&#xff0c;好不容易一次连接上了&#xff0c;出现了下面这幅画面 再三确认了默认用户名是pi&#xff0c;密码raspberry&#xff0c;奈何…

树莓派用默认账号和密码登录不上怎么办;修改树莓派的密码

目录 一、重置树莓派的默认账号和密码 二、修改树莓派的密码 三、超级用户和普通用户的切换 一、重置树莓派的默认账号和密码 在SD卡中根目录建立文件userconf 在userconf中输入如下内容&#xff1a; pi:$6$/4.VdYgDm7RJ0qM1$FwXCeQgDKkqrOU3RIRuDSKpauAbBvP11msq9X58c8Q…

树莓派默认密码_树莓派介绍:没有显示器,怎样远程控制树莓派?

树莓派(Raspberry Pi)是学习计算机知识、架设服务器的好工具&#xff0c;价格低廉&#xff0c;可玩性高。 由于树莓派系统开始时默认是不开启ssh连接的&#xff0c;没有开启ssh就无法连接电脑&#xff0c;后续的操作也就无从谈起。 准备工作 1.烧录好系统的树莓派3B主板 2.普…

树莓派默认密码_树莓派快速指南,从购买到开机

本文给树莓派初学者&#xff0c;或者准备入手树莓派的同学看&#xff0c;主要包括什么是树莓派&#xff0c;入门树莓派需要那些配件&#xff0c;如何烧录树莓派系统&#xff0c;和树莓派的开机指南。如果你不知道学习树莓派需要那些配件&#xff0c;或者购买了树莓派不知道怎么…

树莓派的登录

一、串口登录&#xff1a; 由于树莓派在默认情况下&#xff0c;树莓派的串口和蓝牙链接。而我们要进行串口登录就得断开蓝牙&#xff0c;把串口用来进行数据通信。 1.打开SD卡根目录的"config.txt"文件&#xff0c;将以下内容添加在最后并且保存。 dtoverlaypi3-m…

树梅派raspberrypi的su密码root密码是什么

入手了树梅派4B,上电后&#xff0c;安装portainer的时候想获得root权限&#xff0c;输入su&#xff0c;恩&#xff1f;密码是多少&#xff1f;不记得了&#xff0c;输了好几遍登录密码也不对&#xff0c;无法进入了。 查了一下&#xff0c;原来树莓派的Raspbian系统root用户默…

【mysql】mysql 模糊查询 like 语句

mysql 模糊查询 like 语句 一 like 语句 %xxx%&#xff1a;查询 username 字段中包含 xxx 的记录。 select * from user where username like ‘%xxx%’; %xxx&#xff1a;查询 username 字段中以 xxx 结尾的记录。 select * from user where username like ‘%xxx’; xx…

mybatis模糊查询like语句怎么写

写法为&#xff1a;1、使用“${...}”&#xff0c;语法为“like ${...}”&#xff1b;2、使用“#{...}”&#xff0c;语法为“like #{...}”&#xff1b;3、使用CONCAT函数连接参数形式&#xff0c;语法为“like CONCAT(%,#{...},%)”。 本教程操作环境&#xff1a;windows7系统…

mysql like模糊查询表名_mysql模糊查询like/REGEXP

增删改查是mysql最基本的功能&#xff0c;而其中查是最频繁的操作&#xff0c;模糊查找是查询中非常常见的操作&#xff0c;于是模糊查找成了必修课。 like模式 like意思是长得像&#xff0c;有两个模式&#xff1a;_和% _表示单个字符&#xff0c;通常用来查询定长的数据&…

模糊查询like用法实例(Bee)

like通过两个匹配符%和_进行模糊查询. %: 匹配任意个任意字符 _: 匹配一个字符 以下以userid为例, 在数据库中的值如下图所示: V1.11及之前版本&#xff0c; 使用Op.like&#xff0c; 需要判断值是否为空字段&#xff0c;是否只含有匹配符(%和_) Op.like可以创建比左右匹配…

sql模糊查询 like

like 经常与where 字句和通配符在一块进行使用&#xff0c;表示像啥啥&#xff0c;模糊查询 通配符 主要是 _ 和 %   &#xff05; 百分号表示零个&#xff0c;一个或多个字符   _ 下划线表示单个字符 **注意&#xff1a;**1、 MS Access使用问号&#xff08;?&#xff09…

SQL中的模糊查询like

首先我们创建一个Person表。 create table Person(cname varchar2(50),cage number(3) );插入一些数据: insert into Person (cname,cage) values(张三,19); insert into Person (cname,cage) values(张三丰,20); insert into Person (cname,cage) values(张一,30); insert i…

使用关键字like进行模糊查询

【模糊查询】&#xff1a;使用关键字like [支持%或者下划线匹配&#xff0c;%匹配任意多个字符&#xff0c;一个下划线只匹配任意一个字符。] 实例&#xff1a; 查询名字中带有字母o的员工&#xff1a; select * from emp where ename like %o%; 找出名字以T结…

redis过期策略和持久化

Redis过期策略 注&#xff1a;本文主要参考自《Redis设计与实现》 1、设置过期时间 expire key time(以秒为单位)--这是最常用的方式setex(String key, int seconds, String value)--字符串独有的方式 具体的使用方式&#xff1a;查看"java企业项目开发实践"的第九章…