前言
众所周知,计算机底层原理是二进制,也就是计算机的一切计算全都是基于二进制来操作的。然后我突发奇想,突然想到我自己能不能模拟计算机来解决 A+B Problem。
在实操之前,我们需要知道一点干货:
二进制
在现实生活中,我们的数字并不是单纯的全是数,有时前面还会带有符号,比如 444 我们可以读作“正四”,那么写出来就可以写作 +4+4+4,而 −5-5−5 就直接念作“负五”,写出来也是这个样子。
所以相应的,我们要将一个十进制数转换成二进制,不是简单的将数变过去就行,还要带上符号。因此,我们将转换后的二进制编码的最高位用来作为符号位,如果是非负数就是 000,否则就是 111。比如说我要将 (4)10(4)_{10}(4)10 转换成 8 位的有符号二进制编码,那么最终结果就是 (00000100)2(00000100)_2(00000100)2,其中第一个 000 并不是数字,而是代表这个数是正数。
我刚刚写的那个二进制编码就叫做原码,说白了就是一个十进制数直接转换成二进制然后再带上符号位后的二进制编码。
当然,如果两个数都是非负数,那么我们可以直接用原码相加得到结果(注意做加法时符号位也要带入计算,如果有数字溢出则直接忽略),比如 4+3=74+3=74+3=7,在 4 位有符号二进制编码下就是 (0100)2+(0011)2=(0111)2=(7)10(0100)_2+(0011)_2=(0111)_2=(7)_{10}(0100)2+(0011)2=(0111)2=(7)10,但是如果负数也用原码,那么就完全错了,举个例子:(4)10+(−3)10=(0100)2+(1011)2=(1111)2=(−7)10(4)_{10}+(-3)_{10}=(0100)_2+(1011)_2=(1111)_2=(-7)_{10}(4)10+(−3)10=(0100)2+(1011)2=(1111)2=(−7)10,显然不对。
为了解决这个问题,我们需要一种新的东西——反码。反码就是说:将原二进制编码除符号位以外的数全都反过来,也就是 000 变 111,111 变 000。但是反码显然也不能解决这个问题(你算一下上面的式子就能看出来),于是我们再次更新,换成了补码。
补码的构造很简单,只需要将一个二进制编码的反码的最后一位加一个 111,然后不断进位直到进位结束。也就是说:(−3)10(-3)_{10}(−3)10 的原码应该是 (1011)2(1011)_2(1011)2,那么反码就是 (1100)2(1100)_2(1100)2,补码就是 (1101)2(1101)_2(1101)2。
说回原本的式子:显然非负数相加是可以直接用原码算的(随便举几个例子都能看出来),而负数的计算则必须用到补码。比如上面的式子:(4)10+(−3)10=(0100)2+(1101)2=(0001)2=(1)10(4)_{10}+(-3)_{10}=(0100)_2+(1101)_2=(0001)_2=(1)_{10}(4)10+(−3)10=(0100)2+(1101)2=(0001)2=(1)10,显然成立。至于为啥,我也不是很清楚,应该可以用数学式子推导出来,这里作者太懒了就不推了。
逻辑门
逻辑门是构成计算机运算的底层逻辑,包括与运算(and)、或运算(or)、异或运算(xor)、取反运算(not)等等。当然,逻辑门只能处理单个位之间的运算结果,也就是只能处理 000 与 111 之间的计算结果。
与运算的逻辑很简单,就像是数学里面的交集。简单来说:如果给定的两个数都是 111,那么结果就为 111,反之为 000。也就是说:0and0=0,0and1=0,1and0=0,1and1=10\operatorname{and}0=0,0\operatorname{and}1=0,1\operatorname{and}0=0,1\operatorname{and}1=10and0=0,0and1=0,1and0=0,1and1=1。
或运算则像数学里面的并集:如果两个数都是 000,那么结果为 000,否则为 111。也就是说:0or0=0,0or1=1,1or0=1,1or1=10\operatorname{or}0=0,0\operatorname{or}1=1,1\operatorname{or}0=1,1\operatorname{or}1=10or0=0,0or1=1,1or0=1,1or1=1。
异或运算则像数学里面的对称集:如果两个数相同,那么结果为 000,否则为 111。也就是说:0xor0=0,0xor1=1,1xor0=1,1xor1=00\operatorname{xor}0=0,0\operatorname{xor}1=1,1\operatorname{xor}0=1,1\operatorname{xor}1=00xor0=0,0xor1=1,1xor0=1,1xor1=0。
取反运算就像数学里面的补集:如果这个数为 000,则答案为 111。反之为 000。也就是说:not0=1,not1=0\operatorname{not}0=1,\operatorname{not}1=0not0=1,not1=0。
当然还有平常我们见到的左移位和右移位(也就是代码里的 << 和 >>),这里不赘述。
基础知识了解完了,现在我们该看看如何用逻辑门拼出来加法了。
加法
首先,假设当前这一位的两个数(因为是做加法,那么必然有两个数)是 x,y(x,y∈{0,1})x,y(x,y\isin\{0,1\})x,y(x,y∈{0,1}),然后上一位的进位是 vvv。那么我们先不考虑进位,此时这一位的变换规律显然有:0+0=0,0+1=1,1+0=1,1+1=00+0=0,0+1=1,1+0=1,1+1=00+0=0,0+1=1,1+0=1,1+1=0。至于最后一个式子是因为我们有进位,所以进位给进到了下一位,这一位就只有 000 了。显然能看出这是异或运算,这时再考虑进位,就是在原本的答案基础上再做一次异或运算。
然后考虑进位:在不考虑进位的情况下显然有 0+0=0,0+1=0,1+0=0,1+1=10+0=0,0+1=0,1+0=0,1+1=10+0=0,0+1=0,1+0=0,1+1=1(这里的结果指的是进位的数),可以观察到这是与运算。然后考虑有进位,如果有,那么只需要当前这一位里面有至少一个 111 就能成立。具体可详见下面这幅图:

(看起来还挺壮观的)
最后,我们直接照着图模拟出来:
#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
namespace fastio
{
inline int read()
{
int z=0,f=1;
char c=getchar();
if(c==EOF)
{
exit(0);
}
while(c<'0'||c>'9')
{
if(c==EOF)
{
exit(0);
}
if(c=='-')
{
f=-1;
}
c=getchar();
}
while(c>='0'&&c<='9')
{
z=z*10+c-'0';
c=getchar();
}
return z*f;
}
inline void write(int x)
{
if(x<0)
{
putchar('-');
x=-x;
}
static int top=0,stk[106];
while(x)
{
stk[++top]=x%10;
x/=10;
}
if(!top)
{
stk[++top]=0;
}
while(top)
{
putchar(char(stk[top--]+'0'));
}
}
inline void write(string s)
{
for(auto i:s)
{
putchar(i);
}
}
}
using namespace fastio;
void change_to_2(int x,bool *a,int &n)
{
n=60;
if(x>=0)
{
for(int i=1;i<=n;i++)
{
a[i]=(x>>i-1)&1;
}
n++;
}
else
{
x=-x;
for(int i=1;i<=n;i++)
{
a[i]=(x>>i-1)&1;
}
for(int i=1;i<=n;i++)
{
a[i]^=1;
}
for(int i=1;i<=n;i++)
{
if(a[i]==1)
{
a[i]=0;
}
else
{
a[i]=1;
break;
}
}
n++;
a[n]=1;
}
}
void change_to_10(bool *a,int n,int &x)
{
n--;
if(a[n+1]==0)
{
for(int i=n;i>=1;i--)
{
x=(x<<1)|a[i];
}
}
else
{
for(int i=1;i<=n;i++)
{
if(a[i]==0)
{
a[i]=1;
}
else
{
a[i]=0;
break;
}
}
for(int i=1;i<=n;i++)
{
a[i]^=1;
}
for(int i=n;i>=1;i--)
{
x=(x<<1)|a[i];
}
x=-x;
}
}
int n,m;
bool a[100006],b[100006],c[100006];
signed main()
{
int x=read(),y=read();
change_to_2(x,a,n);//转换成二进制
change_to_2(y,b,m);
bool v=0;
int len=max(n,m);
for(int i=1;i<=len;i++)//加法
{
c[i]=a[i]^b[i]^v;
v=((a[i]|b[i])&v)|(a[i]&b[i]);
}
int ans=0;
change_to_10(c,len,ans);//转换回十进制
write(ans);//输出
return 0;
}
至于减法,那很简单了,直接把减数取相反数,然后看成加法就行。

被折叠的 条评论
为什么被折叠?



