大数运算使用string模拟
大整数运算包的设计与实现
(这里开扩充了一下打印大素数表)
1.问题描述
大整数运算是现代密码学算法实现的基础,重要性不言而喻。大整数我们指的是二进制位512、1024和2048的数,一般的语言不支持。
3.实现提示
在选择了大整数的存储结构之后,主要实现以下运算:
①模加
②模减
③模乘
④模整除
⑤模取余
⑥幂模
⑦GCD
⑧乘法逆
⑨素数判定与生成
首先要定义大数类型
采用的是string,加bool表示符号来确定
class BigInt
{
public:
bool flag;//符号
string values;//大整数
BigInt() //构造方法
{
values = "";
flag = true;
}
BigInt(string str)
{
values = str;
flag = true;
}
};
加法:
思路:利用计算机模拟手算的方式进行
1:先进行对齐,把短的一方补齐至相同位数。
2:倒置,开始计算,将进位计入下一次计算,最后一次还有进为特殊判断
3:得出结果
ps:因为开始思路不清晰,没有进行判定是否会有因为符号引起的变化
核心代码
temp = (a[i] - '0' + b[i] - '0' + jingwei) % 10;//模拟加法
jingwei = (a[i] - '0' + b[i] - '0' + jingwei) / 10;//进为判断
完整代码
BigInt BigIntJiafa(BigInt lvalues, BigInt rvalues)
//这里选择性的判定了一定是正数
{
BigInt ans;
if (lvalues.values == "0")//特殊情况
{
ans.values = rvalues.values;
return ans;
}
if (rvalues.values == "0")
{
ans.values = lvalues.values;
return ans;
}
//记录长度
int lsize, rsize;
lsize = lvalues.values.size();
rsize = rvalues.values.size();
if (lsize < rsize)//进行对齐通过补零实现
{
for (int i = 0; i < rsize - lsize; i++)
{
lvalues.values = "0" + lvalues.values;
}
}
if (lsize > rsize)
{
for (int i = 0; i < lsize - rsize; i++)
{
rvalues.values = "0" + rvalues.values;
}
}
lsize = lvalues.values.size();
string res = "";
//逆转字符串方便计算
reverse(lvalues.values.begin(), lvalues.values.end());
reverse(rvalues.values.begin(), rvalues.values.end());
string a, b;
int temp, jingwei = 0;
a = lvalues.values;
b = rvalues.values;
for (int i = 0; i < lsize; i++)//正式开始计算进位
{
temp = (a[i] - '0' + b[i] - '0' + jingwei) % 10;
jingwei = (a[i] - '0' + b[i] - '0' + jingwei) / 10;
res = res + char(temp + '0');
}
if (jingwei == 1)//最后一次是否产生进为
{
res = res + "1";
}
reverse(res.begin(), res.end());
ans.values = res;
ans.flag = true;
return ans;
}
乘法
思路:
用例子来表现
完整代码:
BigInt BigIntChengfa(BigInt lvalues, BigInt rvalues)
{
BigInt res;
if (lvalues.values == "0" || rvalues.values == "0")//特殊情况
{
res.values = "0";
res.flag = true;
return res;
}
if (lvalues.values == "1")
{
return rvalues;
}
if (rvalues.values == "1")
{
return lvalues;
}
if (lvalues.flag == false || rvalues.flag == false)//处理符号问题
{
if (lvalues.flag == false && rvalues.flag == false)
{
res.flag = true;
}
else
{
res.flag = false;
}
}
string a, b;
int lsize, rsize;
a = lvalues.values;
b = rvalues.values;
lsize = a.size();
rsize = b.size();
reverse(a.begin(), a.end());
reverse(b.begin(), b.end());
int n2 = 0, n3 = 0, jingwei = 0, t = 0, dangqian = 0;
string temp;
for (int i = 0; i < rsize; i++)//开始计算
{
n2 = b[i] - '0';
t = 0;
jingwei = 0;
dangqian = 0;
temp = "";
for (int h = 0; h < i; h++)//计算权重
//eg:十位X乘数要补一个零
{
temp += '0';
}
for (int j = 0; j < lsize; j++)//算出当前位数的值
{
n3 = a[j] - '0';
t = n2 * n3 + jingwei;
dangqian = t % 10;
jingwei = t / 10;
temp += char(dangqian + '0');
}
if (jingwei)
{
temp += char(jingwei + '0');
}
reverse(temp.begin(), temp.end());
BigInt asd;
asd.values = temp;
//cout << "temp= " << temp << endl;
res = BigIntJiafa(asd, res);//将上一个位算出的值加上当前位的值
}
return res;
}
减法
和乘法大同小异,但是要处理特殊情况
直接上代码:
BigInt BigIntJianfa(BigInt lvalues, BigInt rvalues)
{
BigInt ans;
//开始计算
//判断是不是特殊情况
//负数减负数
if (lvalues.flag == false && rvalues.flag == false)
{
BigInt temp;
temp = lvalues;
lvalues = rvalues;
rvalues = temp;
}
//负数减正数
if (lvalues.flag == false && rvalues.flag == true)
{
BigInt temp = BigIntJiafa(lvalues, rvalues);
temp.flag = false;
return temp;
}
//正数减负数
if (lvalues.flag == true && rvalues.flag == false)
{
BigInt temp = BigIntJiafa(lvalues, rvalues);
temp.flag = true;
return temp;
}
if (lvalues.values == "0")
{
ans.values = rvalues.values;
ans.flag = false;
return ans;
}
if (rvalues.values == "0")
{
ans.values = lvalues.values;
ans.flag = true;
return ans;
}
//判断l r 那个大
//开始计算
int lsize, rsize;
//实际上是默认了 lvalue > rvalue
lsize = lvalues.values.size();
rsize = rvalues.values.size();
int x = BigCompare(lvalues.values, rvalues.values);
if (x == 3)
{
BigInt temp;
temp = lvalues;
lvalues = rvalues;
rvalues = temp;
ans.flag = false;
}
if (x == 2)
{
ans.values = "0";
return ans;
}
if (x == 1)
{
ans.flag = true;
}
if (lsize > rsize)
{
for (int i = 0; i < lsize - rsize; i++)
{
rvalues.values = "0" + rvalues.values;
}
}
lsize = lvalues.values.size();
//颠倒方便计算
reverse(lvalues.values.begin(), lvalues.values.end());
reverse(rvalues.values.begin(), rvalues.values.end());
string a, b, res;
a = lvalues.values;
b = rvalues.values;
for (int i = 0; i < lsize; i++)
{
if (a[i] < b[i])//需要借位情况
{
int j = 1;
while (a[i + j] == '0')
{
a[i + j] = '9';
j++;
}
a[i + j] -= 1;
char s = char(a[i] + '5' + '5' - '0' - b[i]);
res += s;
}
else
{
char s = char(a[i] - b[i] + '0');
res += s;
}
}
//再颠倒回来但是没有效果
reverse(res.begin(), res.end());
ans.values = res;
int qv = 0;
while (ans.values[qv] == '0')
qv++;
ans.values.erase(0, qv);
if (ans.values == "")
ans.values += '0';
return ans;
}
除法
思路如图:
采用模拟手算,不断的维护减法队列,最终求出结果
eg:12345678/5678,除数4位,被除数9位
从除数取出四位1234减去5678,够就继续减,不够再取一位变成12345 – 5678 = 6667,判断是否可以再减 – 5678 = 989,判断不够减,取出一位变成9896,在进行判断。。。。
以此往复得到结果
ps:这里写了一个判断字符串那个大的函数,左边大返回1,相等返回2,右边大返回3
int BigCompare(string lvalue, string rvalue)
{
int lsize, rsize;
lsize = lvalue.size();
rsize = rvalue.size();
if (lsize != rsize)
{
if (lsize > rsize)
{
return 1;
}
return 3;
}
for (int i = 0; i < lsize; i++)
{
if (lvalue[i] > rvalue[i])
{
return 1;
}
if (lvalue[i] < rvalue[i])
{
return 3;
}
}
return 2;
}
这里是除法源码
BigInt BigIntChufa(BigInt lvalue, BigInt rvalue)
{
BigInt res;
int yidong = 0;
string buffer = "";
int lsize, rsize;
lsize = lvalue.values.size();
rsize = rvalue.values.size();
int x = BigCompare(lvalue.values, rvalue.values);
if (x == 2)//相等
{
res.values = "1";
res.flag = true;
return res;
}
if (x == 3)//右边大
{
res.values = "0";
return res;
}
if (lvalue.values == "0")//除数或者被除数等于零的情况下
{
res.flag = "0";
return res;
}
if (rvalue.values == "0")
{
res.values == "error";
return res;
}
//开始模拟触发
//res.values="";
while (1)
{
while (BigCompare(buffer, rvalue.values) == 3)//维护除数队列
{
if (yidong == lsize)//当被除数全部进入队列的时候表示即将完成
{
int qv = 0;
while (res.values[qv] == '0')
qv++;
res.values.erase(0, qv);
if (res.values == "")
res.values += '0';
return res;
}
int ss = 0;
while (buffer[ss] == '0')
ss++;
buffer.erase(0, ss);
buffer += lvalue.values[yidong];
res.values += "0";
yidong++;
}
BigInt b_buffer;
while (BigCompare(buffer, rvalue.values) != 3)//减去被除数
{
//cout<<" 当前缓冲区: "<<buffer<<" "<<rvalue.values<<" ";
b_buffer.values = "";
b_buffer.values += buffer;
BigInt temp;
temp = BigIntJianfa(b_buffer, rvalue);
res.values[yidong - 1]++;
int ss = 0;
while (temp.values[ss] == '0')
ss++;
temp.values.erase(0, ss);
//cout<<temp.values<<endl;
buffer = temp.values;
}
}
}
余数
余数就是除法计算的时候除法队列剩余的数就是余数了所以代码完全相同只是返回值不同
这又这里有细微的变化
if (yidong == lsize)
{
res.values = buffer;
int qv = 0;
while (res.values[qv] == '0')
qv++;
res.values.erase(0, qv);
if (res.values == "")
res.values += '0';
return res;
}
幂模
思路:幂模采用的是每次将幂数除二,然后下面自乘一次,如果除二有余那就再乘一次
BigInt BigIntMimo(BigInt lvalues, BigInt rvalues, BigInt m)
{
BigInt temp, chushu;
temp.values = "1";
chushu.values = "2";
BigInt asd;
while (rvalues.values != "0")//当幂数还没有降到0就一直进行除二
{
asd = BigIntYushu(rvalues, chushu);
if (asd.values == "1")//余一需要多一次乘法
{
temp = BigIntChengfa(lvalues, temp);
temp = BigIntYushu(temp, m);
}
//自乘
lvalues = BigIntChengfa(lvalues, lvalues);
lvalues = BigIntYushu(lvalues, m);
rvalues = BigIntChufa(rvalues, chushu);
//cout << rvalues.values << endl;
}
return BigIntYushu(temp, m);
}
gcd求最大公约数
思路欧几里得算法
BigInt BigIntGCD(BigInt lvalue, BigInt rvalue)
{
if (BigCompare(lvalue.values, rvalue.values) == 2)//当相等的时候
{
return lvalue;
}
if (BigCompare(lvalue.values, rvalue.values) == 3)//左边小那就和右边进行置换保证左边大
{
BigInt temp;
temp = lvalue;
lvalue = rvalue;
rvalue = temp;
}
BigInt asd;
asd.values = "0";
while (BigCompare(rvalue.values, asd.values) != 2)
//当余数为零,gcd就可以停止了
{
BigInt temp = BigIntYushu(lvalue, rvalue);
lvalue = rvalue;
rvalue = temp;
}
return lvalue;
}
逆元
(为啥采用了两种,是因为前面不会求逆元,前面又有个遍历求逆元的方法就使用了)
逆元使用了两种思路进行计算,第一是直接更具逆元定义:在模n的情况下,如果m是逆元,那么m的1到(n-1)次方模n的值分别不同,也就是会覆盖群的所有元素,缺点速度慢,只能适用于n比是比较小的情况下
BigInt BigIntManNiyuan(BigInt lvalue, BigInt rvalue)
{
BigInt asd;
asd = BigIntGCD(lvalue, rvalue);//判断是否有逆元
if (asd.values != "1")
{
BigInt res;
res.values = "0";
return res;
}
BigInt temp, lala, tt("1"), aaa;
lala.values = "2";
while (true)//开始遍历
{
temp = BigIntChengfa(lvalue, lala);
aaa = BigIntYushu(temp, rvalue);
if (aaa.values == "1")
{
break;
}
lala = BigIntJiafa(tt, lala);
}
return lala;
}
第二种思路是查资料得到的,可以使用扩展欧几里得算法求逆元eg:
源代码;
这个不是自己写的,是看了别人的代码之后才写的
BigInt BigIntKuaiNiyuan(BigInt a, BigInt b, BigInt& x, BigInt& y)
//扩展欧几里得算法
{
BigInt d = a;
if (b.values != "0")
{
d = BigIntKuaiNiyuan(b, BigIntYushu(a, b), y, x);
BigInt temp;
temp = BigIntChufa(a, b);
temp = BigIntChengfa(temp, x);
y = BigIntJianfa(y, temp);
}
else
{
x.values = "1";
y.values = "0";
}
return d;
}
BigInt BigIntNiyuan(BigInt lvalues, BigInt rvalues)
{
BigInt x, y;
BigIntKuaiNiyuan(lvalues, rvalues, x, y);
if (x.flag == false)
{
x.flag = true;
x = BigIntJianfa(rvalues, x);
}
return x;
}
素数检测
素数检测采用的是Miller-Robin算法,还进行了一些小偷懒
先将大素数进行分解
n-1=2^x * m;
计算出x,m;根据定理
若p为素数则:a^(p-1) =1 (mod p)(反过来不一定成立,但是有概率成立,利用多次计算将成功概率提高到一定程度视为成立)代入
a^ (2^x * m) =1 (mod p) => ((a^m) ^2) ^x将x带入计算,从1-x,如果出现结果等于1或者p-1,表示符合素数的定义,全程没出现代表不是素数
bool Miller_my(BigInt n)
{
BigInt yi, ling("0"), er("2");
BigInt asd[8];//这里偷了懒,原本可以直接进行随机选取的,但是我在这里偷了懒
asd[0].values = "3";
asd[1].values = "5";
asd[2].values = "7";
asd[3].values = "11";
asd[4].values = "13";
asd[5].values = "17";
asd[6].values = "19";
asd[7].values = "23";
yi.values = "1";
BigInt m = BigIntJianfa(n, yi);
BigInt nm = m;
if (n.values == er.values)
{
return true;
}
int cishu = 0;
BigInt yushu;
while (true)//计算出m,x
{
yushu = BigIntYushu(m, er);
if (yushu.values != "0")
{
break;
}
cishu++;
m = BigIntChufa(m, er);
}
//cout << m.values << " " << cishu << endl;
for (int i = 0; i < 8; i++)//进行八次测试
{
//cout << "\n正在测试 " << i << endl;
BigInt x, y;
x = BigIntMimo(asd[i], m, n);
for (int j = 0; j < cishu; j++)
{
//cout << cishu;
y = BigIntChengfa(x, x);
y = BigIntYushu(y, n);
int zzz, xxx, ccc;
zzz = BigCompare(y.values, yi.values);
xxx = BigCompare(x.values, yi.values);
ccc = BigCompare(x.values, nm.values);
if (zzz == 2 && xxx != 2 && ccc != 2)
{
return false;
}
x = y;
}
//cout << "\n";
if (x.values != "1")
{
return false;
}
}
return true;
}
素数生成
素数生成可以拆成两步第一步是生成随机数,判断是否是素数
这里有一个随机数生成函数,其中关键就是rand函数和大素数结尾一定是1,3,7,9这里把7去掉,因为7的素数p会出现,2p+1,结尾是5,一定是个合数,不配合后续算法
BigInt shengchen(int x)
//x表示位数
{
cout << "x= " << x << endl;
srand(time(0));
BigInt asd;
asd.values = "";
int sz[3] = { 1,3,9 };
int sss = rand() % 9;//第一位不可以为零
asd.values += char('0' + sss + 1);
for (int i = 0; i < x - 2; i++)
{
int s = rand() % 10;
asd.values += char('0' + s);
}
int ddd = rand() % 4;
BigInt dddd;
for (int ddd = 0; ddd < 3; ddd++)//借位139
{
dddd = asd;
dddd.values += char('0' + sz[ddd]);
// cout << "生成的数为: " << dddd.values;
if (Miller_my(dddd) == true)
{
//cout << " 满足" << endl;
return dddd;
}
}
return shengchen(x);//不满足重新开始运行
}
大素数打表:
方便在后面的elgamal的大素数p的计算
ofstream destFile("sushu.txt", ios::out);
if (!destFile)
{
cout << "打开失败" << endl;
return 0;
}
BigInt asd,er("2"),yi("1");
int x = 200;
int sss = 16;
while (x--)
{
asd = shengchen(sss);
//cout << asd.values << endl;
asd = BigIntChengfa(asd, er);
asd = BigIntJiafa(asd, yi);
//cout << asd.values << endl;
if (Miller_my(asd) == true)
{
destFile << asd.values << "\n";
}
}
destFile.close();