ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

定点数运算详解:移位、补码、溢出判断与工程实践

定点数运算详解:移位、补码、溢出判断与工程实践 1. 定点数运算到底在算什么定点数运算这个词刚接触计算机组成原理或者数字电路的人可能会觉得抽象。说白了它就是计算机里最底层的那套算术规则——数字用固定位数的小数点位置来表示然后在这个约束下做移位、加减乘除。你写的每一行代码最终在CPU里跑的时候整数运算走的就是这套逻辑。为什么叫“定点”因为小数点位置是固定的。比如一个8位二进制数约定前4位是整数部分、后4位是小数部分那它表示的范围和精度就锁死了。跟浮点数相比定点数运算速度快、硬件开销小但表示范围窄。嵌入式开发、FPGA设计、DSP信号处理这些领域定点数运算是天天要打交道的东西。这篇文章要拆解的核心内容就是定点数运算的五大块移位运算、原码加减、补码加减、原码乘除、补码乘除外加一个贯穿始终的关键问题——溢出怎么判断。不管你是正在啃计算机组成原理的学生还是做底层开发需要手算验证的工程师这些内容都是绕不过去的基本功。我当年学这块的时候最大的困惑不是“怎么算”而是“为什么要这么设计”。比如补码的减法为什么要转成加法移位为什么分逻辑移位和算术移位溢出判断为什么有好几种方法这些问题如果只背结论过两天就忘了。所以下面我会尽量把每个设计决策背后的逻辑讲清楚让你不光会算还能理解为什么这么算。2. 移位运算最容易被低估的基础操作2.1 逻辑移位与算术移位的本质区别移位运算看起来简单——左移右移嘛有什么好讲的但这里恰恰是很多人翻车的地方。关键问题在于移位之后空出来的位补什么逻辑移位不管符号左移右补0右移左补0。它把二进制串当成纯粹的无符号数来处理。算术移位则要考虑符号位右移的时候符号位不变空出的位补符号位的值。举个例子8位二进制数10000010逻辑右移一位01000001最高位补0算术右移一位11000001最高位补1保持符号位为什么要有这个区别因为补码表示负数时右移一位相当于除以2。如果负数右移补0符号就变了数值就错了。你可以这样理解算术右移是在保持“这个数还是负数”的前提下把绝对值缩小一半。左移的情况稍微不同。逻辑左移和算术左移都是低位补0但算术左移如果符号位发生变化就说明溢出了。这一点后面讲溢出判断时会再展开。2.2 移位运算的实操要点与常见坑在实际写代码或者做硬件设计时移位运算有几个必须注意的点第一移位位数超过字长怎么办。比如一个32位寄存器你让它左移35位。不同架构处理方式不同——有的取模35 mod 32 3相当于左移3位有的直接清零。写可移植代码时千万别依赖这种未定义行为。第二算术右移的舍入问题。补码算术右移是向下取整的。比如 -3 的补码右移一位结果是 -2 而不是 -1。因为 -3/2 -1.5向下取整是 -2。如果你需要的是向零取整就得额外处理。第三左移和乘以2的关系。左移一位等于乘以2这个结论在没溢出的前提下成立。一旦溢出结果就不对了。很多新手写代码时喜欢用左移代替乘法来“优化性能”但如果没做溢出检查bug就埋进去了。实操心得在做定点数DSP算法时我习惯把所有的移位操作都加上注释标明是逻辑移位还是算术移位以及预期的数值变化。这样后面review代码或者调试时能快速定位问题。2.3 移位运算在乘除法中的桥梁作用移位运算不只是独立的操作它还是理解乘除法的基础。定点数乘法本质上就是“移位加法”的组合除法是“移位减法”的组合。后面讲原码和补码乘除时你会反复看到移位的影子。举个简单的例子用移位和加法实现乘以5x * 5 x * 4 x (x 2) x。这在编译器优化里很常见因为移位和加法的指令周期通常比乘法短。理解了这个你就能明白为什么定点数乘法的硬件实现里移位器是核心组件之一。3. 原码加减法直观但不够优雅3.1 原码表示法的特点回顾原码是最符合人类直觉的表示方式最高位是符号位0正1负其余位是绝对值。比如8位原码00000101表示 510000101表示 -5。原码的优点是直观转换方便。但做加减法的时候就很麻烦——你得先判断两个数的符号再决定是做加法还是减法还要判断绝对值谁大谁小来确定结果的符号。这套流程用硬件实现的话电路复杂、速度慢。具体来说原码加法要分四种情况情况操作同号相加绝对值相加符号不变异号相加绝对值相减符号取绝对值大的那个数的符号结果为0需要特殊处理避免出现 0 和 -0 两种零原码减法更麻烦得先把减法转成加法A - B A (-B)然后按照加法规则来。3.2 原码加减法的硬件实现困境为什么现代计算机不用原码做加减法核心原因就是电路太复杂。你想想每次做加法都要判断两个操作数的符号位比较两个数的绝对值大小根据判断结果选择做加法还是减法确定结果的符号这一套流程下来延迟大、面积大。而补码可以把加减法统一成加法硬件只需要一个加法器就够了。这就是为什么补码最终胜出的原因——不是因为它更直观而是因为它更高效。不过原码并没有完全退出历史舞台。浮点数的尾数部分就是用原码表示的因为浮点数做乘除时符号位单独处理尾数做无符号运算原码反而更方便。所以理解原码加减法对后面学浮点运算也有帮助。3.3 原码加减法的实操计算示例光说理论不够咱们手算一个例子。假设用8位原码表示计算(6) (-3)6 的原码00000110-3 的原码10000011异号相加比较绝对值6 3所以做减法 6 - 3 3结果的符号取绝对值大的数的符号即正号结果00000011即 3再算(3) (-6)3 的原码00000011-6 的原码10000110异号相加比较绝对值6 3做减法 6 - 3 3结果的符号取绝对值大的数的符号即负号结果10000011即 -3你看每次都要比较绝对值、判断符号步骤繁琐。如果让硬件每次都走这套流程加法器的设计会变得很复杂。这就是原码的硬伤。4. 补码加减法现代计算机的基石4.1 补码的核心思想与转换方法补码的设计思想可以用一句话概括用加法器统一实现加减法。在补码体系里减法A - B直接转成A (-B)而-B的补码可以通过“取反加一”得到。正数的补码等于原码。负数的补码是符号位为1数值位取反加一。比如 -5 的8位补码5 的原码00000101取反11111010加一11111011所以 -5 的补码是11111011。补码的一个重要性质是零只有一种表示。8位补码的零是00000000没有 0 和 -0 的区别。这让硬件设计简化了很多。另一个关键性质是符号位可以直接参与运算。你不需要单独处理符号位把它当成普通二进制位一起加就行。结果的符号位自然就是正确的。这个性质是补码最优雅的地方。4.2 补码加减法的运算规则与溢出判断补码加法的规则很简单两个补码直接相加符号位参与运算丢弃最高位的进位。减法转成加法A - B A (-B) A (B取反加一)。但这里有个必须解决的问题溢出。两个正数相加得到负数或者两个负数相加得到正数这就是溢出。溢出的本质是结果超出了补码能表示的范围。对于n位补码表示范围是-2^(n-1)到2^(n-1) - 1。比如8位补码的范围是 -128 到 127。如果两个正数相加超过127就溢出了。溢出判断有三种常用方法方法一符号位判断法。两个操作数符号相同结果符号与操作数符号不同则溢出。用逻辑表达式表示设符号位分别为 A、B、S溢出 A·B·S A·B·S。方法二进位判断法。最高位符号位的进位和次高位的进位不同则溢出。即C_out XOR C_in。方法三双符号位法。用两位表示符号位00表示正11表示负。运算后如果两位符号位是01或10则溢出。这三种方法各有优劣。符号位判断法需要额外的逻辑门进位判断法在加法器里天然就有进位信号双符号位法需要额外的存储位。实际硬件设计里进位判断法用得比较多因为它复用了加法器的进位链。4.3 补码加减法的完整计算流程咱们用一个具体例子走一遍完整流程。用8位补码计算(100) (50)100 的补码0110010050 的补码00110010相加01100100 00110010 ---------- 10010110结果是10010110符号位是1表示负数。但两个正数相加得到负数显然溢出了。用进位判断法验证最高位进位是0次高位进位是1两者不同确认溢出。再看一个不溢出的例子(30) (-20)30 的补码00011110-20 的补码11101100相加00011110 11101100 ---------- 00001010结果是00001010即 10。正确。注意事项做补码加法时最高位的进位要丢弃不要把它当成结果的一部分。这个进位只是用来判断溢出的信号。5. 原码乘除法符号与数值分开处理5.1 原码乘法的运算步骤原码乘法的思路很直接符号位单独处理数值位做无符号乘法。结果的符号是两个操作数符号的异或数值部分是绝对值相乘。具体步骤提取符号位S S_A XOR S_B取绝对值|A|和|B|计算|A| × |B|用无符号乘法组合结果符号位 数值位无符号乘法的硬件实现通常用移位加法器。比如计算1101 × 1011就是根据乘数的每一位决定是否加上被乘数的移位版本然后累加。手算示例(-3) × (4)用8位原码-3 的原码100000114 的原码00000100符号位1 XOR 0 1负数值位3 × 4 12二进制0001100结果10001100即 -12原码乘法的优点是逻辑清晰缺点是符号和数值分开处理硬件上需要额外的逻辑。而且原码乘法不能像补码那样直接对符号位运算。5.2 原码除法的运算步骤与注意事项原码除法同样把符号和数值分开。符号位异或得到结果符号数值部分做无符号除法。无符号除法的硬件实现通常用移位减法器也叫恢复余数法或不恢复余数法。恢复余数法的流程被除数左移一位减去除数如果结果为正商上1如果为负商上0并恢复余数加回除数重复直到达到所需精度不恢复余数法加减交替法优化了这个流程不需要每次都恢复余数根据余数的符号决定下一步是加还是减。手算示例(-12) ÷ (4)用8位原码-12 的原码100011004 的原码00000100符号位1 XOR 0 1负数值位12 ÷ 4 3二进制0000011结果10000011即 -3实操心得原码除法最容易出错的地方是余数的符号处理。在恢复余数法中如果减完得到负数必须把除数加回去恢复余数否则下一步就错了。这个细节在考试和实际实现中都容易翻车。5.3 原码乘除法在浮点运算中的应用虽然定点数运算里补码是主流但原码乘除法在浮点数运算中反而更常见。浮点数的符号位和尾数是分开存储的做乘除时符号位单独异或尾数做无符号乘除。这正好对应原码乘除法的思路。所以学原码乘除法不是白学它是理解浮点运算的基础。IEEE 754浮点数的尾数乘法本质上就是原码乘法的数值部分。6. 补码乘除法硬件友好的选择6.1 补码乘法的实现方法补码乘法比原码乘法复杂一些因为符号位不能简单地单独处理。补码乘法的经典算法是Booth算法。Booth算法的核心思想是把乘数中连续的1串转换成“加一次、减一次”的操作减少加法次数。具体规则是根据乘数相邻两位的差值决定操作乘数位对操作00不加01加被乘数10减被乘数11不加Booth算法天然支持补码因为补码的符号位可以参与运算。它还有一个优点对于乘数中有大量连续1的情况加法次数会减少。手算示例用Booth算法计算(3) × (-2)4位补码3 的补码0011-2 的补码1110在乘数末尾补一位011100从右往左看两位乘数: 1 1 1 0 0 位对: 11 11 10 00 操作: 无 无 减 无初始部分积0000遇到10部分积减被乘数0011即加1101得1101算术右移1110继续处理最终结果需要根据位数截取Booth算法的完整流程需要多轮移位和加减这里不展开每一步但核心逻辑就是上面这些。6.2 补码除法的实现方法补码除法同样可以用加减交替法但需要处理符号位。基本思路是判断被除数和除数的符号如果同号做减法如果异号做加法根据余数的符号决定商位重复直到精度足够补码除法的商和余数都自动带符号不需要像原码那样单独处理。但要注意补码除法的舍入方式可能和原码不同需要根据具体需求选择。6.3 补码乘除法的溢出与精度问题补码乘法溢出判断两个n位数相乘结果是2n位。如果只保留n位就可能溢出。判断方法是看结果的高n位是否全是符号位的扩展。如果是说明没溢出否则溢出。补码除法的精度问题更微妙。整数除法会截断小数部分补码除法的截断方向是向负无穷取整。比如-7 / 2在补码除法里结果是 -4 而不是 -3。这个行为在写代码时需要特别注意因为不同语言的除法舍入规则可能不同。注意事项做补码除法时如果被除数是负数余数的符号和商的关系需要仔细验证。很多教材上的例子只给正数负数的情况容易出错。7. 溢出判断定点数运算的安全阀7.1 溢出的本质与检测需求溢出的本质是运算结果超出了定点数能表示的范围。对于n位补码范围是[-2^(n-1), 2^(n-1)-1]。一旦超出结果就会回绕正数变负数负数变正数。为什么必须检测溢出因为溢出后的结果在数学上是错的但硬件不会自动报错。如果不检测程序就会带着错误的结果继续跑最终可能导致严重bug。在嵌入式系统、航天软件、金融计算这些领域溢出检测是必须的。7.2 三种溢出判断方法的对比与选择前面提到了三种方法这里做个详细对比方法原理优点缺点适用场景符号位判断同号相加结果异号逻辑简单需要额外逻辑门教学演示进位判断最高位进位≠次高位进位复用加法器进位链需要访问内部进位硬件设计双符号位两位符号位不一致直观易判断需要额外存储位考试常用实际硬件设计里进位判断法最经济因为它不需要额外的逻辑门只需要把加法器内部的进位信号引出来做异或。双符号位法在教材里常见因为它容易用手算验证。7.3 溢出处理策略与工程实践检测到溢出之后怎么办这取决于应用场景饱和运算结果钳位到最大值或最小值。DSP和图像处理常用这种方式因为饱和后的结果比回绕更合理。异常抛出触发硬件异常由软件处理。通用CPU通常这么做。忽略溢出某些场景下溢出是预期行为比如哈希计算、加密算法。在写代码时如果你不确定会不会溢出可以用更大的数据类型做中间计算最后再检查是否超出目标类型的范围。比如两个32位数相加先用64位算再判断结果是否在32位范围内。实操心得在做定点数DSP算法时我习惯在关键步骤加溢出检查用饱和运算代替回绕。虽然多几个指令周期但能避免很多隐蔽的bug。特别是做音频处理时回绕会产生刺耳的噪声饱和运算则只是削波听感上好很多。8. 定点数运算的工程应用与避坑指南8.1 嵌入式开发中的定点数实践嵌入式处理器很多没有浮点单元或者浮点运算很慢。这时候定点数就是唯一选择。常见的做法是把小数乘以一个缩放因子转成整数运算最后再除回来。比如做传感器数据处理温度值范围是 -40.0 到 125.0精度0.1。可以用16位定点数表示实际存储值 温度 × 10范围 -400 到 1250完全在16位有符号整数范围内。这种做法的关键是缩放因子的选择。缩放因子太大容易溢出太小精度不够。需要根据数据的实际范围和精度要求来权衡。8.2 常见错误与调试技巧定点数运算的常见错误移位方向搞反左移是乘右移是除别弄反了。算术移位和逻辑移位混用负数右移必须用算术移位。溢出没检测结果回绕后完全错误但很难发现。舍入方式不一致不同步骤用了不同的舍入方式累积误差。符号扩展错误把短位宽的数扩展到位宽更长的数时负数要补1而不是补0。调试技巧用小的测试用例覆盖边界情况比如最大值、最小值、零、正负交替。打印中间结果和手算结果对比。如果条件允许用Python或MATLAB先验证算法再移植到目标平台。8.3 从定点数到浮点数的过渡建议如果你的项目一开始用定点数后来发现精度不够或者范围不够想转浮点数有几点建议先评估是否真的需要浮点数。很多场景下增加定点数的位宽就能解决问题。如果处理器有FPU浮点运算的性能损失可能不大。如果必须用软件浮点性能会下降很多需要重新评估实时性。混合使用定点数和浮点数时注意转换过程中的精度损失。定点数运算是计算机算术的根基理解它不只是为了考试更是为了在底层开发时能写出正确、高效的代码。移位、补码、溢出判断这些概念会伴随你的整个技术生涯。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进