<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>LanFa的个人主页</title><description>LanFa的个人主页</description><link>https://lan-fa.github.io</link><item><title>使用 AI Agent 工具帮助你从零构建一个小程序</title><link>https://lan-fa.github.io/blog/build-mini-program-with-ai-agent</link><guid isPermaLink="true">https://lan-fa.github.io/blog/build-mini-program-with-ai-agent</guid><description>记录使用 opencode 等 AI Agent 工具从零构建 HandyIn 二维码作业收取系统的完整过程与经验。</description><pubDate>Sat, 22 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;使用 AI Agent 工具帮助你从零构建一个小程序&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Lan-Fa/HandyIn&quot;&gt;Lan-Fa/HandyIn: HandyIn 是一个开源、跨平台的二维码作业收取与统计系统，通过多人协作扫码自动完成纸质作业登记、实时统计以及未交名单管理。&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;明确自己的需求：我需要做一个什么东西？&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;考虑到不同人有不同手机，我又没有 ios 开发环境，所以我想把它做成网页。
&lt;ul&gt;
&lt;li&gt;它应该是前后端分离的。&lt;/li&gt;
&lt;li&gt;它也应该是轻量级的，不妨使用前端 React 框架 + 后端 Node.js 的形式。&lt;/li&gt;
&lt;li&gt;它应该有自己的数据库，我认为 PostgreSQL 比较好用。&lt;/li&gt;
&lt;li&gt;它应该可以被别人轻易的部署，docker 用在这里看起来很不错。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;我希望有管理者和被管理者两种账户权限。&lt;/li&gt;
&lt;li&gt;既然有账号，那么也就需要有密码，我们还需要一定的安全性，以防被别人轻易攻破。&lt;/li&gt;
&lt;li&gt;老师应该可以管理学生，每个学生应该属于一个班级。&lt;/li&gt;
&lt;li&gt;每个学生还应该有一个学号，作为学生的主键，不妨就采用二南开常用的：年份+部门+班级+编号的形式。&lt;/li&gt;
&lt;li&gt;对于这个项目的管理，应该使用 git ，在 ai 写出不尽人意的修改的时候，方便进行回滚等版本控制的操作，同时在创作的时候，随时进行进度的提交，为后面创建一个“还原点”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;经过一番思考，我们得到了初步的构想。&lt;/p&gt;
&lt;h2&gt;让你的 agent 工具为你制定计划&lt;/h2&gt;
&lt;p&gt;这里以 opencode 为例。&lt;/p&gt;
&lt;p&gt;首先，你需要配置好你自己的 apikey ，这样才能开始使用 ai agent 工具。&lt;/p&gt;
&lt;p&gt;在这次项目中， 我将全程使用 DeepSeek V4 Pro 。&lt;/p&gt;
&lt;p&gt;在 opencode 中，存在 Plan 和 Build 两种模式，我们可以使用 Plan 模式进行具体的实现步骤构想，在成熟后切换到 Build 模式，让工具进行代码实现的任务。&lt;/p&gt;
&lt;p&gt;在让 Plan 计划的时候，可以让它在写代码的时候顺便编写测试代码，测试每个模块的功能，会节省很多调 bug 的时间。&lt;/p&gt;
&lt;p&gt;第一次给 Agent 的 Prompt 大致如下：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我要开发一个学校内部使用的作业登记系统……
技术栈使用 React + Node.js + PostgreSQL……
暂时不要写代码，请先分析需求并制定实现计划。
同时规划测试方案。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最终，我们得到了一个策划方案：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://lan-fa.github.io/_astro/image-20260822121231010.BPQlmzHA_1oXGkx.webp&quot; alt=&quot;image-20260822121231010&quot;&gt;&lt;/p&gt;
&lt;h2&gt;不要让 Agent 一次写完整个项目&lt;/h2&gt;
&lt;p&gt;即使已经有了完整 Plan，也推荐按照模块逐步实现：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;初始化项目&lt;/li&gt;
&lt;li&gt;建数据库&lt;/li&gt;
&lt;li&gt;完成登录&lt;/li&gt;
&lt;li&gt;完成学生管理&lt;/li&gt;
&lt;li&gt;完成作业管理&lt;/li&gt;
&lt;li&gt;最后实现二维码功能&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;每完成一个阶段，都应该：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;运行项目&lt;/li&gt;
&lt;li&gt;执行测试&lt;/li&gt;
&lt;li&gt;查看 git diff&lt;/li&gt;
&lt;li&gt;手动验证主要功能&lt;/li&gt;
&lt;li&gt;提交一次 Git&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;切换到 Build 模式，让它把策划方案记录到新建的 docs/ 目录下，就可以开始让它编写代码了。经过一番实现，我们获得了一个很粗糙的雏形。&lt;/p&gt;
&lt;h2&gt;进行 Bug 的调整&lt;/h2&gt;
&lt;p&gt;它成功跑起来了，但是看起来还有许多 bug。&lt;/p&gt;
&lt;p&gt;上报 bug 的时候，我们可以结合日志、你观察到的现象等信息，ai 知道这些信息后，可以更有针对性的完成任务。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://lan-fa.github.io/_astro/image-20260822121347462.CdYmMnga_1J7iSy.webp&quot; alt=&quot;image-20260822121347462&quot;&gt;&lt;/p&gt;
&lt;p&gt;和刚刚相同，采用 Plan 计划 + Build 工作的方式，把我们看到的 bug 进行修复。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://lan-fa.github.io/_astro/image-20260822121420569.CxiY8bUo_ZYYjnM.webp&quot; alt=&quot;image-20260822121420569&quot;&gt;&lt;/p&gt;
&lt;p&gt;调整好目前看到的 bug 后，我们得到了这样的程序：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://lan-fa.github.io/_astro/e44052076cae6ed7539bc886ac7ce00b.k4MQal0F_Z2m4LmT.webp&quot; alt=&quot;e44052076cae6ed7539bc886ac7ce00b&quot;&gt;&lt;/p&gt;
&lt;p&gt;它看起来很丑？所以需要我们尝试美化。&lt;/p&gt;
&lt;h2&gt;阶段的分隔&lt;/h2&gt;
&lt;p&gt;不过到这里，第一阶段的任务已经完成，因此，我们需要新开一个对话框，这在 opencode 中可以通过 &lt;code&gt;/new&lt;/code&gt; 指令完成。&lt;/p&gt;
&lt;p&gt;分阶段是很有必要的一个事情。这可以防止上下文过长导致模型的表现变差，同时也可以节省一些 token，降低你的开销。&lt;/p&gt;
&lt;p&gt;但是新的对话框可能不拥有之前的记忆，怎么办？很简单，只需要让它保存下来之前打记忆即可。可以使用 &lt;code&gt;/compact&lt;/code&gt; 进行压缩，然后让它存储到本地文件中。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://lan-fa.github.io/_astro/image-20260822121714878.B9Unf9vq_20S0VR.webp&quot; alt=&quot;image-20260822121714878&quot;&gt;&lt;/p&gt;
&lt;h2&gt;让它变得漂亮一些？&lt;/h2&gt;
&lt;p&gt;我们发现 React 框架有一些很不错的 UI 组件库，可以直接拿来使用。因此在 Plan 模式中进行规划。&lt;/p&gt;
&lt;p&gt;与其让 Agent 从零手写按钮、Dialog、Table、Form，不如选择成熟组件库。这样不仅视觉效果通常更好，而且 Agent 对主流组件库也更熟悉，生成代码的稳定性更高。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://lan-fa.github.io/_astro/image-20260822121734543.B61O_DqB_mqTVt.webp&quot; alt=&quot;image-20260822121734543&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://lan-fa.github.io/_astro/image-20260822121803393.Ca2rEPU6_2oxdoF.webp&quot; alt=&quot;image-20260822121803393&quot;&gt;&lt;/p&gt;
&lt;p&gt;在实现的时候，同样不要忘记进行时刻测试。&lt;/p&gt;
&lt;p&gt;在一些修改后，我们就有了现在的样子：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://lan-fa.github.io/_astro/image-20260822122220821.Cb_ke56x_29um8V.webp&quot; alt=&quot;image-20260822122220821&quot;&gt;&lt;/p&gt;
&lt;h2&gt;新功能的加入&lt;/h2&gt;
&lt;p&gt;同样开启新的窗口，把你认为还需要添加的功能加入即可。&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>《逻辑与计算机系统设计》复习</title><link>https://lan-fa.github.io/blog/logic-and-computer-system-design-review</link><guid isPermaLink="true">https://lan-fa.github.io/blog/logic-and-computer-system-design-review</guid><description>逻辑与计算机系统设计课程常用知识点与期末复习笔记。</description><pubDate>Fri, 03 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;《逻辑与计算机系统设计》常用知识点&lt;/h1&gt;
&lt;h2&gt;逻辑函数&lt;/h2&gt;
&lt;h2&gt;逻辑函数的表达形式&lt;/h2&gt;
&lt;h2&gt;逻辑函数化简&lt;/h2&gt;
&lt;h2&gt;数据表示&lt;/h2&gt;
&lt;h3&gt;BCD编码&lt;/h3&gt;
&lt;h4&gt;8421码&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;有权码&lt;/strong&gt;，从高到低依次表示8、4、2、1
$$
(258)&lt;em&gt;{10}=(0010\space0101\space1000)&lt;/em&gt;{8421}
$$
&lt;strong&gt;不允许出现&lt;/strong&gt;：1010~1111&lt;/p&gt;
&lt;h4&gt;2421码&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;有权码&lt;/strong&gt;，从高到低依次表示2、4、2、1&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;无单值性&lt;/strong&gt;：0101、1011都表示5&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不允许出现&lt;/strong&gt;：0101~1010&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;自补性&lt;/strong&gt;：对9自补，按位取反后可得对9的补数的2421码&lt;/p&gt;
&lt;h4&gt;余3码&lt;/h4&gt;
&lt;p&gt;8421码基础上，每位十进制数字加上&lt;code&gt;0011&lt;/code&gt;得到。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不允许出现&lt;/strong&gt;：0000~0010、1101~1111&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;自补性&lt;/strong&gt;：按位取反后得到9的补数&lt;/p&gt;
&lt;h3&gt;可靠性编码&lt;/h3&gt;
&lt;h4&gt;格雷码&lt;/h4&gt;
&lt;p&gt;二进制码为：$B=B_{n-1}B_{n-2}...B_{i+1}B_{i}...B_{1}B_{0}$&lt;/p&gt;
&lt;p&gt;对应格雷码为：$G=G_{n-1}G_{n-2}...G_{i+1}G_{i}...G_{1}G_{0}$&lt;/p&gt;
&lt;p&gt;$G_{n-1}=B_{n-1}$&lt;/p&gt;
&lt;p&gt;$G_{i}=B_{i+1} \oplus B_i$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;两个相邻数仅有一位不同&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;汉明距离&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;最小汉明距离&lt;/strong&gt;：任意两个二进制合法编码之间&lt;strong&gt;不同位数的最小值&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;最小汉明距离越大，纠错能力越强，存储开销和计算开销越大&lt;/p&gt;
&lt;p&gt;$dis \ge d+1$ ：检测到最多d个错误&lt;/p&gt;
&lt;p&gt;$dis \ge 2*c+1$：定位最多c个错误&lt;/p&gt;
&lt;h3&gt;奇偶校验&lt;/h3&gt;
&lt;p&gt;偶校验：若前面出现1次数为偶数则&lt;strong&gt;校验位为0&lt;/strong&gt;，校验通过&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;无错结论不可信&lt;/strong&gt;，不能检测出同时发生偶数个错误&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不能纠错&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;行列奇偶校验&lt;/h3&gt;
&lt;p&gt;新增一行，对所有行的每一列进行奇偶校验，可&lt;strong&gt;定位1位错误，并纠正&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;海明校验&lt;/h3&gt;
&lt;p&gt;需要校验位位数：$2^{p}\ge d+p+1$ ，因为&lt;strong&gt;校验位也可能出错&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第i个校验位位置在汉明码的第$2^{i-1}$位&lt;/p&gt;
&lt;h4&gt;校验位计算方法&lt;/h4&gt;
&lt;p&gt;海明码通常采用偶校验。校验位放在编号为 2 的幂次的位置上，即第 1、2、4、8、16……位，其余位置放有效数据位。&lt;/p&gt;
&lt;p&gt;以 7 位数据 &lt;code&gt;b1 b2 b3 b4 b5 b6 b7&lt;/code&gt; 为例，需要 4 位校验位，位置安排为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;位置：  1   2   3   4   5   6   7   8   9   10  11
内容： P1  P2  b1  P3  b2  b3  b4  P4  b5  b6  b7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每个校验位负责一组位置。判断规则是：如果某个位置编号的二进制表示中包含某个校验位对应的权值，那么该位置就由这个校验位负责。&lt;/p&gt;
&lt;p&gt;采用偶校验时，各校验位计算公式为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;P1 = b1 ⊕ b2 ⊕ b4 ⊕ b5 ⊕ b7
P2 = b1 ⊕ b3 ⊕ b4 ⊕ b6 ⊕ b7
P3 = b2 ⊕ b3 ⊕ b4
P4 = b5 ⊕ b6 ⊕ b7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;⊕&lt;/code&gt; 表示异或运算。异或结果为 &lt;code&gt;0&lt;/code&gt; 表示参与运算的比特中 &lt;code&gt;1&lt;/code&gt; 的个数为偶数，异或结果为 &lt;code&gt;1&lt;/code&gt; 表示参与运算的比特中 &lt;code&gt;1&lt;/code&gt; 的个数为奇数。&lt;/p&gt;
&lt;p&gt;因此，计算校验位的本质就是：让每个校验位所负责的那一组比特满足偶校验，即该组中 &lt;code&gt;1&lt;/code&gt; 的总个数为偶数。&lt;/p&gt;
&lt;h4&gt;校验方法&lt;/h4&gt;
&lt;p&gt;接收端收到海明码后，重新对各组进行偶校验，得到错误定位字。&lt;/p&gt;
&lt;p&gt;仍以 7 位数据、4 位校验位为例，设收到的数据位为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;P1 P2 b1 P3 b2 b3 b4 P4 b5 b6 b7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;则重新计算：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;G1 = P1 ⊕ b1 ⊕ b2 ⊕ b4 ⊕ b5 ⊕ b7
G2 = P2 ⊕ b1 ⊕ b3 ⊕ b4 ⊕ b6 ⊕ b7
G3 = P3 ⊕ b2 ⊕ b3 ⊕ b4
G4 = P4 ⊕ b5 ⊕ b6 ⊕ b7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将结果组合成错误定位字：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;G4G3G2G1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果错误定位字为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;0000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;表示没有检测到错误。&lt;/p&gt;
&lt;p&gt;如果错误定位字不为 &lt;code&gt;0000&lt;/code&gt;，则它的二进制值就是出错位的位置编号。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;G4G3G2G1 = 0110
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;0110&lt;/code&gt; 的十进制值为 6，表示第 6 位出错。此时将第 6 位取反，即 &lt;code&gt;0&lt;/code&gt; 变 &lt;code&gt;1&lt;/code&gt;，或 &lt;code&gt;1&lt;/code&gt; 变 &lt;code&gt;0&lt;/code&gt;，即可完成纠错。&lt;/p&gt;
&lt;h3&gt;CRC 校验&lt;/h3&gt;
&lt;p&gt;有效信息k位，校验信息r位，$N=k+r\le 2^r-1$&lt;/p&gt;
&lt;p&gt;适合检测&lt;strong&gt;连续多位出错&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;生成多项式&lt;/h4&gt;
&lt;p&gt;CRC 校验需要选定一个生成多项式 &lt;code&gt;G(x)&lt;/code&gt;，它是计算校验信息时使用的“除数”。如果校验信息为 &lt;code&gt;r&lt;/code&gt; 位，则生成多项式对应的二进制串长度为 &lt;code&gt;r + 1&lt;/code&gt; 位。&lt;/p&gt;
&lt;p&gt;生成多项式通常要求最高位和最低位都为 &lt;code&gt;1&lt;/code&gt;。例如，当 &lt;code&gt;r = 3&lt;/code&gt; 时，可以选用 4 位生成多项式：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;G(x) = 1011
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;校验位计算方法&lt;/h4&gt;
&lt;p&gt;设有效信息为 &lt;code&gt;K&lt;/code&gt;，长度为 &lt;code&gt;k&lt;/code&gt; 位；校验信息为 &lt;code&gt;R&lt;/code&gt;，长度为 &lt;code&gt;r&lt;/code&gt; 位。&lt;/p&gt;
&lt;p&gt;计算 CRC 校验位的步骤如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 在有效信息 K 后面补 r 个 0，相当于将 K 逻辑左移 r 位。
2. 用补 0 后的数据除以生成多项式 G(x)，进行模 2 除法。
3. 得到 r 位余数 R。
4. 将余数 R 拼接到原始有效信息 K 后面，得到 CRC 编码结果。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;即：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;发送码字 = K + R
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中，模 2 除法中的减法不需要借位，本质上就是异或运算。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1 ⊕ 1 = 0
0 ⊕ 0 = 0
1 ⊕ 0 = 1
0 ⊕ 1 = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此，CRC 校验位就是“补 0 后的数据”除以生成多项式后得到的余数。&lt;/p&gt;
&lt;h4&gt;校验方法&lt;/h4&gt;
&lt;p&gt;接收端收到完整码字后，使用同一个生成多项式 &lt;code&gt;G(x)&lt;/code&gt; 再做一次模 2 除法。&lt;/p&gt;
&lt;p&gt;如果余数为全 0，说明没有检测到错误：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;余数 = 000...0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果余数不为 0，说明数据在传输或存储过程中发生了错误：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;余数 ≠ 000...0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此，CRC 校验的判断规则为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;收到的码字 ÷ G(x) 的余数为 0：未检测到错误
收到的码字 ÷ G(x) 的余数不为 0：检测到错误
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CRC 校验一般用于检错，尤其适合检测突发错误；它通常只能判断“是否出错”，不能像海明校验那样直接定位并纠正某一位错误。讲义中 CRC 的流程也是先根据 &lt;code&gt;k+r ≤ 2^r-1&lt;/code&gt; 确定 &lt;code&gt;r&lt;/code&gt;，再选 &lt;code&gt;r+1&lt;/code&gt; 位生成多项式，并将待校验信息左移 &lt;code&gt;r&lt;/code&gt; 位后进行模 2 除法。&lt;/p&gt;
&lt;h4&gt;模 2 除法&lt;/h4&gt;
&lt;p&gt;模 2 除法是 CRC 校验中计算余数的方法，也可以理解为“二进制多项式除法”。它的除法形式与普通竖式除法相似，但运算规则不同：模 2 除法中的减法不需要借位，本质上就是异或运算。&lt;/p&gt;
&lt;p&gt;其基本运算规则为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;0 ⊕ 0 = 0
1 ⊕ 1 = 0
1 ⊕ 0 = 1
0 ⊕ 1 = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说，在模 2 除法中：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;相同为 0，不同为 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进行 CRC 计算时，通常不关心商是多少，只关心最后得到的余数。设生成多项式为 &lt;code&gt;G(x)&lt;/code&gt;，其对应二进制串长度为 &lt;code&gt;r + 1&lt;/code&gt; 位，则模 2 除法最终得到的余数长度为 &lt;code&gt;r&lt;/code&gt; 位，这个余数就是 CRC 校验信息。&lt;/p&gt;
&lt;p&gt;模 2 除法的具体过程如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 从被除数左侧开始，取出与除数等长的一段。
2. 如果当前段最高位为 1，就与除数进行异或。
3. 如果当前段最高位为 0，则不能除，直接继续向后取下一位。
4. 重复上述过程，直到所有位都处理完。
5. 最后剩下的 r 位就是余数。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如，设有效信息为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;K = 1011
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成多项式为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;G(x) = 1101
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成多项式长度为 4 位，所以 &lt;code&gt;r = 3&lt;/code&gt;。先在有效信息后补 3 个 0：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1011 → 1011000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后用 &lt;code&gt;1011000&lt;/code&gt; 对 &lt;code&gt;1101&lt;/code&gt; 做模 2 除法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1011 ⊕ 1101 = 0110
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;去掉前导 0，并带下下一位，得到：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;继续异或：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1100 ⊕ 1101 = 0001
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;继续带下后面的位，最后得到余数：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此，CRC 校验位为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R = 100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终发送的 CRC 编码结果为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1011 100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;即：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1011100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接收端校验时，将收到的完整码字再次除以同一个生成多项式 &lt;code&gt;G(x)&lt;/code&gt;。如果余数为全 0，说明没有检测到错误；如果余数不为 0，说明数据出错。&lt;/p&gt;
&lt;p&gt;因此，模 2 除法的关键是：用异或代替普通减法，不考虑借位和进位，最后得到的余数就是 CRC 校验信息。&lt;/p&gt;
&lt;h4&gt;纠错方法&lt;/h4&gt;
&lt;p&gt;CRC 通常作为检错码使用，但在特定条件下也可以实现单比特纠错。其前提是：假设传输过程中有且只有 1 位发生错误，并且已经知道生成多项式 &lt;code&gt;G(x)&lt;/code&gt; 对应的“余数—出错位”关系。&lt;/p&gt;
&lt;p&gt;接收端收到完整码字后，使用同一个生成多项式 &lt;code&gt;G(x)&lt;/code&gt; 进行模 2 除法，得到校验余数。&lt;/p&gt;
&lt;p&gt;如果余数为全 0，表示没有检测到错误：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;收到码字 ÷ G(x) 的余数 = 000...0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果余数不为 0，表示检测到错误。在有且只有 1 位出错的前提下，可以根据余数查表，确定出错的位置。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;余数 ≠ 000...0  →  检测到错误
余数对应某一位  →  定位该位出错
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;定位出错位后，将该位取反即可完成纠错：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;0 变 1
1 变 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如，如果根据余数查表得到出错位为第 6 位，则将第 6 位取反，就可以恢复原码字。&lt;/p&gt;
&lt;p&gt;需要注意的是，CRC 的纠错能力依赖于前提条件。若有 2 位同时发生错误，CRC 可以判断出数据出错，但通常不能正确定位错误位置，因此不能可靠纠错。若有 3 位或更多位同时出错，CRC 甚至不能保证一定检测出来，可能出现余数为 0、误认为无错的情况。&lt;/p&gt;
&lt;p&gt;因此可以总结为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1 位出错：在已知余数—出错位对应关系时，可以定位并纠错。
2 位出错：可以检测到错误，但不能可靠定位，通常不能纠错。
3 位及以上出错：不能保证全部检测出来，也不能保证纠错。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以，CRC 的主要功能仍然是检错；讲义中提到的纠错，是在“有且只有 1 位出错”的特殊假设下，通过余数定位错误位并翻转该位来实现的。&lt;/p&gt;
&lt;h3&gt;IEEE754&lt;/h3&gt;
&lt;h4&gt;IEEE 754 的基本作用&lt;/h4&gt;
&lt;p&gt;IEEE 754 是计算机中表示和处理浮点数的常用标准。它主要解决的问题是：如何用固定长度的二进制位串表示非常大或非常小的实数。&lt;/p&gt;
&lt;p&gt;定点数的小数点位置固定，表示范围有限；而浮点数的小数点位置可以“浮动”，因此可以表示更大的数值范围。例如科学记数法可以把十进制数写成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;± 尾数 × 10^指数
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;二进制浮点数也采用类似思想，把一个数表示成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;± 尾数 × 2^指数
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此，IEEE 754 浮点数本质上由三部分组成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;符号位 S
阶码 E
尾数 M
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中，符号位决定正负，阶码决定小数点移动的位置，尾数决定有效数字。&lt;/p&gt;
&lt;h4&gt;IEEE 754 单精度浮点数格式&lt;/h4&gt;
&lt;p&gt;IEEE 754 单精度浮点数共有 32 位，格式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;S | EEEEEEEE | MMMMMMMMMMMMMMMMMMMMMMM
1位符号位 | 8位阶码 | 23位尾数
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以写成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;第31位：符号位 S
第30~23位：阶码 E
第22~0位：尾数 M
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;S = 0 表示正数
S = 1 表示负数
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;阶码 &lt;code&gt;E&lt;/code&gt; 不是直接存储真实指数，而是采用移码表示。对于单精度浮点数：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;E = e + 127
e = E - 127
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;e&lt;/code&gt; 是真实指数，&lt;code&gt;127&lt;/code&gt; 是偏移量，也叫 bias。&lt;/p&gt;
&lt;p&gt;规格化数的真值公式为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;N = (-1)^S × 1.M × 2^(E - 127)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的 &lt;code&gt;1.M&lt;/code&gt; 表示尾数前面默认有一个隐藏的 &lt;code&gt;1&lt;/code&gt;。这个最高有效位称为隐藏位，不需要实际存储，因此可以多保存 1 位有效精度。&lt;/p&gt;
&lt;h4&gt;IEEE 754 双精度浮点数格式&lt;/h4&gt;
&lt;p&gt;IEEE 754 双精度浮点数共有 64 位，格式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;S | EEEEEEEEEEE | MMMMM...MMMMM
1位符号位 | 11位阶码 | 52位尾数
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;第63位：符号位 S
第62~52位：阶码 E
第51~0位：尾数 M
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;双精度浮点数的阶码偏移量为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;bias = 1023
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此规格化数的真值公式为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;N = (-1)^S × 1.M × 2^(E - 1023)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;与单精度相比，双精度的阶码位数更多，能表示更大的指数范围；尾数位数更多，能保存更高的有效精度。&lt;/p&gt;
&lt;h4&gt;规格化数&lt;/h4&gt;
&lt;p&gt;当阶码字段满足：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1 ≤ E ≤ 254
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;时，单精度浮点数表示规格化数。&lt;/p&gt;
&lt;p&gt;规格化数的形式为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;N = (-1)^S × 1.M × 2^(E - 127)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;1.M&lt;/code&gt; 的整数部分 &lt;code&gt;1&lt;/code&gt; 不存储，而是默认存在。&lt;/p&gt;
&lt;p&gt;例如，一个正数可以规格化为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;101.101₂ = 1.01101₂ × 2^2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;S = 0
e = 2
E = 2 + 127 = 129 = 10000001₂
M = 01101000000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;规格化的好处是可以让浮点数表示唯一化，同时充分利用尾数字段保存有效数字。&lt;/p&gt;
&lt;h4&gt;非规格化数&lt;/h4&gt;
&lt;p&gt;当阶码字段为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;E = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;且尾数字段为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;M ≠ 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;时，IEEE 754 表示非规格化数。&lt;/p&gt;
&lt;p&gt;非规格化数的形式不是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1.M × 2^e
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;0.M × 2^(-126)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说，非规格化数没有隐藏的最高位 &lt;code&gt;1&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;非规格化数的主要作用是表示非常接近 0 的数，使浮点数从最小规格化数逐渐过渡到 0，而不是突然断掉。这种设计可以减小下溢附近的误差。&lt;/p&gt;
&lt;h4&gt;零的表示&lt;/h4&gt;
&lt;p&gt;当阶码字段为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;E = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;且尾数字段为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;M = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;时，IEEE 754 表示 0。&lt;/p&gt;
&lt;p&gt;由于符号位仍然存在，所以 IEEE 754 中有两种 0：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;S = 0, E = 0, M = 0 表示 +0
S = 1, E = 0, M = 0 表示 -0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;+0 和 -0 数值相等，但编码不同
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在一般数值比较中，&lt;code&gt;+0&lt;/code&gt; 和 &lt;code&gt;-0&lt;/code&gt; 通常被认为相等。&lt;/p&gt;
&lt;h4&gt;无穷大的表示&lt;/h4&gt;
&lt;p&gt;当阶码字段为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;E = 255
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;且尾数字段为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;M = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;时，IEEE 754 表示无穷大。&lt;/p&gt;
&lt;p&gt;符号位决定是正无穷还是负无穷：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;S = 0 表示 +∞
S = 1 表示 -∞
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;正数 / +0 = +∞
负数 / +0 = -∞
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;无穷大通常用于表示上溢结果，或者某些除零运算的结果。&lt;/p&gt;
&lt;h4&gt;NaN 的表示&lt;/h4&gt;
&lt;p&gt;当阶码字段为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;E = 255
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;且尾数字段为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;M ≠ 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;时，IEEE 754 表示 NaN。&lt;/p&gt;
&lt;p&gt;NaN 是 Not a Number 的缩写，表示“不是一个有效数值”。&lt;/p&gt;
&lt;p&gt;常见产生 NaN 的运算包括：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;0 / 0
∞ / ∞
0 × ∞
∞ - ∞
sqrt(负数)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;NaN 的意义是：该运算没有确定的实数结果，因此不能用普通浮点数表示。&lt;/p&gt;
&lt;h4&gt;为什么阶码要用移码表示&lt;/h4&gt;
&lt;p&gt;IEEE 754 的阶码采用移码表示，而不是直接用补码表示指数。&lt;/p&gt;
&lt;p&gt;对于单精度浮点数：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;E = e + 127
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于双精度浮点数：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;E = e + 1023
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样做有两个重要作用。&lt;/p&gt;
&lt;p&gt;第一，阶码字段可以按无符号数存储，便于硬件比较大小。&lt;/p&gt;
&lt;p&gt;第二，可以把阶码全 0 和全 1 预留出来表示特殊值：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;E = 全0：表示 0 或非规格化数
E = 全1：表示 ∞ 或 NaN
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此，规格化数不能使用全 0 或全 1 的阶码。&lt;/p&gt;
&lt;h4&gt;IEEE 754 单精度数值分类总结&lt;/h4&gt;
&lt;p&gt;单精度浮点数可以根据 &lt;code&gt;E&lt;/code&gt; 和 &lt;code&gt;M&lt;/code&gt; 分成以下几类：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;E = 0, M = 0：
表示 +0 或 -0

E = 0, M ≠ 0：
表示非规格化数

1 ≤ E ≤ 254：
表示规格化数

E = 255, M = 0：
表示 +∞ 或 -∞

E = 255, M ≠ 0：
表示 NaN
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以分析 IEEE 754 位串时，不应直接套规格化数公式，而应先判断阶码 &lt;code&gt;E&lt;/code&gt; 是否为全 0 或全 1。&lt;/p&gt;
&lt;h4&gt;IEEE 754 编码的一般步骤&lt;/h4&gt;
&lt;p&gt;若要把一个十进制真值转换为 IEEE 754 单精度浮点数，可以按以下步骤进行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 判断正负，确定符号位 S。
2. 将绝对值转换为二进制。
3. 将二进制数规格化为 1.M × 2^e。
4. 计算阶码 E = e + 127。
5. 取规格化后小数点后面的部分作为尾数 M。
6. 尾数不足 23 位补 0，超过 23 位则按规则舍入。
7. 拼接 S | E | M，得到 32 位浮点数编码。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;103.5₁₀ = 1100111.1₂
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;规格化为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1100111.1₂ = 1.1001111₂ × 2^6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果是 &lt;code&gt;-103.5&lt;/code&gt;，则：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;S = 1
e = 6
E = 6 + 127 = 133 = 10000101₂
M = 10011110000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终拼接为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1 10000101 10011110000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;即：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;11000010110011110000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;转成十六进制为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;C2CF0000H
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;IEEE 754 解码的一般步骤&lt;/h4&gt;
&lt;p&gt;若要把 IEEE 754 单精度浮点数转换为真值，可以按以下步骤进行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 将 32 位二进制数拆成 S、E、M。
2. 判断 E 和 M 是否表示特殊值。
3. 若是规格化数，计算 e = E - 127。
4. 写出 1.M。
5. 根据公式 N = (-1)^S × 1.M × 2^e 计算真值。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;C2CF0000H
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;写成二进制：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;11000010110011110000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拆分：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;S = 1
E = 10000101₂ = 133
M = 10011110000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;e = 133 - 127 = 6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;真值为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;N = -1.1001111₂ × 2^6
  = -1100111.1₂
  = -103.5₁₀
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;IEEE 754 的精度问题&lt;/h4&gt;
&lt;p&gt;IEEE 754 浮点数虽然能表示很大的范围，但不能精确表示所有实数。&lt;/p&gt;
&lt;p&gt;原因是浮点数的位宽有限，尾数字段只能保存有限位二进制有效数字。因此，很多十进制小数转换成二进制后会变成无限循环小数，只能被近似存储。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;0.1
1.1
3.3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些十进制小数通常不能被二进制浮点数精确表示。&lt;/p&gt;
&lt;p&gt;因此，浮点数运算可能出现如下现象：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;3.3 / 1.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;数学上结果是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;3.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但计算机中实际参与运算的 &lt;code&gt;3.3&lt;/code&gt; 和 &lt;code&gt;1.1&lt;/code&gt; 可能已经是近似值，所以结果可能不严格等于 &lt;code&gt;3.0&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;因此在程序中通常不应直接判断：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;a == 3.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而应采用误差范围判断：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;|a - 3.0| &amp;#x3C; ε
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;IEEE 754 的上溢和下溢&lt;/h4&gt;
&lt;p&gt;IEEE 754 的指数范围有限，因此浮点运算可能发生上溢或下溢。&lt;/p&gt;
&lt;p&gt;当运算结果的绝对值太大，超过浮点数能够表示的最大范围时，发生上溢：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;overflow
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上溢结果通常变为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;+∞ 或 -∞
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当运算结果的绝对值太小，接近 0，小到无法用规格化数表示时，发生下溢：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;underflow
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下溢结果可能变为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;非规格化数 或 +0 / -0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以简单理解为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;上溢：数太大，趋向无穷
下溢：数太小，趋向 0
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;IEEE 754 异常运算结果&lt;/h4&gt;
&lt;p&gt;IEEE 754 对一些特殊运算结果有规定。&lt;/p&gt;
&lt;p&gt;非零数除以 0：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;正数 / 0 = +∞
负数 / 0 = -∞
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;无定义形式通常得到 NaN：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;0 / 0 = NaN
∞ / ∞ = NaN
0 × ∞ = NaN
∞ - ∞ = NaN
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果运算中已经有 NaN 参与，结果通常仍然是 NaN。&lt;/p&gt;
&lt;p&gt;因此，分析浮点异常运算时，可以先判断是否涉及：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;0
∞
NaN
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这三类特殊值。&lt;/p&gt;
&lt;h4&gt;IEEE 754 做题总结&lt;/h4&gt;
&lt;p&gt;分析 IEEE 754 浮点数时，可以按以下模板：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 先拆位段：
   S | E | M

2. 再判断类型：
   E = 0, M = 0      → ±0
   E = 0, M ≠ 0      → 非规格化数
   1 ≤ E ≤ 254       → 规格化数
   E = 255, M = 0    → ±∞
   E = 255, M ≠ 0    → NaN

3. 若是规格化数：
   e = E - 127
   N = (-1)^S × 1.M × 2^e

4. 若是编码题：
   先转二进制，再规格化，再求 E 和 M，最后拼接。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;核心记忆：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;IEEE 754 = 符号位 S + 阶码 E + 尾数 M
单精度：1 + 8 + 23
双精度：1 + 11 + 52
规格化数：(-1)^S × 1.M × 2^(E - bias)
E 全 0：0 或非规格化数
E 全 1：∞ 或 NaN
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;IEEE754与定点数的转换&lt;/h3&gt;
&lt;h4&gt;定点数与 IEEE 754 浮点数的基本区别&lt;/h4&gt;
&lt;p&gt;定点数的特点是“小数点位置固定”。同一个二进制位串，如果小数点位置不同，表示的数值也不同。因此解释定点数时，必须先知道它的格式，例如有多少位整数部分、多少位小数部分，以及是否采用补码表示。&lt;/p&gt;
&lt;p&gt;若一个定点数采用补码表示，并且有 &lt;code&gt;F&lt;/code&gt; 位小数位，则可以先把整个二进制位串看作一个补码整数，再除以 &lt;code&gt;2^F&lt;/code&gt; 得到真值：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;真值 = 补码整数值 / 2^F
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;IEEE 754 浮点数的小数点位置不固定，而是通过阶码控制数值范围。单精度 IEEE 754 浮点数共有 32 位，格式为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;S | EEEEEEEE | MMMMMMMMMMMMMMMMMMMMMMM
1位符号位 | 8位阶码 | 23位尾数
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;规格化数的真值公式为：&lt;/p&gt;
&lt;p&gt;$$
N=(-1)^S \times 1.M \times 2^{E-127}
$$&lt;/p&gt;
&lt;p&gt;其中，&lt;code&gt;S&lt;/code&gt; 是符号位，&lt;code&gt;E&lt;/code&gt; 是移码表示的阶码，&lt;code&gt;M&lt;/code&gt; 是尾数字段。规格化数中的最高位 &lt;code&gt;1&lt;/code&gt; 是隐藏位，不直接存储。&lt;/p&gt;
&lt;h4&gt;定点数转换为 IEEE 754 单精度浮点数&lt;/h4&gt;
&lt;p&gt;定点数转换为 IEEE 754 的核心思路是：先求出定点数的真值，再按照 IEEE 754 的 &lt;code&gt;S、E、M&lt;/code&gt; 三部分重新编码。&lt;/p&gt;
&lt;p&gt;步骤如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 根据定点数格式求出真值。
2. 判断正负，确定符号位 S。
3. 将真值的绝对值转换为二进制。
4. 将二进制数规格化为 1.xxxxx × 2^e。
5. 计算阶码 E = e + 127。
6. 取规格化后小数点后面的部分作为尾数 M。
7. 尾数不足 23 位补 0，超过 23 位则需要舍入。
8. 按 S | E | M 拼接成 32 位 IEEE 754 单精度浮点数。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如，若真值为 &lt;code&gt;-103.5&lt;/code&gt;，则：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;103.5₁₀ = 1100111.1₂
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;规格化为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1100111.1₂ = 1.1001111₂ × 2^6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;S = 1
e = 6
E = 6 + 127 = 133 = 10000101₂
M = 10011110000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拼接得到：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1 10000101 10011110000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;即：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;11000010110011110000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按 4 位分组转为十六进制：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1100 0010 1100 1111 0000 0000 0000 0000
 C    2    C    F    0    0    0    0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;(-103.5)₁₀ = C2CF0000H
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;IEEE 754 单精度浮点数转换为定点数&lt;/h4&gt;
&lt;p&gt;IEEE 754 转定点数的核心思路是：先拆出 &lt;code&gt;S、E、M&lt;/code&gt;，求出浮点数真值，再根据目标定点格式重新编码。&lt;/p&gt;
&lt;p&gt;步骤如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 将 32 位 IEEE 754 数拆成 S、E、M 三部分。
2. 判断 E 是否为特殊值。
3. 若 1 ≤ E ≤ 254，则说明它是规格化数。
4. 计算真实指数 e = E - 127。
5. 根据公式 N = (-1)^S × 1.M × 2^e 求出真值。
6. 若要转成定点数，设目标格式有 F 位小数位，则计算：
   定点整数值 = N × 2^F
7. 再将该整数值按照目标定点格式存储，例如补码或无符号数。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如，IEEE 754 单精度数：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;C2CF0000H
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先写成二进制：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;11000010110011110000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拆分：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;S = 1
E = 10000101₂ = 133
M = 10011110000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;计算指数：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;e = E - 127 = 133 - 127 = 6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此真值为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;N = -1.1001111₂ × 2^6
  = -1100111.1₂
  = -103.5₁₀
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果目标定点格式是 &lt;code&gt;Q8.8&lt;/code&gt;，即有 8 位小数位，则：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;定点整数值 = -103.5 × 2^8 = -26496
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后再把 &lt;code&gt;-26496&lt;/code&gt; 按目标位宽写成补码即可。需要注意，若目标定点格式表示范围不足，则会发生溢出。&lt;/p&gt;
&lt;h4&gt;IEEE 754 特殊值的判断&lt;/h4&gt;
&lt;p&gt;IEEE 754 单精度数中，最重要的是观察阶码 &lt;code&gt;E&lt;/code&gt; 和尾数 &lt;code&gt;M&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. E = 0，M = 0：
   表示 +0 或 -0。

2. E = 0，M ≠ 0：
   表示非规格化数。

3. 1 ≤ E ≤ 254：
   表示规格化数，真值为：
   N = (-1)^S × 1.M × 2^(E - 127)

4. E = 255，M = 0：
   表示 +∞ 或 -∞。

5. E = 255，M ≠ 0：
   表示 NaN，即 Not a Number。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此，分析 IEEE 754 位串时，不能一上来就套规格化数公式。应当先判断 &lt;code&gt;E&lt;/code&gt; 是否为全 0 或全 1。&lt;/p&gt;
&lt;h4&gt;浮点数异常运算结果分析&lt;/h4&gt;
&lt;p&gt;IEEE 754 中有一些特殊运算结果，主要涉及 &lt;code&gt;0&lt;/code&gt;、&lt;code&gt;∞&lt;/code&gt; 和 &lt;code&gt;NaN&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;非零数除以 0 时，结果通常为无穷大：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;正数 / +0 = +∞
负数 / +0 = -∞
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1.0 / 0.0 = +∞
-1.0 / 0.0 = -∞
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;无数学意义或无法确定唯一结果的运算，通常得到 &lt;code&gt;NaN&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;0 / 0 = NaN
∞ / ∞ = NaN
0 × ∞ = NaN
∞ - ∞ = NaN
sqrt(负数) = NaN
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果运算结果太大，超过浮点数能够表示的范围，则可能发生上溢，结果变为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;+∞ 或 -∞
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果运算结果太小，接近 0，可能发生下溢，结果变为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;非规格化数 或 +0 / -0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此，分析异常运算时可以按以下顺序判断：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 是否有 NaN 参与。
   如果有 NaN 参与，结果一般仍为 NaN。

2. 是否出现无定义形式。
   例如 0/0、∞/∞、0×∞、∞-∞，结果为 NaN。

3. 是否是非零数除以 0。
   若是，则结果为 +∞ 或 -∞，符号由运算符号决定。

4. 是否发生上溢。
   若结果超过可表示范围，则得到 +∞ 或 -∞。

5. 是否发生下溢。
   若结果太接近 0，则得到非规格化数或 0。
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;为什么浮点数运算可能不精确&lt;/h4&gt;
&lt;p&gt;很多十进制小数在二进制中不能被有限位精确表示。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;3.3
1.1
0.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些数转换为二进制后通常是无限循环小数，而 IEEE 754 的尾数字段长度有限，因此只能存储近似值。&lt;/p&gt;
&lt;p&gt;因此，类似下面的运算：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;3.3 / 1.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;数学结果应为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;3.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但在计算机中，&lt;code&gt;3.3&lt;/code&gt; 和 &lt;code&gt;1.1&lt;/code&gt; 可能都不是精确值，而是近似值，所以实际计算结果可能不是严格等于 &lt;code&gt;3.0&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;所以程序中不应直接用：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;a == 3.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;来判断浮点计算结果是否等于 3，而应使用误差范围判断：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;|a - 3.0| &amp;#x3C; ε
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;做题总结模板&lt;/h4&gt;
&lt;p&gt;遇到定点数与 IEEE 754 互换题，可以按如下模板书写：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;若由定点数转 IEEE 754：
1. 根据定点格式求出真值。
2. 判断符号，得到 S。
3. 将绝对值转为二进制。
4. 规格化为 1.M × 2^e。
5. 计算 E = e + 127。
6. 得到尾数 M。
7. 拼接 S | E | M。

若由 IEEE 754 转定点数：
1. 拆分 S、E、M。
2. 先判断是否为特殊值。
3. 若为规格化数，计算 e = E - 127。
4. 根据 N = (-1)^S × 1.M × 2^e 求真值。
5. 根据目标定点格式重新编码。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;遇到异常运算结果分析题，可以按如下模板书写：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. NaN 参与运算，结果通常为 NaN。
2. 0/0、∞/∞、0×∞、∞-∞ 等无定义形式，结果为 NaN。
3. 非零数除以 0，结果为 +∞ 或 -∞。
4. 结果过大，发生上溢，可能得到 +∞ 或 -∞。
5. 结果过小，发生下溢，可能得到非规格化数或 0。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;核心记忆：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;定点数看小数点位置；
IEEE 754 看 S、E、M；
异常运算重点看 0、∞、NaN。
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;组合逻辑电路设计&lt;/h2&gt;
&lt;h3&gt;险象&lt;/h3&gt;
&lt;h4&gt;竞争与险象&lt;/h4&gt;
&lt;p&gt;竞争：同一个输入变量经过不同路径到达同一逻辑门，因路径延迟不同，到达时间不同。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;竞争是原因；
险象是结果。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有竞争不一定有险象，但险象通常由竞争引起。&lt;/p&gt;
&lt;h4&gt;0 型险象&lt;/h4&gt;
&lt;p&gt;0 型险象：输出本应保持为 1，却短暂变为 0。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;波形：1 → 0 → 1
典型形式：F = A + A&apos; = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;记忆：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;正常为 1，中间掉到 0 → 0 型险象
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1 型险象&lt;/h4&gt;
&lt;p&gt;1 型险象：输出本应保持为 0，却短暂变为 1。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;波形：0 → 1 → 0
典型形式：F = A · A&apos; = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;记忆：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;正常为 0，中间冒出 1 → 1 型险象
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;险象判断条件&lt;/h4&gt;
&lt;p&gt;![[4 逻辑与计算机系统设计基础-第四章 组合逻辑电路设计(一) 4.1-4.4-cnwang-rev.pdf#page=35]]&lt;/p&gt;
&lt;p&gt;判断是否可能有险象，看三点：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 某变量同时以原变量和反变量出现，如 A 和 A&apos;。
2. 固定其他变量后，表达式可化为：
   X + X&apos; 或 X · X&apos;
3. 输入到同一门的两条路径传播延迟不同。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应关系：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X + X&apos; → 可能产生 0 型险象
X · X&apos; → 可能产生 1 型险象
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;为什么只看一个变量变化&lt;/h4&gt;
&lt;p&gt;分析险象时，通常只改变一个变量，固定其他变量。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;固定 n-1 个变量；
只看 1 个变量变化。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为多个变量同时变化会使分析复杂，也可能抵消某个变量的险象。&lt;/p&gt;
&lt;h4&gt;险象消除方法&lt;/h4&gt;
&lt;p&gt;常用方法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 增加冗余项
2. 卡诺图中补圈，使相邻项被同一个圈覆盖
3. 平衡路径延迟
4. 加滤波或惯性延时，滤掉短脉冲
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;同步时序电路设计&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Moore型&amp;#x26;Mealy型&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;运算方法与运算器&lt;/h2&gt;
&lt;h3&gt;运算溢出&lt;/h3&gt;
&lt;p&gt;![[9 逻辑与计算机系统设计基础 第五章 运算方法与算术逻辑单元(一）-cnwang-rev.pdf#page=19]]&lt;/p&gt;
&lt;h4&gt;溢出的基本概念&lt;/h4&gt;
&lt;p&gt;在定点补码运算中，计算机只能用固定的位数表示数值。如果真实运算结果超出了该位数补码所能表示的范围，就会发生溢出。&lt;/p&gt;
&lt;p&gt;设一个补码数共有 &lt;code&gt;n+1&lt;/code&gt; 位，其中最高位为符号位，后面 &lt;code&gt;n&lt;/code&gt; 位为数值位，则它能表示的范围是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;-2^n ~ 2^n - 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果运算结果小于 &lt;code&gt;-2^n&lt;/code&gt;，称为负溢出；如果运算结果大于 &lt;code&gt;2^n - 1&lt;/code&gt;，称为正溢出。&lt;/p&gt;
&lt;p&gt;溢出只针对“按有符号补码解释”的结果而言。补码加法器本身只是做二进制加法，硬件得到的结果仍然是一个固定位宽的二进制串；但这个二进制串可能已经不能表示真实数学结果。&lt;/p&gt;
&lt;h4&gt;补码加法与溢出的关系&lt;/h4&gt;
&lt;p&gt;补码加法的基本公式是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;[X + Y]补 = [X]补 + [Y]补
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说，在补码表示下，两个有符号数相加，可以直接把它们的补码送入加法器相加。&lt;/p&gt;
&lt;p&gt;但是，加法器的位数是固定的。如果最高位产生了进位，超出固定位宽的进位会被舍去。需要注意：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;最高位进位被舍去，不等于一定发生溢出；
没有最高位进位，也不等于一定没有溢出。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此，不能只看最后的进位输出 &lt;code&gt;Carryout&lt;/code&gt; 来判断补码有符号数是否溢出。&lt;/p&gt;
&lt;h4&gt;方法一：根据操作数符号和结果符号判断溢出&lt;/h4&gt;
&lt;p&gt;补码加法中，溢出只可能发生在两个同号数相加时。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;正数 + 正数：结果应该为正
负数 + 负数：结果应该为负
正数 + 负数：不会溢出
负数 + 正数：不会溢出
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此，方法一的判断规则是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 先判断两个操作数的符号是否相同。
2. 如果两个操作数异号，则不会发生溢出。
3. 如果两个操作数同号，再看结果符号。
4. 若结果符号与操作数符号不同，则发生溢出。
5. 若结果符号与操作数符号相同，则没有溢出。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以写成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;同号相加，结果变号 → 溢出
同号相加，结果不变号 → 不溢出
异号相加 → 不溢出
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;负数 + 负数 = 正数
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两个负数相加，真实结果应当更小，仍然应该是负数；如果机器结果变成正数，就说明发生了负溢出。&lt;/p&gt;
&lt;p&gt;又如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;正数 + 正数 = 负数
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两个正数相加，真实结果应当仍为正数；如果机器结果变成负数，就说明发生了正溢出。&lt;/p&gt;
&lt;h4&gt;方法一的逻辑表达式&lt;/h4&gt;
&lt;p&gt;设两个操作数的符号位分别为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X_s
Y_s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果的符号位为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;S_s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;则发生溢出的条件为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X_s = Y_s 且 S_s ≠ X_s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以分成正溢出和负溢出：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;正溢出：X_s = 0，Y_s = 0，S_s = 1
负溢出：X_s = 1，Y_s = 1，S_s = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;V = (~X_s &amp;#x26; ~Y_s &amp;#x26; S_s) | (X_s &amp;#x26; Y_s &amp;#x26; ~S_s)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;V&lt;/code&gt; 是溢出标志位：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;V = 1 表示溢出
V = 0 表示没有溢出
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;方法二：根据符号位进位判断溢出&lt;/h4&gt;
&lt;p&gt;方法二从加法器的进位角度判断溢出。&lt;/p&gt;
&lt;p&gt;设：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Cn：最高数据位向符号位产生的进位
Cf：符号位向更高位产生的进位
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;则溢出判断规则为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;如果 Cn = Cf，则没有溢出；
如果 Cn ≠ Cf，则发生溢出。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;V = Cn ⊕ Cf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;⊕&lt;/code&gt; 表示异或。&lt;/p&gt;
&lt;h4&gt;方法二的理解&lt;/h4&gt;
&lt;p&gt;最高数据位的进位 &lt;code&gt;Cn&lt;/code&gt; 表示数值部分对符号位产生了影响；符号位的进位 &lt;code&gt;Cf&lt;/code&gt; 表示符号位运算后又向更高位产生了影响。&lt;/p&gt;
&lt;p&gt;如果这两个进位相同，说明符号位的变化是正常的，结果仍在补码可表示范围内。&lt;/p&gt;
&lt;p&gt;如果这两个进位不同，说明符号位被异常改变，结果超出了补码表示范围，因此发生溢出。&lt;/p&gt;
&lt;p&gt;可以记成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;进位进符号位 和 进位出符号位 不同 → 溢出
进位进符号位 和 进位出符号位 相同 → 不溢出
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种方法适合硬件实现，因为只需要对两个进位信号做异或运算即可。&lt;/p&gt;
&lt;h4&gt;方法三：使用变型补码双符号位判断溢出&lt;/h4&gt;
&lt;p&gt;方法三使用变型补码，也就是使用两个符号位。&lt;/p&gt;
&lt;p&gt;设双符号位为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;f1 f2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;f1&lt;/code&gt; 是最高符号位，&lt;code&gt;f2&lt;/code&gt; 是第二符号位。&lt;/p&gt;
&lt;p&gt;判断规则为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;f1f2 = 00：结果为正数，没有溢出
f1f2 = 11：结果为负数，没有溢出
f1f2 = 01：正溢出
f1f2 = 10：负溢出
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此，双符号位法的溢出标志为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;V = f1 ⊕ f2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果两个符号位相同，说明没有溢出；如果两个符号位不同，说明发生溢出。&lt;/p&gt;
&lt;h4&gt;双符号位法的理解&lt;/h4&gt;
&lt;p&gt;双符号位可以理解为给符号位多留了一位“观察空间”。&lt;/p&gt;
&lt;p&gt;正常情况下，一个正数的符号位应为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;00
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一个负数的符号位应为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;11
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果两个正数相加后结果超过最大正数，第二符号位会表现出异常，出现：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;01
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这表示正溢出。&lt;/p&gt;
&lt;p&gt;如果两个负数相加后结果小于最小负数，会出现：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这表示负溢出。&lt;/p&gt;
&lt;p&gt;所以可以记成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;00、11：正常
01、10：溢出
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;加法溢出判断总结&lt;/h4&gt;
&lt;p&gt;补码加法的溢出判断可以总结为三种方法。&lt;/p&gt;
&lt;p&gt;第一种是符号法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;同号相加，结果变号，则溢出。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第二种是进位法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;V = Cn ⊕ Cf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Cn：进入符号位的进位
Cf：符号位产生的进位输出
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第三种是双符号位法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;V = f1 ⊕ f2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;f1f2 = 00：无溢出，正数
f1f2 = 11：无溢出，负数
f1f2 = 01：正溢出
f1f2 = 10：负溢出
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;考试中最常用的是符号法，硬件实现中常用进位异或法。&lt;/p&gt;
&lt;h4&gt;减法溢出判断&lt;/h4&gt;
&lt;p&gt;补码减法通常转化为加法实现：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X - Y = X + (-Y)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;[X - Y]补 = [X]补 + [-Y]补
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以判断减法是否溢出时，不要直接把它当成普通减法看，而应先改写成加法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X - Y → X + (-Y)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后按照补码加法溢出规则判断。&lt;/p&gt;
&lt;p&gt;也就是说，判断 &lt;code&gt;X - Y&lt;/code&gt; 是否溢出时，应该比较：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X 的符号
-Y 的符号
结果的符号
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;规则仍然是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X 与 -Y 同号，而结果变号 → 溢出
X 与 -Y 异号 → 不溢出
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;减法中的常见溢出情况&lt;/h4&gt;
&lt;p&gt;减法中容易发生溢出的情况包括：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;正数 - 负数 = 正数 + 正数
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果结果超过最大正数，则发生正溢出。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;较大正数 - 较大负数
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;真实结果会非常大，可能超过补码最大正数。&lt;/p&gt;
&lt;p&gt;另一种情况是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;负数 - 正数 = 负数 + 负数
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果结果小于最小负数，则发生负溢出。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;较小负数 - 较大正数
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;真实结果会非常小，可能超过补码最小负数的下界。&lt;/p&gt;
&lt;h4&gt;判断溢出时的注意点&lt;/h4&gt;
&lt;p&gt;判断补码有符号运算溢出时，不能只看最高位是否产生进位。&lt;/p&gt;
&lt;p&gt;例如，两个负数相加时，最高位可能产生进位并被舍去，但这不一定表示溢出；两个正数相加时，即使没有最终进位，也可能因为结果变成负数而溢出。&lt;/p&gt;
&lt;p&gt;因此，判断补码溢出时应优先使用以下规则：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 有符号数看符号变化。
2. 加法只在同号相加时可能溢出。
3. 减法先转化为加法，再按加法规则判断。
4. 硬件中可用 V = Cn ⊕ Cf 判断。
5. 双符号位中可用 V = f1 ⊕ f2 判断。
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;溢出判断做题模板&lt;/h4&gt;
&lt;p&gt;遇到补码加法溢出判断题，可以这样写：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 写出两个操作数的补码。
2. 进行补码加法。
3. 观察两个操作数符号是否相同。
4. 若两个操作数异号，则不溢出。
5. 若两个操作数同号，再观察结果符号。
6. 若结果符号与操作数符号不同，则溢出。
7. 若结果符号与操作数符号相同，则不溢出。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;遇到补码减法溢出判断题，可以这样写：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 将 X - Y 改写为 X + (-Y)。
2. 求 [-Y]补。
3. 进行补码加法 [X]补 + [-Y]补。
4. 比较 X、-Y 和结果的符号。
5. 若 X 与 -Y 同号而结果变号，则溢出。
6. 否则不溢出。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果题目要求从硬件信号判断，可以写：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;V = Cn ⊕ Cf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果题目使用双符号位，可以写：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;V = f1 ⊕ f2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;核心记忆：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;同号相加才可能溢出；
结果变号就是溢出；
进位判断看进入符号位和离开符号位是否不同；
双符号位判断看两个符号位是否不同。
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;定点乘除法运算&lt;/h3&gt;
&lt;h4&gt;原码一位乘法&lt;/h4&gt;
&lt;p&gt;原码一位乘法采用“符号单独处理，数值部分相乘”的方法。&lt;/p&gt;
&lt;p&gt;设：&lt;/p&gt;
&lt;p&gt;$$
[X]_{\text{原}}=X_f.X_1X_2\cdots X_n
$$&lt;/p&gt;
&lt;p&gt;$$
[Y]_{\text{原}}=Y_f.Y_1Y_2\cdots Y_n
$$&lt;/p&gt;
&lt;p&gt;则结果符号位为：&lt;/p&gt;
&lt;p&gt;$$
P_f=X_f\oplus Y_f
$$&lt;/p&gt;
&lt;p&gt;数值部分只计算：&lt;/p&gt;
&lt;p&gt;$$
|X|\times |Y|
$$&lt;/p&gt;
&lt;p&gt;核心操作是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;每轮只判断乘数的一位；
若当前乘数判断位为 1，则部分积加 |X|；
若当前乘数判断位为 0，则部分积加 0；
然后 {部分积Σ, 乘数Y} 整体逻辑右移一位。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可写成：&lt;/p&gt;
&lt;p&gt;$$
{\Sigma,Y}\leftarrow {\Sigma+Y_n|X|,Y}/2
$$&lt;/p&gt;
&lt;p&gt;其中 &lt;code&gt;/2&lt;/code&gt; 表示逻辑右移一位。&lt;/p&gt;
&lt;p&gt;做题步骤：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 写出 [X]原 和 [Y]原。
2. 符号位单独异或，得到结果符号 Pf。
3. 取 |X| 和 |Y| 做无符号一位乘法。
4. Σ 初值为 0，Y 存放乘数数值位。
5. 每轮看乘数最低位：
   - 为 1：Σ = Σ + |X|
   - 为 0：Σ = Σ + 0
6. 将 {Σ,Y} 整体逻辑右移一位。
7. 重复 n 次。
8. 最终结果为 Pf.{Σ,Y}。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 原码乘法中符号位不参与数值运算。
2. 右移是逻辑右移，因为参与运算的是绝对值。
3. 若加法产生最高进位，该进位也要参与整体右移，不能丢。
4. 结果数值部分通常为 2n 位，最终结果为 1 位符号位 + 2n 位数值位。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;记忆：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;原码一位乘法 = 符号异或 + 数值部分循环执行“判断乘数位、加或不加、整体右移”。
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;补码 Booth 一位乘法&lt;/h4&gt;
&lt;p&gt;补码 Booth 一位乘法直接对补码进行乘法，符号位参与运算。&lt;/p&gt;
&lt;p&gt;它不再单独处理符号位，而是利用乘数相邻两位的变化决定加法操作。&lt;/p&gt;
&lt;p&gt;需要增加一个附加位：&lt;/p&gt;
&lt;p&gt;$$
Y_{n+1}=0
$$&lt;/p&gt;
&lt;p&gt;每轮检查：&lt;/p&gt;
&lt;p&gt;$$
Y_nY_{n+1}
$$&lt;/p&gt;
&lt;p&gt;其中 (Y_n) 是当前乘数最低位，(Y_{n+1}) 是附加位。&lt;/p&gt;
&lt;p&gt;Booth 判断规则：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Yn Yn+1 = 00：Σ = Σ + 0
Yn Yn+1 = 11：Σ = Σ + 0
Yn Yn+1 = 01：Σ = Σ + [X]补
Yn Yn+1 = 10：Σ = Σ + [-X]补
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后对：&lt;/p&gt;
&lt;p&gt;$$
{\Sigma,Y,Y_{n+1}}
$$&lt;/p&gt;
&lt;p&gt;整体算术右移一位。&lt;/p&gt;
&lt;p&gt;做题步骤：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 写出 [X]补、[-X]补、[Y]补。
2. 设置 Σ = 0，附加位 Yn+1 = 0。
3. 检查 YnYn+1：
   - 00 或 11：加 0
   - 01：加 [X]补
   - 10：加 [-X]补
4. 将 {Σ,Y,Yn+1} 整体算术右移一位。
5. 重复规定次数。
6. 最后 {Σ,Y} 即为补码乘积。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. Booth 乘法中符号位参与运算。
2. 右移必须是算术右移，负数左边补 1，正数左边补 0。
3. 通常使用双符号位，保证中间部分积的符号不会丢失。
4. 双符号位中：
   00 表示正数正常；
   11 表示负数正常；
   01 或 10 表示溢出或中间位宽不够。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Booth 算法的本质是识别乘数中连续的 &lt;code&gt;1&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;00：仍在 0 区间，不操作
11：仍在 1 区间，不操作
01：连续 1 的结束边界，加 X
10：连续 1 的开始边界，减 X
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;记忆：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Booth 乘法 = 看乘数相邻两位；
01 加 X，10 减 X，00/11 不动；
然后整体算术右移。
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;原码一位除法：恢复余数法与加减交替法&lt;/h4&gt;
&lt;p&gt;原码除法采用“符号单独处理，数值部分相除”的方法。&lt;/p&gt;
&lt;p&gt;设：&lt;/p&gt;
&lt;p&gt;$$
[X]_{\text{原}}=X_f.X_1X_2\cdots X_n
$$&lt;/p&gt;
&lt;p&gt;$$
[Y]_{\text{原}}=Y_f.Y_1Y_2\cdots Y_n
$$&lt;/p&gt;
&lt;p&gt;商的符号位为：&lt;/p&gt;
&lt;p&gt;$$
Q_f=X_f\oplus Y_f
$$&lt;/p&gt;
&lt;p&gt;数值部分只计算：&lt;/p&gt;
&lt;p&gt;$$
|X|/|Y|
$$&lt;/p&gt;
&lt;h5&gt;原码恢复余数法&lt;/h5&gt;
&lt;p&gt;恢复余数法的核心是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;每轮先减除数试商；
如果结果非负，说明够减，商上 1；
如果结果为负，说明不够减，商上 0，并加回除数恢复余数。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每轮操作：&lt;/p&gt;
&lt;p&gt;$$
R\leftarrow 2R
$$&lt;/p&gt;
&lt;p&gt;$$
R\leftarrow R-|Y|
$$&lt;/p&gt;
&lt;p&gt;判断：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;若 R ≥ 0：商上 1，余数保持不变。
若 R &amp;#x3C; 0：商上 0，执行 R = R + |Y| 恢复余数。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;做题步骤：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 写出 [X]原 和 [Y]原。
2. 商符号 Qf = Xf ⊕ Yf。
3. 只取 |X| 和 |Y| 做除法。
4. 通常先做一次 X - Y，得到 Q0，用于判断整数位或溢出。
5. 之后每轮：
   - 余数左移一位；
   - 减除数试商；
   - 结果非负，商上 1；
   - 结果为负，商上 0，并加除数恢复余数。
6. 做满 n 位小数商。
7. 最终商写成 Qf.Q1Q2...Qn。
8. 由于余数左移了 n 次，最后余数要乘 2^(-n)。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. Q0 通常是整数位或溢出判断位，不一定写入最终定点小数商。
2. 若要求 n 位小数商，通常需要 n+1 次试商，其中第 1 次用于得到 Q0。
3. 左移次数为 n 次，所以最终余数要右移 n 位，即乘 2^(-n)。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;记忆：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;恢复余数法 = 先减除数试商；
够减商 1；
不够减商 0，并把减掉的除数加回来。
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;原码加减交替法&lt;/h5&gt;
&lt;p&gt;原码加减交替法也叫不恢复余数法。&lt;/p&gt;
&lt;p&gt;它对恢复余数法进行改进：
如果试商失败，不立刻恢复余数，而是在下一轮通过相反操作修正。&lt;/p&gt;
&lt;p&gt;基本规则：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;若当前余数 R ≥ 0：
    左移一位后减除数。

若当前余数 R &amp;#x3C; 0：
    左移一位后加除数。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是：&lt;/p&gt;
&lt;p&gt;$$
R\ge0:\quad R\leftarrow 2R-|Y|
$$&lt;/p&gt;
&lt;p&gt;$$
R&amp;#x3C;0:\quad R\leftarrow 2R+|Y|
$$&lt;/p&gt;
&lt;p&gt;每次加减完成后判断结果：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;若 R ≥ 0：商上 1。
若 R &amp;#x3C; 0：商上 0。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;做题步骤：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 商符号仍然为 Qf = Xf ⊕ Yf。
2. 数值部分使用 |X| 和 |Y|。
3. 先试 X - Y，得到 Q0。
4. 如果余数为正，下一轮左移后减除数。
5. 如果余数为负，下一轮左移后加除数。
6. 每轮根据新余数的符号上商：
   - R ≥ 0：商上 1
   - R &amp;#x3C; 0：商上 0
7. 若最后余数为负，通常需要加除数校正。
8. 最终余数仍需乘 2^(-n)。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;和恢复余数法的区别：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;恢复余数法：
    试商失败后立刻加除数恢复余数。

加减交替法：
    试商失败后不恢复，下一轮改为加除数。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;记忆：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;原码加减交替法 = 余数正，左移减除数；
余数负，左移加除数；
结果正商 1，结果负商 0。
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;补码加减交替法除法&lt;/h4&gt;
&lt;p&gt;补码加减交替法除法直接使用补码进行除法，符号位参与运算。&lt;/p&gt;
&lt;p&gt;与原码除法不同，它不单独用异或求商符号，而是让被除数、除数、余数和商都按补码规则参与运算。&lt;/p&gt;
&lt;p&gt;通常采用双符号位，以防止中间运算溢出。&lt;/p&gt;
&lt;p&gt;设：&lt;/p&gt;
&lt;p&gt;$$
[x]&lt;em&gt;{\text{补}},\quad [y]&lt;/em&gt;{\text{补}},\quad [-y]_{\text{补}}
$$&lt;/p&gt;
&lt;p&gt;第一步根据被除数和除数的符号决定操作：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;被除数与除数同号：
    做 x - y，即加 [-y]补。

被除数与除数异号：
    做 x + y，即加 [y]补。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后续每轮根据余数和除数的符号决定操作。&lt;/p&gt;
&lt;p&gt;判断规则：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;余数与除数同号：
    商上 1，余数左移一位，下一步减除数。

余数与除数异号：
    商上 0，余数左移一位，下一步加除数。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;即：&lt;/p&gt;
&lt;p&gt;$$
R\text{ 与 }Y\text{ 同号}:\quad Q_i=1,\quad R\leftarrow 2R-Y
$$&lt;/p&gt;
&lt;p&gt;$$
R\text{ 与 }Y\text{ 异号}:\quad Q_i=0,\quad R\leftarrow 2R+Y
$$&lt;/p&gt;
&lt;p&gt;做题步骤：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 写出 [x]补、[y]补、[-y]补。
2. 扩展成双符号位。
3. 判断 x 与 y 是否同号：
   - 同号：先做 x - y
   - 异号：先做 x + y
4. 得到余数 R 后，比较 R 与 y 的符号：
   - 同号：商上 1，左移后减 y
   - 异号：商上 0，左移后加 y
5. 重复直到得到规定商位。
6. 最后一步通常只移商，不再继续试商。
7. 根据情况校正商和余数。
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;商的校正&lt;/h5&gt;
&lt;p&gt;补码一位除法中，商可能需要校正。&lt;/p&gt;
&lt;p&gt;常见规则：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;能除尽时：
    若除数 &gt; 0，商不校正；
    若除数 &amp;#x3C; 0，商加 2^(-n) 校正。

不能除尽时：
    若商 &gt; 0，商不校正；
    若商 &amp;#x3C; 0，商加 2^(-n) 校正。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 (n) 是保留的小数位数。&lt;/p&gt;
&lt;p&gt;例如保留 4 位小数：&lt;/p&gt;
&lt;p&gt;$$
2^{-n}=2^{-4}=0.0001
$$&lt;/p&gt;
&lt;p&gt;若未校正商为：&lt;/p&gt;
&lt;p&gt;$$
1.0100
$$&lt;/p&gt;
&lt;p&gt;则校正后：&lt;/p&gt;
&lt;p&gt;$$
1.0100+0.0001=1.0101
$$&lt;/p&gt;
&lt;h5&gt;余数的校正&lt;/h5&gt;
&lt;p&gt;补码不恢复余数法是先比较、后上商，因此最后余数也可能需要校正。&lt;/p&gt;
&lt;p&gt;校正规则：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;若商 &gt; 0：
    当余数与被除数异号时，余数加除数校正。

若商 &amp;#x3C; 0：
    当余数与被除数同号时，余数减除数校正。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;商正：余数和被除数异号 → 余数加除数。
商负：余数和被除数同号 → 余数减除数。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 补码除法中符号位参与运算。
2. 判断上商不是看余数正负，而是看余数与除数是否同号。
3. 同号商 1，异号商 0。
4. 最终商可能需要校正，尤其是商为负且不能除尽时。
5. 最终余数也可能需要校正。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;记忆：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;补码加减交替除法 =
符号位参与运算；
余数与除数同号商 1，下一步减除数；
余数与除数异号商 0，下一步加除数；
最后检查商和余数是否需要校正。
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;存储系统&lt;/h2&gt;
&lt;p&gt;![[22 逻辑与计算机系统设计基础-第九章 存储器层次结构（一）v5.3-cnwang-rev.pdf#page=112]]&lt;/p&gt;
&lt;h3&gt;字长扩展&amp;#x26;字数扩展&lt;/h3&gt;
&lt;h4&gt;字长扩展：数据总线扩展&lt;/h4&gt;
&lt;p&gt;字长扩展也叫&lt;strong&gt;位扩展&lt;/strong&gt;，解决的是：&lt;strong&gt;单片存储器的数据位宽不够&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如 CPU 一次要读写 8 bit，但单片芯片只有 &lt;code&gt;256K × 1&lt;/code&gt;，每次只能输出 1 bit，就需要 8 片并联，组成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;8片 256K × 1  →  1组 256K × 8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;讲义中给出的规则是：若存储系统位宽为 &lt;code&gt;N&lt;/code&gt; 位，使用位宽为 &lt;code&gt;k&lt;/code&gt; 位的芯片，且 &lt;code&gt;k &amp;#x3C; N&lt;/code&gt;，则需要：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;芯片数 = N / k
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;字长扩展的连接方法&lt;/h4&gt;
&lt;p&gt;字长扩展时，所有芯片的&lt;strong&gt;地址线、控制线、片选信号共用&lt;/strong&gt;，但每片芯片接到数据总线的不同位上。&lt;/p&gt;
&lt;p&gt;例如用 8 片 &lt;code&gt;256K × 1&lt;/code&gt; 组成 &lt;code&gt;256K × 8&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;所有芯片共用地址线 A17~A0
所有芯片共用读写控制线 WE / OE / CS
第0片接 D0
第1片接 D1
……
第7片接 D7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说，CPU 给出一个地址时，8 片芯片&lt;strong&gt;同时工作&lt;/strong&gt;，每片贡献 1 bit，合起来得到 8 bit。&lt;/p&gt;
&lt;p&gt;考试关键词：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;字长扩展：各芯片并行工作
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;字长扩展的特点&lt;/h4&gt;
&lt;p&gt;字长扩展只扩大&lt;strong&gt;数据位宽&lt;/strong&gt;，不扩大可寻址单元个数。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;256K × 1  扩展成  256K × 8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;变化是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;字数：仍然是 256K
字长：从 1 bit 变成 8 bit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以地址线数量不变，数据线数量增加。&lt;/p&gt;
&lt;hr&gt;
&lt;h4&gt;字数扩展：地址总线扩展&lt;/h4&gt;
&lt;p&gt;字数扩展也叫&lt;strong&gt;容量扩展&lt;/strong&gt;，解决的是：&lt;strong&gt;单片存储器的存储单元个数不够&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如需要 &lt;code&gt;256K × 8&lt;/code&gt; 的存储系统，但单片只有 &lt;code&gt;64K × 8&lt;/code&gt;，则需要 4 片：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;4片 64K × 8  →  1组 256K × 8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;讲义中给出的规则是：若存储系统容量为 &lt;code&gt;M&lt;/code&gt;，使用容量为 &lt;code&gt;l&lt;/code&gt; 的芯片，且 &lt;code&gt;l &amp;#x3C; M&lt;/code&gt;，则需要：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;芯片数 = M / l
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;字数扩展的连接方法&lt;/h4&gt;
&lt;p&gt;字数扩展时，每片芯片的数据位宽已经够用，所以每片芯片都接完整的数据总线。&lt;/p&gt;
&lt;p&gt;低位地址线接入每片芯片内部，用来选择芯片内部的某个单元；高位地址线送入译码器，用来产生片选信号，决定当前访问哪一片。&lt;/p&gt;
&lt;p&gt;例如用 4 片 &lt;code&gt;64K × 8&lt;/code&gt; 组成 &lt;code&gt;256K × 8&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;64K = 2^16，所以每片内部需要 A15~A0
256K = 2^18，所以系统总共需要 A17~A0
高位 A17~A16 经过 2-4 译码器产生片选信号
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;地址分配可以理解为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A17 A16 = 00 → 选第0片
A17 A16 = 01 → 选第1片
A17 A16 = 10 → 选第2片
A17 A16 = 11 → 选第3片
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;考试关键词：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;字数扩展：同一时刻仅一片芯片工作
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;字数扩展的特点&lt;/h4&gt;
&lt;p&gt;字数扩展只扩大&lt;strong&gt;存储单元个数&lt;/strong&gt;，不扩大每个单元的数据位宽。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;64K × 8  扩展成  256K × 8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;变化是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;字数：从 64K 变成 256K
字长：仍然是 8 bit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以数据线数量不变，地址线数量增加。&lt;/p&gt;
&lt;hr&gt;
&lt;h4&gt;综合扩展&lt;/h4&gt;
&lt;p&gt;![[22 逻辑与计算机系统设计基础-第九章 存储器层次结构（一）v5.3-cnwang-rev.pdf#page=119]]&lt;/p&gt;
&lt;p&gt;如果单片芯片的&lt;strong&gt;字数不够&lt;/strong&gt;，同时&lt;strong&gt;字长也不够&lt;/strong&gt;，就要同时进行字数扩展和字长扩展。&lt;/p&gt;
&lt;p&gt;讲义中给出的总公式是：若目标存储系统为 &lt;code&gt;M × N&lt;/code&gt; 位，使用芯片为 &lt;code&gt;l × k&lt;/code&gt; 位，且 &lt;code&gt;l &amp;#x3C; M, k &amp;#x3C; N&lt;/code&gt;，则需要：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;总芯片数 = (M / l) × (N / k)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;理解方法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;先用 N/k 片做一组，完成字长扩展；
再用 M/l 组，完成字数扩展。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如用 &lt;code&gt;64K × 1&lt;/code&gt; 芯片组成 &lt;code&gt;256K × 8&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;字长扩展：8片 64K×1 → 64K×8
字数扩展：4组 64K×8 → 256K×8
总芯片数 = 8 × 4 = 32片
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;映射关系&lt;/h3&gt;
&lt;h4&gt;直接映射 Cache&lt;/h4&gt;
&lt;p&gt;直接映射（direct-mapped cache）是最简单的 Cache 映射方式。它规定：&lt;strong&gt;每个主存块只能放到 Cache 中唯一确定的一个位置&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;映射关系为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Cache块号 i = 主存块号 j mod Cache块数
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说，主存块的位置不是自由选择的，而是由地址直接决定。&lt;/p&gt;
&lt;p&gt;主存地址通常分为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Tag | Index | Offset
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Offset：块内偏移，选择块内的哪个字节/字
Index：Cache 行号/组号，确定访问哪一行
Tag：标记，判断该行中放的是不是目标主存块
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;直接映射可以看作：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1路组相联
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为每个组中只有 1 个 Cache 块。讲义中也明确说明：直接映射 = 1 路组相联，一个组里面只有一个 Cache 块。&lt;/p&gt;
&lt;p&gt;查找过程：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 用 Index 找到唯一的 Cache 行
2. 判断该行 valid 位是否为 1
3. 比较该行 Tag 是否等于地址中的 Tag
4. 相等则命中，不相等则缺失
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;特点：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;优点：结构简单，查找速度快，只需要一路比较
缺点：冲突缺失多，多个主存块可能争抢同一个 Cache 行
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;直接映射发生冲突时，被替换的位置是确定的，所以：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;不需要替换算法
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;N 路组相联 Cache&lt;/h4&gt;
&lt;p&gt;N 路组相联（N-way set-associative cache）是直接映射和全相联之间的折中。&lt;/p&gt;
&lt;p&gt;它把 Cache 分成若干个组，每组有 N 个 Cache 块，也叫 N 个 way。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;N路组相联 = 每组有 N 个 Cache line
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主存块进入 Cache 时：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;先由组索引确定它属于哪一组；
再在该组的 N 个位置中任选一个位置存放。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以它不是“任意放”，而是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;组固定，组内位置可选
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主存地址仍然分为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Tag | Set Index | Offset
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Offset：块内偏移
Set Index：确定访问哪一组
Tag：和该组内所有路的 Tag 并行比较
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查找过程：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 用 Set Index 找到对应组
2. 读出该组内 N 个 Tag 和 Valid 位
3. N 路并行比较 Tag
4. 若某一路 valid=1 且 Tag 相等，则命中
5. 若都不相等，则缺失
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;讲义中说，N 路组相联的标签阵列采用“普通 SRAM 单元阵列读出 + 并行比较”的机制；由于每组路数通常较少，如 2 路、4 路、8 路或 16 路，所以比较器数量仍可接受。&lt;/p&gt;
&lt;p&gt;缺失时：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;若该组还有空路，则放入空路；
若该组已满，则需要替换算法选择一个牺牲块。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;常见替换算法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;LRU：替换最近最少使用的块
FIFO：替换最早进入的块
Random：随机替换
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;特点：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;优点：比直接映射冲突少，命中率更高
缺点：硬件更复杂，需要多个比较器和多路选择器
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;讲义中也指出，组相联 Cache 通常比直接映射慢一些，因为需要额外比较器和输出多路选择器。&lt;/p&gt;
&lt;hr&gt;
&lt;h4&gt;全相联 Cache&lt;/h4&gt;
&lt;p&gt;全相联（fully-associative cache）是最灵活的 Cache 映射方式。它规定：&lt;strong&gt;主存中的任意块可以放入 Cache 的任意位置&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;可以理解为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;整个 Cache 只有一个组
该组中包含所有 Cache 块
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;讲义中明确说明：全相联 Cache 只有一个组，主存的一个给定块可以放置在 Cache 中任意块的位置。&lt;/p&gt;
&lt;p&gt;全相联地址通常只分为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Tag | Offset
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它没有普通意义上的组索引 &lt;code&gt;Index&lt;/code&gt;，因为只有一个组，不需要用 Index 选择组。&lt;/p&gt;
&lt;p&gt;查找过程：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 将地址中的 Tag 与 Cache 中所有块的 Tag 并行比较
2. 若某一项 valid=1 且 Tag 相等，则命中
3. 若所有 Tag 都不相等，则缺失
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;全相联的特点：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;优点：映射最灵活，冲突缺失最少，命中率高
缺点：需要和所有 Cache 块的 Tag 并行比较，硬件开销最大
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;讲义中指出：对于给定 Cache 容量，全相联 Cache 的冲突缺失率最少，但只适合非常小容量的 Cache。&lt;/p&gt;
&lt;p&gt;缺失时：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;如果 Cache 还有空块，放入任意空块；
如果 Cache 已满，必须使用替换算法选择牺牲块。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;全相联一定需要替换算法，因为目标块可以放在任意位置，满了以后必须决定淘汰谁。&lt;/p&gt;
&lt;h3&gt;替换策略&lt;/h3&gt;
&lt;h4&gt;先进先出替换（FIFO）&lt;/h4&gt;
&lt;p&gt;维护：&lt;strong&gt;距离进来已经经过的时间&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;替换时，替换&lt;strong&gt;经过时间最长的内容&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;最不经常使用算法（LFU）&lt;/h4&gt;
&lt;p&gt;维护：&lt;strong&gt;使用次数&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;替换时，替换&lt;strong&gt;使用次数最少的内容&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;最近最少使用（LRU）&lt;/h4&gt;
&lt;p&gt;维护：距离进来已经经过的时间，&lt;strong&gt;命中后重置为0&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;替换时，替换&lt;strong&gt;经过时间最长的内容&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;写策略&lt;/h3&gt;
&lt;h4&gt;写穿&lt;/h4&gt;
&lt;p&gt;写入 cache 块的数据同时写入主存，不需要脏位，但需要更多主存写入&lt;/p&gt;
&lt;h4&gt;写回&lt;/h4&gt;
&lt;p&gt;需要脏位，脏块移除时才会写回主存&lt;/p&gt;
&lt;h3&gt;cache优化&lt;/h3&gt;
&lt;h4&gt;读优化&lt;/h4&gt;
&lt;p&gt;时间局部性：利用驱逐算法将最不经常使用的数据逐出 cache。&lt;/p&gt;
&lt;p&gt;空间局部性：大块预取。&lt;/p&gt;
&lt;h4&gt;写优化&lt;/h4&gt;
&lt;p&gt;写回&lt;/p&gt;
&lt;h3&gt;命中率&lt;/h3&gt;
&lt;p&gt;$$
h=\frac{N_c}{N_c+N_m}
$$&lt;/p&gt;
&lt;p&gt;$N_c$ 表示 cache 完成存取访问的总次数&lt;/p&gt;
&lt;p&gt;$N_m$ 表示主存完成存取访问的总次数&lt;/p&gt;
&lt;h3&gt;$t_a$ 平均访问时间&lt;/h3&gt;
&lt;p&gt;$$
t_a=t_c+(1-h)t_m
$$&lt;/p&gt;
&lt;p&gt;$t_c$ 表示命中 cache 存储器时的访问时间&lt;/p&gt;
&lt;p&gt;$t_m$ 表示命中主存储器时的访问时间&lt;/p&gt;
&lt;h3&gt;存储访问时间&lt;/h3&gt;
&lt;p&gt;$$
e=\frac{t_c}{h}
$$&lt;/p&gt;
&lt;h3&gt;虚拟内存&lt;/h3&gt;
&lt;h2&gt;指令系统&lt;/h2&gt;
&lt;h3&gt;指令数量计算&lt;/h3&gt;
&lt;p&gt;![[17 逻辑与计算机系统设计基础-第七章 指令集架构(一)-cnwang-rev.pdf#page=21]]&lt;/p&gt;
&lt;h3&gt;寻址模式&lt;/h3&gt;
&lt;p&gt;![[17 逻辑与计算机系统设计基础-第七章 指令集架构(一)-cnwang-rev.pdf#page=24]]&lt;/p&gt;
&lt;h4&gt;操作数寻址&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;立即数寻址&lt;/strong&gt; ：变量赋初值&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;MOV AX, 200H
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;寄存器寻址&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;MOV AX, BX
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;内存直接寻址：直接访问内存&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;MOV AX, [200H]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;
&lt;p&gt;内存间接寻址&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;寄存器间接寻址&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;基址寻址&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;变址寻址&lt;/strong&gt; ：数组访问&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;MIPS寻址模式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;寄存器寻址&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;变址寻址&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;立即数寻址&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PC相对寻址&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;中央处理器&lt;/h2&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>《数据库系统原理》期末复习</title><link>https://lan-fa.github.io/blog/database-system-notes</link><guid isPermaLink="true">https://lan-fa.github.io/blog/database-system-notes</guid><description>要复习不完了，我该怎么办</description><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;可惜事已至此，复习不完也要复习完了。。。所以————————————&lt;/p&gt;
&lt;h1&gt;《数据库系统原理》常用知识&lt;/h1&gt;
&lt;h2&gt;怎么快速判断一个关系最高到哪个范式&lt;/h2&gt;
&lt;p&gt;可以用一个很快的判断流程。考试时不要从定义硬背，按这个顺序走：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;先求候选码
再分主属性/非主属性
再看函数依赖左边是不是码
最后看多值依赖
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;第一步：先找候选码&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是所有范式判断的基础。&lt;/p&gt;
&lt;p&gt;做法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;出现在右边的属性，通常不必一开始放进码。&lt;/li&gt;
&lt;li&gt;从来不出现在右边的属性，通常必须在候选码里。&lt;/li&gt;
&lt;li&gt;用属性闭包 &lt;code&gt;X+&lt;/code&gt; 看能不能推出全部属性。&lt;/li&gt;
&lt;li&gt;能推出全部属性，并且去掉任何一个属性后都不能推出全部属性，就是候选码。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R(A,B,C,D)
F = {A -&gt; B, C -&gt; D}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;右边出现过：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;B, D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;没出现在右边：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A, C
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以先试：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;(AC)+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A -&gt; B
C -&gt; D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;(AC)+ = ABCD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此候选码是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AC
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;第二步：分主属性和非主属性&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;主属性：出现在某个候选码中的属性
非主属性：不出现在任何候选码中的属性
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;刚才例子里候选码是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;主属性：A, C
非主属性：B, D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一步很重要，因为 2NF、3NF 都要看非主属性。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;第三步：判断 1NF&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;1NF 最简单：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;属性值不可再分，每个单元格只能放一个值。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果表里有这种东西：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;学生     电话
张三     123, 456
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一个格子里放多个电话号码，就不满足 1NF。&lt;/p&gt;
&lt;p&gt;一般教材题默认已经满足 1NF，除非题目明显给出“多值属性”。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;第四步：判断 2NF&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;2NF 要求：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;非主属性不能部分依赖于候选码。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;只在候选码是组合码时容易出问题。&lt;/p&gt;
&lt;p&gt;判断口诀：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;如果候选码是单属性码，一定满足 2NF。
如果候选码是组合码，就看有没有“候选码的一部分 -&gt; 非主属性”。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R(A,B,C,D)
候选码：AC
F = {A -&gt; B, C -&gt; D}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A -&gt; B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;A&lt;/code&gt; 是候选码 &lt;code&gt;AC&lt;/code&gt; 的一部分，&lt;code&gt;B&lt;/code&gt; 是非主属性。&lt;/p&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;B 部分依赖于候选码 AC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;违反 2NF。&lt;/p&gt;
&lt;p&gt;因此这个关系最高只能到：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1NF
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;第五步：判断 3NF&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;3NF 要求：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;非主属性不能传递依赖于候选码。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;常见违反形式：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;候选码 -&gt; 非主属性1 -&gt; 非主属性2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X 是候选码
X -&gt; A
A -&gt; B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;A&lt;/code&gt;、&lt;code&gt;B&lt;/code&gt; 都是非主属性。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R(Sno, Sdept, Mname)
F = {Sno -&gt; Sdept, Sdept -&gt; Mname}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;候选码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Sno
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Sno -&gt; Sdept -&gt; Mname
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以 &lt;code&gt;Mname&lt;/code&gt; 传递依赖于 &lt;code&gt;Sno&lt;/code&gt;，违反 3NF。&lt;/p&gt;
&lt;p&gt;这个关系如果没有部分依赖，那就是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;最高 2NF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;更快的 3NF 判断公式是：&lt;/p&gt;
&lt;p&gt;对于每个非平凡函数依赖：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X -&gt; A
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;至少满足一个：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X 是超码
A 是主属性
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;否则就不满足 3NF。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;第六步：判断 BCNF&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;BCNF 比 3NF 更严格。&lt;/p&gt;
&lt;p&gt;BCNF 要求：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;对每个非平凡函数依赖 &lt;code&gt;X -&gt; Y&lt;/code&gt;，X 必须是超码。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;口诀：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;只要有一个非平凡函数依赖的左边不是超码，就不是 BCNF。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R(A,B,C)
F = {AB -&gt; C, C -&gt; B}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;候选码可能是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AB, AC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以主属性：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A, B, C
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看依赖：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;C -&gt; B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;右边 &lt;code&gt;B&lt;/code&gt; 是主属性，所以它满足 3NF。&lt;/p&gt;
&lt;p&gt;但是 &lt;code&gt;C&lt;/code&gt; 不能推出全部属性，不是超码。&lt;/p&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;不满足 BCNF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此这个关系：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;最高 3NF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这也是 3NF 和 BCNF 的典型区别。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;第七步：判断 4NF&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;4NF 是在 BCNF 基础上进一步看多值依赖。&lt;/p&gt;
&lt;p&gt;4NF 要求：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;对每个非平凡多值依赖 &lt;code&gt;X -&gt;-&gt; Y&lt;/code&gt;，X 必须是超码。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;口诀：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;函数依赖看 BCNF
多值依赖看 4NF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果有：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X -&gt;-&gt; Y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并且：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Y 不包含于 X
X ∪ Y 不是全部属性
X 不是超码
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么就违反 4NF。&lt;/p&gt;
&lt;p&gt;典型例子：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Teaching(C, T, B)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;含义：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;C = 课程
T = 教师
B = 教材
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一门课程有多个教师，也有多本教材，教师和教材独立。&lt;/p&gt;
&lt;p&gt;存在：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;C -&gt;-&gt; T
C -&gt;-&gt; B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是 &lt;code&gt;C&lt;/code&gt; 不是候选码，所以违反 4NF。&lt;/p&gt;
&lt;p&gt;因此要拆成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;CT
CB
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;你可以直接背这个快速判断表：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1NF：
属性不可再分。

2NF：
在 1NF 基础上，非主属性不能部分依赖于候选码。
重点看：组合候选码的一部分 -&gt; 非主属性。

3NF：
在 2NF 基础上，非主属性不能传递依赖于候选码。
重点看：候选码 -&gt; 非主属性 -&gt; 非主属性。

BCNF：
每个非平凡函数依赖 X -&gt; Y，X 都必须是超码。
重点看：有没有左边不是超码的函数依赖。

4NF：
每个非平凡多值依赖 X -&gt;-&gt; Y，X 都必须是超码。
重点看：有没有非候选码决定多个独立取值集合。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;考试最快流程：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 求候选码
2. 标主属性、非主属性
3. 看有没有部分依赖
   有：最高 1NF
4. 看有没有传递依赖
   有：最高 2NF
5. 看每个函数依赖左边是不是超码
   不是：最高 3NF
6. 看有没有非平凡多值依赖且左边不是超码
   有：最高 BCNF
7. 都没有：4NF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意最后一句：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“最高到哪个范式”就是从低往高查，第一次违反的前一级，就是最高范式。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;达到BCNF的无损连接分解算法&lt;/h2&gt;
&lt;p&gt;这页讲的是：&lt;strong&gt;如何把一个关系模式分解到 BCNF，并且保证无损连接&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;核心思想很简单：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;只要某个关系模式不满足 BCNF，就找出一个违反 BCNF 的函数依赖，用它把表拆成两张表。反复拆，直到所有表都满足 BCNF。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;设原关系模式是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R&amp;#x3C;U, F&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;U = 属性全集
F = 函数依赖集
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;算法输出：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ρ = {R1, R2, ..., Rk}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要求：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;每个 &lt;code&gt;Ri&lt;/code&gt; 都是 BCNF&lt;/li&gt;
&lt;li&gt;整个分解是无损连接分解&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;BCNF 的判断标准是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;对于每个非平凡函数依赖 X -&gt; A，
X 必须是超码。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果存在：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X -&gt; A
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并且：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X 不是超码
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么这个关系就不是 BCNF。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;这页算法的重点是第 3 步：&lt;/p&gt;
&lt;p&gt;如果某个关系 &lt;code&gt;Ri&amp;#x3C;Ui, Fi&gt;&lt;/code&gt; 不是 BCNF，那么一定存在某个违反 BCNF 的函数依赖：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X -&gt; A
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;满足：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X -&gt; A 是非平凡函数依赖
X 不是 Ri 的码
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后把 &lt;code&gt;Ri&lt;/code&gt; 分解成两个关系：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;S1 = X ∪ {A}
S2 = Ui - {A}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;课件写成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;US1 = XA
US2 = Ui - {A}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Ri(Ui)  分解为  S1(X,A) 和 S2(Ui - A)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：&lt;code&gt;X&lt;/code&gt; 还保留在 &lt;code&gt;S2&lt;/code&gt; 里，因为 &lt;code&gt;US2 = Ui - {A}&lt;/code&gt;，只删掉右边那个属性 &lt;code&gt;A&lt;/code&gt;，不删 &lt;code&gt;X&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;举个例子：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R(A, B, C)
F = {A -&gt; B}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先判断是不是 BCNF。&lt;/p&gt;
&lt;p&gt;候选码是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A -&gt; B
AC -&gt; ABC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以 &lt;code&gt;AC&lt;/code&gt; 是码。&lt;/p&gt;
&lt;p&gt;但是有依赖：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A -&gt; B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;A&lt;/code&gt; 不是超码，因为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A+ = {A, B}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;推不出 &lt;code&gt;C&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R(A,B,C) 不满足 BCNF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按照算法，用违反 BCNF 的依赖：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X -&gt; A
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里为了避免和属性名混淆，可以写成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X -&gt; Y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;具体是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A -&gt; B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;则：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;S1 = A ∪ B = AB
S2 = U - B = AC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以分解为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R1(A, B)
R2(A, C)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;为什么这样分解是无损连接？&lt;/p&gt;
&lt;p&gt;因为分解后：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R1(A,B)
R2(A,C)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它们的公共属性是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而已知：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A -&gt; B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;公共属性 A -&gt; R1 - R2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以这个二元分解是无损连接的。&lt;/p&gt;
&lt;p&gt;通用地看：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;S1 = XA
S2 = Ui - {A}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两个表的交集是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;S1 ∩ S2 = X
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为 &lt;code&gt;X&lt;/code&gt; 在两边都有。&lt;/p&gt;
&lt;p&gt;而：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X -&gt; A
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X -&gt; S1 - S2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此这一步分解一定是无损连接的。&lt;/p&gt;
&lt;p&gt;这就是算法为什么能保证“达到 BCNF 的无损连接分解”。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;但是要注意一个很重要的问题：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这个算法保证无损连接，但不一定保持函数依赖。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;也就是说，拆完以后：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;自然连接不会出错
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但原来的某些函数依赖可能无法只在单张小表里检查，需要连接后才能检查。&lt;/p&gt;
&lt;p&gt;所以 BCNF 分解的特点是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;优点：一定无损连接，并且每个子关系达到 BCNF
缺点：不一定保持函数依赖
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;再用一个经典例子说明。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R(A, B, C)
F = {AB -&gt; C, C -&gt; B}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先求候选码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AB 是候选码
AC 也是候选码
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AB -&gt; C，所以 AB+ = ABC
C -&gt; B，所以 AC+ = ABC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看依赖：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AB -&gt; C
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;AB&lt;/code&gt; 是候选码，没问题。&lt;/p&gt;
&lt;p&gt;再看：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;C -&gt; B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;C&lt;/code&gt; 不是超码，因为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;C+ = {C, B}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;推不出 &lt;code&gt;A&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;所以违反 BCNF。&lt;/p&gt;
&lt;p&gt;按照算法，用：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;C -&gt; B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分解：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;S1 = C B
S2 = U - B = A C
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R1(C, B)
R2(A, C)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检查：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R1(C,B)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;C -&gt; B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;C&lt;/code&gt; 是 &lt;code&gt;R1&lt;/code&gt; 的候选码，所以是 BCNF。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R2(A,C)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;没有非平凡函数依赖，通常也是 BCNF。&lt;/p&gt;
&lt;p&gt;所以最终分解为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;{ R1(C,B), R2(A,C) }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;是 BCNF 无损连接分解。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;这页你可以记成一句考试口诀：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;发现 X -&gt; A 违反 BCNF，
就拆成 XA 和 U - A。
反复拆，直到每个表都是 BCNF。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中“违反 BCNF”的意思是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X -&gt; A 非平凡，且 X 不是超码。
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;达到3NF且保持函数依赖的分解算法&lt;/h2&gt;
&lt;p&gt;这页是讲：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;把关系模式分解到 3NF，并且保持函数依赖。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;它和前面的 BCNF 分解算法不同：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;BCNF 分解：保证无损连接，但不一定保持函数依赖
3NF 分解：保证保持函数依赖，通常也可通过补候选码保证无损连接
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;先看输入：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R&amp;#x3C;U, F&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;U = 全部属性集
F = 函数依赖集
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;目标是分解成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ρ = {R1, R2, ..., Rk}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并且每个 &lt;code&gt;Ri&lt;/code&gt; 都满足 3NF。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;第 1 步：求最小函数依赖集 F&apos;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;也叫最小覆盖、最小依赖集。&lt;/p&gt;
&lt;p&gt;它要满足：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;每个依赖右边只有一个属性：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X -&gt; A
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;左边没有多余属性。&lt;/li&gt;
&lt;li&gt;整个依赖集中没有多余依赖。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;比如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A -&gt; BC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要先拆成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A -&gt; B
A -&gt; C
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果有：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AB -&gt; C
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但其实：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A -&gt; C
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也能推出，那么 &lt;code&gt;B&lt;/code&gt; 就是多余属性，要删掉。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;第 2 步：如果 F&apos; 中有 X -&gt; A，并且 XA = U，就不用分解&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这里：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;XA = U
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;意思是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X ∪ {A} = 全部属性集 U
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是这个函数依赖已经覆盖了整个关系模式。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R(A,B,C)
F&apos; = {AB -&gt; C}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AB ∪ C = ABC = U
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以直接：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ρ = {R}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;算法结束。&lt;/p&gt;
&lt;p&gt;直观理解：
这个依赖已经说明 &lt;code&gt;AB&lt;/code&gt; 能决定整个关系，原关系通常已经足够好，不需要再按这个算法拆。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;第 3 步：找出没有出现在 F&apos; 中的属性，单独构成一个关系模式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;课件写：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;找出不在 F&apos; 中出现的属性，将它们构成一个关系模式，并从 U 中去掉它们。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这里“不在 F&apos; 中出现”指的是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这个属性既没有出现在任何依赖的左边，也没有出现在任何依赖的右边。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;比如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R(A,B,C,D,E)
F&apos; = {A -&gt; B, C -&gt; D}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里 &lt;code&gt;E&lt;/code&gt; 没有出现在任何函数依赖里。&lt;/p&gt;
&lt;p&gt;所以把它单独拿出来：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R0(E)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后剩余属性继续处理：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;U = {A,B,C,D}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为什么要这样做？
因为 &lt;code&gt;E&lt;/code&gt; 和任何依赖都没关系，放在哪个依赖生成的小表里都不自然，所以先单独保留，避免属性丢失。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;第 4 步：对 F&apos; 中每个 X -&gt; A，构造一个关系模式 XA&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;也就是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X -&gt; A
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R(X,A)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;F&apos; = {A -&gt; B, C -&gt; D}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就生成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R1(A,B)
R2(C,D)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果多个依赖左边相同，可以合并。&lt;/p&gt;
&lt;p&gt;比如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A -&gt; B
A -&gt; C
A -&gt; D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不用生成三个表：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AB
AC
AD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以合并成一个：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A B C D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R(A,B,C,D)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为它们都是由 &lt;code&gt;A&lt;/code&gt; 决定的属性。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;第 5 步：删除被包含的关系模式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果出现：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Ui ⊆ Uj
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就删掉较小的 &lt;code&gt;Ui&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;例如分解结果里有：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R1(A,B)
R2(A,B,C)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;{A,B} ⊆ {A,B,C}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以 &lt;code&gt;R1(A,B)&lt;/code&gt; 可以删掉，只保留：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R2(A,B,C)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原因是 &lt;code&gt;R1&lt;/code&gt; 的属性已经完全包含在 &lt;code&gt;R2&lt;/code&gt; 里，单独保留会冗余。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;来个完整例子：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R(A,B,C,D)
F = {A -&gt; B, C -&gt; D}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最小依赖集就是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;F&apos; = {A -&gt; B, C -&gt; D}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按第 4 步：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A -&gt; B 生成 R1(A,B)
C -&gt; D 生成 R2(C,D)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以得到：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ρ = {R1(A,B), R2(C,D)}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是注意：这里属性覆盖不完整吗？&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R1 ∪ R2 = {A,B,C,D}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完整，所以没丢属性。&lt;/p&gt;
&lt;p&gt;每个关系都满足 3NF：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R1(A,B): A -&gt; B，A 是码
R2(C,D): C -&gt; D，C 是码
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以结果是 3NF 分解。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;不过这里要特别注意一个考试点：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这个算法主要保证“保持函数依赖”。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因为每个依赖 &lt;code&gt;X -&gt; A&lt;/code&gt; 都被放进了一个关系模式 &lt;code&gt;XA&lt;/code&gt; 里，所以原来的函数依赖可以在分解后的表中直接检查。&lt;/p&gt;
&lt;p&gt;比如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A -&gt; B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 &lt;code&gt;R1(A,B)&lt;/code&gt; 里检查。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;C -&gt; D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 &lt;code&gt;R2(C,D)&lt;/code&gt; 里检查。&lt;/p&gt;
&lt;p&gt;不需要把表连接回去再检查。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;但是如果题目要求：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;既保持函数依赖，又无损连接
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通常还要补一步：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果分解结果中没有任何一个关系模式包含原关系的候选码，就再加入一个候选码关系。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;比如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R(A,B,C,D)
F = {A -&gt; B, C -&gt; D}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;候选码是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;刚才得到：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R1(A,B)
R2(C,D)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;没有任何一个表包含候选码 &lt;code&gt;AC&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;所以为了保证无损连接，通常还要加入：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R3(A,C)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ρ = {R1(A,B), R2(C,D), R3(A,C)}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样才是常见教材里的：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;无损连接且保持函数依赖的 3NF 分解
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;所以这页你可以这样记：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;3NF 保依赖分解算法：

1. 求最小依赖集 F&apos;
2. 每个 X -&gt; A 生成一个表 XA
3. 相同左部的依赖合并
4. 删除被其他表包含的小表
5. 若要求无损连接，检查是否含候选码；没有就补一个候选码表
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一句话版：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BCNF 分解是“发现违规就拆”；3NF 分解是“按最小依赖集造表”。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;达到3NF既保持函数依赖又无损连接的分解&lt;/h2&gt;
&lt;p&gt;这页是在补全前一页的问题：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;前一个算法 6.4 可以得到 &lt;strong&gt;保持函数依赖的 3NF 分解&lt;/strong&gt;，但不一定保证 &lt;strong&gt;无损连接&lt;/strong&gt;。
算法 6.5 就是在它的基础上，补一个“候选码关系”，从而保证无损连接。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;先看它的输入条件：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;设 ρ = {R1&amp;#x3C;U1,F1&gt;, ..., Rk&amp;#x3C;Uk,Fk&gt;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是 &lt;code&gt;R&amp;#x3C;U,F&gt;&lt;/code&gt; 的一个 &lt;strong&gt;保持函数依赖的 3NF 分解&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;也就是说，&lt;code&gt;ρ&lt;/code&gt; 已经满足两个性质：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 每个 Ri 都是 3NF
2. 保持函数依赖
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是它可能还没有保证：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;无损连接
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以算法 6.5 的目标是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;3NF + 保持函数依赖 + 无损连接
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;关键步骤是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;设 X 为 R&amp;#x3C;U,F&gt; 的码
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是先找原关系 &lt;code&gt;R&lt;/code&gt; 的一个候选码 &lt;code&gt;X&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;然后检查：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;是否存在某个 Ui，使得 X ⊆ Ui
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;意思是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;分解后的某张表里，是否已经包含了原关系的候选码。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果有，那么：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ρ 即为所求
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是不用再补表。&lt;/p&gt;
&lt;p&gt;如果没有，就新增一张表：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R*(X)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;τ = ρ ∪ {R*&amp;#x3C;X, FX&gt;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;FX&lt;/code&gt; 是 &lt;code&gt;F&lt;/code&gt; 在 &lt;code&gt;X&lt;/code&gt; 上的投影。&lt;/p&gt;
&lt;p&gt;简单说就是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果现有分解里没有任何一个关系包含候选码，就额外加一张“候选码表”。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;举个例子，还是这个：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R(A,B,C,D)
F = {A -&gt; B, C -&gt; D}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先求候选码。&lt;/p&gt;
&lt;p&gt;因为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A -&gt; B
C -&gt; D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AC -&gt; ABCD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此候选码是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用算法 6.4 按最小依赖集造表：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A -&gt; B  得 R1(A,B)
C -&gt; D  得 R2(C,D)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以得到：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ρ = {R1(A,B), R2(C,D)}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它保持函数依赖，因为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A -&gt; B 在 R1 中
C -&gt; D 在 R2 中
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每张表也都是 3NF。&lt;/p&gt;
&lt;p&gt;但是它无损吗？不一定。&lt;/p&gt;
&lt;p&gt;检查有没有某个 &lt;code&gt;Ui&lt;/code&gt; 包含候选码 &lt;code&gt;AC&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;U1 = {A,B}
U2 = {C,D}
X = {A,C}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;显然：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AC ⊄ AB
AC ⊄ CD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;没有任何一张表包含候选码 &lt;code&gt;AC&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;所以要补一张：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R3(A,C)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终分解为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;τ = {R1(A,B), R2(C,D), R3(A,C)}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这就同时满足：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 3NF
2. 保持函数依赖
3. 无损连接
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;为什么补候选码表就能保证无损连接？&lt;/p&gt;
&lt;p&gt;直观理解：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;候选码能唯一标识原关系中的一行。
如果分解结果中保留了一张候选码表，就相当于保留了原表元组之间的“骨架”，自然连接时不容易乱配出假元组。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;比如刚才如果只有：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R1(A,B)
R2(C,D)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它们没有公共属性，连接回来会变成笛卡尔积，很容易多出假元组。&lt;/p&gt;
&lt;p&gt;加上：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R3(A,C)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R1(A,B) 通过 A 和 R3(A,C) 连接
R2(C,D) 通过 C 和 R3(A,C) 连接
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;R3(A,C)&lt;/code&gt; 把 &lt;code&gt;A&lt;/code&gt; 和 &lt;code&gt;C&lt;/code&gt; 的正确搭配保存下来了，所以不会乱配。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;最后这句：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;如发现某个 Ui ⊆ X，则应将 Ui 去掉
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;意思是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果原来某张表的属性集完全包含在候选码表 &lt;code&gt;X&lt;/code&gt; 里面，那它就没必要单独保留了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;比如候选码是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X = {A,C}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果原来分解里已经有：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R1(A)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;{A} ⊆ {A,C}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加入 &lt;code&gt;R*(A,C)&lt;/code&gt; 后，&lt;code&gt;R1(A)&lt;/code&gt; 就被包含了，可以删掉。&lt;/p&gt;
&lt;p&gt;这是为了去掉冗余关系模式。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;所以算法 6.5 可以这样记：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;先用算法 6.4 得到保持函数依赖的 3NF 分解；
再看有没有表包含原关系候选码；
有：结束；
没有：补一张候选码表；
如果有小表被候选码表包含，就删掉小表。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一句话总结：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;算法 6.5 = 算法 6.4 的结果 + 必要时补一个候选码关系，从而保证无损连接。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;关于无损分解的总结&lt;/h2&gt;
&lt;p&gt;可以把这部分压缩成&lt;strong&gt;三个考点&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 判断是否保持函数依赖
2. 判断是否无损连接
3. 构造“保持FD + 无损连接 + 3NF”的分解
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这也正好对应 PPT 6.5 的复习点：保持函数依赖如何判定、无损连接如何判定，以及算法 6.5：无损连接 + 保持函数依赖的 3NF 分解。&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;考点一：保持函数依赖&lt;/h3&gt;
&lt;p&gt;它问的是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;分解以后，原来的函数依赖还能不能在各个子关系中检查？
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;设原关系：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R(U, F)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分解为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ρ = {R1(U1), R2(U2), ..., Rn(Un)}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每个子关系上能保留一部分函数依赖：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;F1 = F 在 U1 上的投影
F2 = F 在 U2 上的投影
...
Fn = F 在 Un 上的投影
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;令：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;G = F1 ∪ F2 ∪ ... ∪ Fn
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;F+ = G+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就叫&lt;strong&gt;保持函数依赖&lt;/strong&gt;。PPT 的定义也是：如果 &lt;code&gt;F&lt;/code&gt; 与 &lt;code&gt;F1∪F2∪...∪Fn&lt;/code&gt; 等价，则该分解保持函数依赖。&lt;/p&gt;
&lt;p&gt;考试中通常这样写：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;求各子关系上的投影 F1, F2, ...
令 G = F1 ∪ F2 ∪ ...
检查 F 中每条函数依赖是否能由 G 推出。
若都能推出，则保持FD；
若有一条推不出，则不保持FD。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R(A,B,C)
F = {A -&gt; B, B -&gt; C}
ρ = {AB, AC}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分解后：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AB 上有 A -&gt; B
AC 上有 A -&gt; C
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;G = {A -&gt; B, A -&gt; C}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是原来的：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;B -&gt; C
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不能由 &lt;code&gt;G&lt;/code&gt; 推出。&lt;/p&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ρ = {AB, AC} 不保持函数依赖
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;记住一句话：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;保持FD看的是：约束还能不能在分解后的表中局部检查。
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;考点二：无损连接&lt;/h3&gt;
&lt;p&gt;它问的是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;分解后的表自然连接回来，会不会多出假元组？
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R = R1 ⋈ R2 ⋈ ... ⋈ Rn
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就叫&lt;strong&gt;无损连接&lt;/strong&gt;。PPT 中也说，若 &lt;code&gt;R&lt;/code&gt; 与 &lt;code&gt;R1、R2、...、Rn&lt;/code&gt; 自然连接的结果相等，则该分解具有无损连接性。&lt;/p&gt;
&lt;p&gt;最常考的是&lt;strong&gt;二分解&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R -&gt; R1, R2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;判断规则：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;如果 (R1 ∩ R2) -&gt; (R1 - R2)
或者 (R1 ∩ R2) -&gt; (R2 - R1)
属于 F+
则无损连接。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以记成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;交集 -&gt; 某一边的差集
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只要推出&lt;strong&gt;其中一个差集&lt;/strong&gt;就够，不需要两个都推出。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R(A,B,C)
F = {A -&gt; B, C -&gt; B}
ρ = {AC, BC}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;计算：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AC ∩ BC = C
AC - BC = A
BC - AC = B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;C -&gt; B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;C -&gt; BC - AC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ρ = {AC, BC} 是无损连接
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PPT 例 6.20 正是这样判断的：&lt;code&gt;AC∩BC=C&lt;/code&gt;，&lt;code&gt;BC-AC=B&lt;/code&gt;，由于 &lt;code&gt;C→B∈F+&lt;/code&gt;，所以该分解具有无损连接性。&lt;/p&gt;
&lt;p&gt;但是注意：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;无损连接 ≠ 保持函数依赖
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;无损连接看的是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;连接回来会不会出错
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;保持 FD 看的是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;依赖约束有没有丢
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这两个是不同性质。&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;考点三：构造“保持FD + 无损连接 + 3NF”的分解&lt;/h3&gt;
&lt;p&gt;这是让你自己分解。&lt;/p&gt;
&lt;p&gt;目标是得到：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ρ = {R1, R2, ..., Rn}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;满足：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. 每个 Ri 达到 3NF
2. 保持函数依赖
3. 无损连接
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PPT 算法 6.5 说：先得到一个保持函数依赖的 3NF 分解；若已有某个子模式包含原关系的码，则该分解就是所求；否则再加入一个由码构成的关系。&lt;/p&gt;
&lt;p&gt;考试步骤可以写成：&lt;/p&gt;
&lt;h4&gt;第一步：求最小函数依赖集&lt;/h4&gt;
&lt;p&gt;把 &lt;code&gt;F&lt;/code&gt; 化成最小依赖集：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;右边单属性；
左边无多余属性；
依赖本身无多余。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A -&gt; BC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先拆成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A -&gt; B
A -&gt; C
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;第二步：按每条 FD 建关系&lt;/h4&gt;
&lt;p&gt;对每条：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X -&gt; A
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;建一个关系：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;XA
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果多条 FD 左部相同，可以合并。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A -&gt; B
A -&gt; C
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以合并成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ABC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而不是非要写成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AB, AC
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;第三步：检查是否包含候选码&lt;/h4&gt;
&lt;p&gt;看当前分解中有没有某个子关系包含原关系 &lt;code&gt;R&lt;/code&gt; 的&lt;strong&gt;一个候选码&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如果有，不用加。&lt;/p&gt;
&lt;p&gt;如果没有，额外加入一个候选码关系。&lt;/p&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;只需要加入一个候选码，不需要加入所有候选码。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如候选码有：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AC, BC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只需要加：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者只加：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;BC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;任选一个即可。&lt;/p&gt;
&lt;hr&gt;
&lt;h4&gt;第四步：删掉被包含的子关系&lt;/h4&gt;
&lt;p&gt;如果有：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AB ⊆ ABC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么 &lt;code&gt;AB&lt;/code&gt; 可以删掉，保留 &lt;code&gt;ABC&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;最后压缩成答题口诀&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;保持FD：看 F 和各子关系投影并集是否等价。
无损连接：二分解看 交集 -&gt; 某个差集。
3NF分解：最小依赖集建表；若无候选码表，加一个候选码；再删包含项。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再短一点就是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;保持FD管“依赖有没有丢”；
无损连接管“连接会不会多假元组”；
3NF分解管“怎么构造一个既保持FD又无损的分解”。
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;需要背诵的一些东西&lt;/h1&gt;
&lt;h3&gt;数据库系统的特点？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;数据结构化&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;数据不再只服务某一个程序，而是面向整个组织；不仅记录内部有结构，数据之间也有联系。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;数据共享性高、冗余度低、易扩充&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;数据独立性高&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;数据由DBMS统一管理和控制&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;模型&lt;/h2&gt;
&lt;h4&gt;概念模型&lt;/h4&gt;
&lt;h4&gt;数据模型&lt;/h4&gt;
&lt;h5&gt;层次模型&lt;/h5&gt;
&lt;p&gt;适合表示一对多关系&lt;/p&gt;
&lt;p&gt;不适合多对多关系&lt;/p&gt;
&lt;h5&gt;网状模型&lt;/h5&gt;
&lt;h5&gt;关系模型&lt;/h5&gt;
&lt;h2&gt;三级模式&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;概念模式 / 模式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;面向数据库设计人员，描述数据整体逻辑结构&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;外模式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;面向应用程序，靠近用户，描述视图。&lt;/p&gt;
&lt;p&gt;不同用户的需求创建不同视图&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;内模式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;面向物理层，描述数据采用什么样的数据结构存储和获取&lt;/p&gt;
&lt;h2&gt;二级映像&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;外模式 / 概念模式映像&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;保持逻辑独立性：修改概念模式基本表时，不影响外模式&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每一个外模式都对应一个映像&lt;/li&gt;
&lt;li&gt;映像定义包含在外模式描述中&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;概念模式 / 内模式映像&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;保持物理独立性：修改了内模式，不影响上层的概念模式和外模式&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;唯一&lt;/li&gt;
&lt;li&gt;包含在模式描述中&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三个完整性&lt;/h2&gt;
&lt;h3&gt;实体完整性&lt;/h3&gt;
&lt;p&gt;所有主属性都不能为控制&lt;/p&gt;
&lt;h3&gt;参照完整性&lt;/h3&gt;
&lt;p&gt;外码必须出现在引用中，或者为空值&lt;/p&gt;
&lt;h3&gt;用户定义完整性&lt;/h3&gt;
&lt;p&gt;下次一定好好复习（&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Test Blog</title><link>https://lan-fa.github.io/blog/test</link><guid isPermaLink="true">https://lan-fa.github.io/blog/test</guid><description>A temporary test post for checking the blog page.</description><pubDate>Mon, 08 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这是一篇测试性质的博客，用来确认博客列表、标签和文章页面是否能够正常显示。&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item></channel></rss>