{"componentChunkName":"component---src-templates-blog-post-js","path":"/how-to-add/","result":{"data":{"site":{"siteMetadata":{"title":"😏","author":"thoamsy"}},"markdownRemark":{"id":"3821514a-a15c-594c-8424-bc10b539abd4","html":"<p>以前在面试候选人的时候，我总是喜欢先问一个问题：</p>\n<blockquote>\n<p>-1 的补码是多少？</p>\n</blockquote>\n<p>这么多次面试下来，结果令我很失望，绝大多数候选人，只是记住了大学时候学到的：<strong>取反加 1</strong>，接着就给出了 <em>10000000001</em> 这种错得离谱答案。</p>\n<p>确实，二进制是一个容易被忽略的东西，你不了解它并不会导致你写不出业务代码，也不会让你觉得很沮丧。即使你写过 <code class=\"language-text\">if (~arr.indexOf(x))</code> 这种类似的代码，但是这也不代表你就非常理解了二进制的兴趣。</p>\n<p>本文的目的主要是为了揭示在计算机中，<strong>整数</strong>加法和减法是如何被表示的。有一定经验的人应该都清楚，CPU 其实就是一个非常复杂的集成电路，而集成电路的最小单位就是各种逻辑门。想必你也听过了类似<em>与门，或门，与非门，或非门</em>等等逻辑门的名称，但是并没有一个叫做 <strong>加法门，减法门</strong>的东西。所以可以联想到加法减法的实现最终是由这些逻辑门组成的。我们在知道如何加法运算后，接着会看如何用补码来计算减法。</p>\n<h2 id=\"加法\" style=\"position:relative;\"><a href=\"#%E5%8A%A0%E6%B3%95\" aria-label=\"加法 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>加法</h2>\n<h3 id=\"基本概念\" style=\"position:relative;\"><a href=\"#%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5\" aria-label=\"基本概念 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>基本概念</h3>\n<p>首先需要明确的是，我们下面讨论的是二进制加法，它的真值表很简单，就下面 4 种情况：</p>\n<table>\n<thead>\n<tr>\n<th>+</th>\n<th>0</th>\n<th>1</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>0</td>\n<td>1</td>\n</tr>\n<tr>\n<td>1</td>\n<td>1</td>\n<td>0</td>\n</tr>\n</tbody>\n</table>\n<p><code class=\"language-text\">0 x 0 = 0， 0 x 1 = 1 x 0 = 1，1 x 1 = 0</code></p>\n<p>注意到它的真值表其实和 <strong>或门</strong>比较类似，只不过或门中，<code class=\"language-text\">1 x 1 = 1</code>，而它是 0。\n同时它的真值表和 <strong>与非门</strong> 也很相似，也就是：<code class=\"language-text\">0 x 0 = 1, 0 x 1 = 1 x 0 = 1, 1 x 1 = 0</code>。他们的区别在 0 x 0 的结果上。</p>\n<table>\n<thead>\n<tr>\n<th align=\"center\">输入 A</th>\n<th align=\"center\">输入 B</th>\n<th align=\"center\">或门</th>\n<th align=\"center\">与非门</th>\n<th align=\"center\">想要的结果</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td align=\"center\">0</td>\n<td align=\"center\">0</td>\n<td align=\"center\">0</td>\n<td align=\"center\">1</td>\n<td align=\"center\">0</td>\n</tr>\n<tr>\n<td align=\"center\">0</td>\n<td align=\"center\">1</td>\n<td align=\"center\">1</td>\n<td align=\"center\">1</td>\n<td align=\"center\">1</td>\n</tr>\n<tr>\n<td align=\"center\">1</td>\n<td align=\"center\">0</td>\n<td align=\"center\">1</td>\n<td align=\"center\">1</td>\n<td align=\"center\">1</td>\n</tr>\n<tr>\n<td align=\"center\">1</td>\n<td align=\"center\">1</td>\n<td align=\"center\">1</td>\n<td align=\"center\">0</td>\n<td align=\"center\">0</td>\n</tr>\n</tbody>\n</table>\n<p>所以我们只要再把它们两个的结果，再取一次与，就得到了“加法”的真值。\n看上去很复杂，经过了 四次转化（与门，或门，与非门 2 次），但因为这个逻辑实在太常见，所以也有一个专门的称呼叫做：<strong>异或门(XOR)</strong>。对就是我们非常熟悉的 <code class=\"language-text\">^</code></p>\n<p>不过这里可以忽略了进位的问题，我们接下来单独再分析进位的情况</p>\n<table>\n<thead>\n<tr>\n<th align=\"center\">进位</th>\n<th>0</th>\n<th>1</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td align=\"center\">0</td>\n<td>0</td>\n<td>0</td>\n</tr>\n<tr>\n<td align=\"center\">1</td>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>\n<p>显而易见，只有 a 和 b 同时为 1 的时候才会进位。这能否让你联想到它的真值表和 <strong>与门(AND)</strong> 的真值表一模一样呢？\n所以呢，加法的本质其实分为了两步：XOR 得到相加不进位的结果，AND 得到进位不相加的结果。</p>\n<h3 id=\"处理进位\" style=\"position:relative;\"><a href=\"#%E5%A4%84%E7%90%86%E8%BF%9B%E4%BD%8D\" aria-label=\"处理进位 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>处理进位</h3>\n<p>但这只是一个模糊的说法，因为最低位进位后，会将它交给下一个高位，这个时候参与加法的就不是之前的 2 个数，还是 3 个了。\n具体在电路的实现的时候，会有所谓的<strong>半加器和全加器</strong>来组合完成这个过程。但好在我们不是电工，不需要太在乎这到底表示了什么，我们可以用编程语言的思路来模拟这个过程。</p>\n<p>假设有一个函数 f，它的作用就是上面提到的，接受两个参数，返回当前位的结果， 和进位</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">function</span> <span class=\"token function\">f</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">a<span class=\"token punctuation\">,</span> b</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token punctuation\">[</span>a <span class=\"token operator\">^</span> b<span class=\"token punctuation\">,</span> a <span class=\"token operator\">&amp;</span> b<span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>接着，因为在进入高位的时候还要计算上一次得到的进位，所以如果以 11 + 01 为例的话，就是这样。</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> <span class=\"token punctuation\">[</span>r1<span class=\"token punctuation\">,</span> c1<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token function\">f</span><span class=\"token punctuation\">(</span><span class=\"token number\">1</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// r1 = 0, c1 = 1</span>\n\n<span class=\"token keyword\">const</span> <span class=\"token punctuation\">[</span>r2<span class=\"token punctuation\">,</span> c2<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token function\">f</span><span class=\"token punctuation\">(</span><span class=\"token number\">1</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// r2 = 1, c2 = 0</span>\n<span class=\"token keyword\">const</span> <span class=\"token punctuation\">[</span>r3<span class=\"token punctuation\">,</span> c3<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token function\">f</span><span class=\"token punctuation\">(</span>r2<span class=\"token punctuation\">,</span> c1<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// r3 = 0. c3 = 1</span>\n\n<span class=\"token keyword\">const</span> <span class=\"token punctuation\">[</span>r4<span class=\"token punctuation\">,</span> c4<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token function\">f</span><span class=\"token punctuation\">(</span><span class=\"token number\">0</span><span class=\"token punctuation\">,</span> c3<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// r4 = 1, c4 = 0</span></code></pre></div>\n<p>最后结果 <code class=\"language-text\">r4r3r1</code> => <code class=\"language-text\">100</code></p>\n<p>看上去似乎挺复杂的，一个简单的 2 位加法就要写 4 个表达式，如果改成 8 位的话，脑子都写炸了 🤯。好在上面的写法只是最底层的电路的思考方式，我们在使用计算机语言的时候，其实可以更加抽象的解决这个问题。</p>\n<p>现在来仔细看，相加不进位 s 和进位不相加 c 这两个数字，c 本质上还是一个被加数。继续用上面 11 + 01 的例子，<code class=\"language-text\">11 &amp; 01 = 01</code>，01 会给下一个高位使用，也就是说 <code class=\"language-text\">01</code> 其实表示了一个的 <code class=\"language-text\">010</code> 被加数。同时 <code class=\"language-text\">11 ^ 01 = 10</code> ，所以我们再分别计算 s 和 c <code class=\"language-text\">10 ^ 10 = 00, 10 &amp; 10 = 10</code>。接着就是 <code class=\"language-text\">00 ^ (10 &lt; 1) = 100, 000 &amp; 000 = 0</code>。\n因为这个时候进位为 0，所以不再需要计算算下去，最终结果就是 <code class=\"language-text\">100</code>，所以我们可以通过几行代码来实现加法。</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">function</span> <span class=\"token function\">sum</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">a<span class=\"token punctuation\">,</span> b</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>b<span class=\"token punctuation\">)</span> <span class=\"token keyword\">return</span> a<span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">return</span> <span class=\"token function\">sum</span><span class=\"token punctuation\">(</span>a <span class=\"token operator\">^</span> b<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span>a <span class=\"token operator\">&amp;</span> b<span class=\"token punctuation\">)</span> <span class=\"token operator\">&lt;&lt;</span> <span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<h2 id=\"减法\" style=\"position:relative;\"><a href=\"#%E5%87%8F%E6%B3%95\" aria-label=\"减法 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>减法</h2>\n<h3 id=\"补码\" style=\"position:relative;\"><a href=\"#%E8%A1%A5%E7%A0%81\" aria-label=\"补码 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>补码</h3>\n<p>减法的难点在于借位。加法的时候，我们可以按顺序的从最低位往最高位计算，把进位留给下一位就行了。但是如果引入进位的话，就需要提前去读取下一位，这还算好办。如果出现连续结尾，比如十进制加法的中 10000 - 9 的话，这就让借位在低层运算中基本上不可能实现，因为这个时候还没有引入<strong>内存</strong>的概念。就因为这个，补码出现了。</p>\n<p>首先，需要强调的是，因为翻译的问题，补码的英文其实是 <em>two’s complement</em>，字面意思是 2 的补数。同样的，其实还存在 10 的补数。接下来我们将通过 10 的补数来了解减法和加法的关系。</p>\n<p>考虑下面这个例子：<code class=\"language-text\">253 - 176 = 77</code>，我们先求用 999 减去减数，<code class=\"language-text\">999 - 176 = 823</code>。接下来再把被减数和结果相加得到 <code class=\"language-text\">253 + 823 = 1076</code>。接着再把它加 1 并减去 1000 就得到最后的差值。同理，如果减数是 n 位数的话，它的补数就是 n 个 9 减去它的值。在这里 <code class=\"language-text\">253 - 176 = 253 + 999 - 176 + 1 - 1000 = 77</code>。而 176 的补数就是好 823 + 1 = 824\n换句话说我们可以通过引入补数的概念，把减法转成<strong>一定不会出现借位的加减法运算</strong>，在一定程度上已经解决了上面提到借位难以实现的问题。</p>\n<p>不过呢，这里忽略了另一种情况，如果我们改成 <code class=\"language-text\">176 - 253 = -77</code> 的话。上面的步骤如果还是按之前一样：</p>\n<ol>\n<li>999 - 253 = 746</li>\n<li>746 + 176 = 922</li>\n<li>922 - 999 = ???</li>\n</ol>\n<p>我们可以很轻松地将它们的顺序互调，改成 999 - 922 = 77，接着再加上负号变成 -77。而加法器本身，在判断是否需要作出这些转换的时候，依赖两个标记位：SUB 和 CO。分别代表减法和进位的标记位。正常情况下，补数加上被减数应该是大于当前最高位的，就像 253 + 823 > 999 一样，所以会发生<strong>进位</strong>，如果没有发生，并且 SUB 标记被设置的话，就说明结果将会是负数。</p>\n<p>接下来，我们再思考下二进制和十进制的区别，就能发现一个有意思的事情：二进制里最高位都是 1，它减去任何一个数的效果，就是<strong>按位取反</strong>，接着再 + 1 ，又因为在语言的实现中，天生具备了自动截断的功能，所以最后的那个减法是不需要去另外编码的，也就得到了最终的补码。这也是为什么总是会提到取反加一的原因。</p>\n<p>通过上面的例子，也能理解为什么在语言中普遍使用最高位为 1 来表示负数了。以一个 4 位的例子来看：<code class=\"language-text\">2 - 7 = 0010 - 0111 = 0010 + ~0111 + 1 = 0010 + 1001 = 1011 = -5</code>。真正意义上地将减法变成了加法</p>\n<p>换句话说：<code class=\"language-text\">a - b = a + ~b + 1</code>。所以减法的实现非常简单</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">function</span> <span class=\"token function\">difference</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">a<span class=\"token punctuation\">,</span> b</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token function\">sum</span><span class=\"token punctuation\">(</span>a<span class=\"token punctuation\">,</span> <span class=\"token operator\">~</span>b <span class=\"token operator\">+</span> <span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<h3 id=\"排列\" style=\"position:relative;\"><a href=\"#%E6%8E%92%E5%88%97\" aria-label=\"排列 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>排列</h3>\n<p>我们将 4 位的二进制排开的话，会得到：</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token number\">1000</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1001</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1010</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1011</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1100</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1101</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1110</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1111</span><span class=\"token punctuation\">,</span>\n<span class=\"token number\">0000</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0001</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0010</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0011</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0100</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0101</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0110</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0111</span><span class=\"token punctuation\">,</span></code></pre></div>\n<p>分别对应</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token operator\">-</span><span class=\"token number\">8</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">-</span><span class=\"token number\">7</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">-</span><span class=\"token number\">6</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">-</span><span class=\"token number\">5</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">-</span><span class=\"token number\">4</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">-</span><span class=\"token number\">3</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">-</span><span class=\"token number\">2</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">-</span><span class=\"token number\">1</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1</span><span class=\"token punctuation\">,</span> <span class=\"token number\">2</span><span class=\"token punctuation\">,</span> <span class=\"token number\">3</span><span class=\"token punctuation\">,</span> <span class=\"token number\">4</span><span class=\"token punctuation\">,</span> <span class=\"token number\">5</span><span class=\"token punctuation\">,</span> <span class=\"token number\">6</span><span class=\"token punctuation\">,</span> <span class=\"token number\">7</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>如果我们暂且不考虑符号的话，就能发现它具有循环有序的性质。最大的数 7 的下一个是 8，但是要加上符号。通过这个性质，我们应该能够非常快地打出，-1 的补码就是全 1，这是毋庸置疑的。</p>\n<h3 id=\"题外话\" style=\"position:relative;\"><a href=\"#%E9%A2%98%E5%A4%96%E8%AF%9D\" aria-label=\"题外话 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>题外话</h3>\n<p>虽然说，我们可以通过上面的方式模拟加减法，但是这不代表你在实际编程中应该使用它们。现代 CPU 再已经内置了加法器甚至减法器，所以根本不需要特意用位运算去模拟这个过程，本文主要还是为了揭示加法减法之间的关系。另外如果是浮点数的话，会变得更加特殊。</p>\n<h2 id=\"总结\" style=\"position:relative;\"><a href=\"#%E6%80%BB%E7%BB%93\" aria-label=\"总结 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>总结</h2>\n<ol>\n<li>加法分为两步，异或表示只加不进位，与表示只进位不加</li>\n<li>因为借位的困难，减法可以通过补码的技巧来实现加法</li>\n<li>最高位是 1 表示负数，可以极大的简化减法的实现。</li>\n</ol>","frontmatter":{"title":"如何实现加法和减法","spoiler":"你知道 -1 的补码是多少吗？","image":null,"date":"2019/09/01"}}},"pageContext":{"slug":"/how-to-add/","previous":{"fields":{"slug":"/shuffle/"},"frontmatter":{"title":"Shuffle 算法","image":null,"spoiler":"随机算法的基本实现"}},"next":{"fields":{"slug":"/buy-an-iphone11/"},"frontmatter":{"title":"对于新 iPhone 的看法","image":null,"spoiler":"大哥换个肾？"}}}}}