补码运算与整数溢出(上

目录

第一段:补码运算、整数溢出

[1. 无符号 unsigned 整数](#1. 无符号 unsigned 整数)

[2. 有符号 signed int](#2. 有符号 signed int)

[3. 硬件层面溢出判断](#3. 硬件层面溢出判断)

原码、反码、补码换算规则

[1. 正数(符号位 = 0)](#1. 正数(符号位 = 0))

[2. 负数(符号位 = 1)](#2. 负数(符号位 = 1))

[3. 0 的表示](#3. 0 的表示)

补码加减法实例,进位与舍弃

[示例 1:计算 5 - 3,等价 5 + (-3)](#示例 1:计算 5 - 3,等价 5 + (-3))

[示例 2:计算 3 - 5,等价 3 + (-5)](#示例 2:计算 3 - 5,等价 3 + (-5))

[溢出判断(4 位例子)](#溢出判断(4 位例子))

符号扩展(位扩展)

[1. 有符号数 signed(符号扩展)](#1. 有符号数 signed(符号扩展))

[2. 无符号数 unsigned(零扩展)](#2. 无符号数 unsigned(零扩展))

[3. C 语言代码现象](#3. C 语言代码现象)


补码运算、整数溢出

计算机 CPU 内部没有独立减法硬件电路。 减法的硬件实现公式:a - b = a + (-b的补码),加减法全部复用加法器电路。

1. 无符号 unsigned 整数

无符号数运算超出范围,遵循模运算

C 标准行为是确定的 。 以 32 位unsigned int举例:

取值范围

最大值+1,溢出回卷,结果直接变成 0。

c

cpp 复制代码
#include <cstdio>
int main(void)
{
    unsigned int u = 4294967295U;
    u = u + 1;
    printf("%u\n", u); //输出 0
    return 0;
}

2. 有符号 signed int

32 位 int 范围:,即-2147483648 ~ 2147483647

C 语言标准规定,有符号整数溢出属于未定义行为。 编译器有权优化,不一定一定看到翻转负数;VS 调试运行会观察到溢出翻转。

c

cpp 复制代码
#include <cstdio>
int main(void)
{
    int a = 2147483647;
    a = a + 1;
    printf("%d\n", a); //VS下变成负数
    return 0;
}

3. 硬件层面溢出判断

  • 两个正数相加,结果变成负数 → 正溢出
  • 两个负数相加,结果变成正数 → 负溢出

重要结论:一正一负两个数相加,永远不会溢出

对比表格

表格

类型 溢出行为 C 语言标准定义
unsigned 溢出模\(2^N\)自动回卷 行为确定
signed int 结果不确定,编译器可优化 未定义行为

做计组硬件计算题,按补码规则直接推演溢出;写 C 代码时,不能依赖有符号数溢出的结果。


思考

  1. 32 位unsigned int = 4294967295,再加 1 结果是多少?
  2. C 语言标准中,有符号 int 溢出,行为是否确定?
  3. 正数和负数相加,会不会发生溢出?

1.0

2.不确定

3.不会

:

  1. ✔ 32 位 unsigned 最大值 + 1,模,结果为 0
  2. ✔ C 标准:有符号 int 溢出属于未定义行为,行为不确定
  3. ✔ 一正一负相加,不可能溢出(硬件层面,结果一定落在表示范围内)

简要

  • unsigned 溢出:标准明确定义,模运算回卷,可写代码验证
  • signed 溢出:写代码不要依赖溢出后的结果,编译器优化后行为会变;计组做题时,按补码二进制去判断溢出
  • 溢出判断口诀:同号相加才有可能溢出,异号相加不会溢出

原码、反码、补码换算规则

计算机存储和运算使用补码,但是做题时经常需要在真值、原码、反码、补码之间互相转换。下面以8 位二进制举例。

最高位是符号位:0 代表正数,1 代表负数;剩余比特是数值位。

1. 正数(符号位 = 0)

✅原码 = 反码 = 补码 例:+5,8 位 原码:0000 0101 反码:0000 0101 补码:0000 0101

2. 负数(符号位 = 1)

转换步骤:

  1. 写出真值绝对值的原码
  2. 保持符号位不变,数值位全部取反 → 反码
  3. 反码 + 1 → 补码

例:-5,8 位

  1. 绝对值 + 5 原码:0000 0101
  2. 加上负号原码:1000 0101
  3. 反码:1111 1010
  4. 补码:1111 1011

3. 0 的表示

  • 原码:存在+0(00000000)-0(10000000)两种表示
  • 补码:0 只有唯一编码00000000 多余的编码10000000用来表示额外负数,8 位补码范围:

实操代码

c

cpp 复制代码
#include <stdio.h>

void print_bin(char val)
{
    for(int i=7;i>=0;i--)
    {
        putchar( (val & (1<<i)) ? '1' : '0' );
    }
    putchar('\n');
}
int main(void)
{
    char a = -5;
    print_bin(a);
    return 0;
}

思考

  1. 8 位,+3的原码是多少?
  2. 8 位,-3的补码是多少?
  3. 8 位补码为什么可以表示到 - 128?

:

1.00000011

2.11111101

3.-128 -> 原码11111111->反码10000000->补码100000001 ?

:

  1. ✔ 8 位 + 3 原码:00000011
  2. ✔ 8 位 - 3 补码:11111101
  3. ❌这里出错了。

重点:-128 不存在 8 位原码和反码,不能用普通负数的转换方式推导。

原理

8 位一共 256 种编码。 0:00000000,占用 1 个编码。 剩下 255 个编码。正数:1~127(127 个);负数本应 127 个。 但是补码把10000000这个编码单独拿出来,规定代表 -128

  • 10000000 没有对应的原码、反码
  • 8 位补码范围:-128 ~ 127

你刚才写的推导:

原码 11111111→反码 10000000→补码 100000001 这是错误的,11111111是 - 1 的原码。

一句话:

8 位二进制编码 10000000,在补码里直接定义为 - 128,不能套用 "原码取反加 1" 的常规负数换算公式。


补码加减法实例,进位与舍弃

核心公式:减法,全部转换成加上减数负数的补码。 相加产生超出位数的最高进位,直接丢弃(硬件自动截断)。

下面用4 位补码演示(方便笔算) 4 位补码范围:-8 ~ +7

示例 1:计算 5 - 3,等价 5 + (-3)

  1. 5的补码 = 0101
  2. -3的补码 =1101

相加:

plaintext

复制代码
  0101
+ 1101
-------
1 0010

最高位产生进位 1,超出 4 位,直接丢掉。结果0010,真值 = 2 ✔

示例 2:计算 3 - 5,等价 3 + (-5)

1.3补码 = 0011

2.-5补码 = 1011

plaintext

复制代码
  0011
+ 1011
-------
 1110

无进位,结果1110,是 - 2 的补码 ✔

溢出判断(4 位例子)

4 位范围 - 8~7

例:

6补码 = 0110

3补码 = 0011

plaintext

复制代码
 0110
+0011
------
1001

两个正数相加,结果最高位为 1(负数)→ 发生正溢出,答案无效。

关键点区分: ✅最高位进位(进位输出丢弃)≠ 溢出 进位只是比特溢出到外面;溢出代表结果超出可表示范围,答案错误,二者不是一回事,考研高频坑!

实操代码(8 位 char 验证)

c

cpp 复制代码
#include <stdio.h>
void print_bin(char val)
{
    for(int i=7;i>=0;i--)
        putchar( val & (1<<i) ? '1':'0' );
    putchar('\n');
}
int main(void)
{
    char a = 5;
    char b = -3;
    char res = a + b;
    print_bin(a);
    print_bin(b);
    print_bin(res);
    return 0;
}

思考

  1. 补码减法\(A-B\),等价于什么?
  2. 4 位补码相加,产生超出 4 位的进位,硬件如何处理?
  3. 二进制相加出现最高进位,是否一定代表溢出?

1.A +(-B)

2.截断

3.不一定

  1. ✔ A-B = A+(-B)的补码,减法转为加上负数的补码
  2. ✔ 超出字长的进位,硬件直接截断丢弃
  3. ✔ 进位和溢出是两回事:进位是最高位往外多出的比特;溢出代表结果超出当前字长能表达的范围。有进位 ≠ 溢出

举个简短例子巩固: 4 位补码计算 5-3,相加后产生进位 1,直接丢弃,结果0010=2,有进位,但是没有溢出,结果正确


符号扩展(位扩展)

短的数据类型,赋值给更长的数据类型,高位如何填充,保证数值不变

比如:8 位 char → 32 位 int。分有符号 signed无符号 unsigned两套规则。

1. 有符号数 signed(符号扩展)

规则:高位全部填充符号位

  • 正数符号位 0:高位补 0
  • 负数符号位 1:高位补 1

示例:8 位 char -5,补码 11111011,扩展到 16 位 原 8 位:1111 1011 16 位结果:1111 1111 1111 1011 高位全部补上 1,数值仍然是 - 5 不变。

8 位正数+500000101 扩展 16 位:00000000 00000101,高位补 0。

2. 无符号数 unsigned(零扩展)

规则:不管正负,高位统一填充0 示例:unsigned char 0xAB(8 位10101011),扩展到 16 位 结果:00000000 10101011,高位全部补 0。

3. C 语言代码现象

c

cpp 复制代码
#include <stdio.h>
int main(void)
{
    char c = -5;
    int a = c;       //有符号,符号扩展,高位补1
    unsigned char uc = 0xAB;
    int b = uc;      //无符号,零扩展,高位补0
    printf("a = %d\n",a);
    printf("b = %d\n",b);
    return 0;
}

运行结果:

plaintext

复制代码
a = -5
b = 171

  1. 扩展目的:数值保持不变,仅仅增加二进制位数。
  2. 只有位数变长才存在扩展;位数截断是另外一回事。
  3. 第一步:先判断是 signed 还是 unsigned,再选择补 1 还是补 0。

思考

  1. 有符号负数做位扩展,高位补什么?
  2. unsigned 无符号数位扩展,高位补什么?
  3. 符号扩展的目标是什么?

1.1

2.0

3.增加二进制位数

  1. ✔ 有符号负数位扩展,高位补符号位1
  2. ✔ unsigned 无符号数,高位统一补0(零扩展)
  3. ❌ 不完全准确。 目标不是单纯增加位数,核心目标:扩展之后,数值大小保持不变,增加位数只是操作形式。

一句话总结: 符号扩展 / 零扩展:把短字长转为长字长,保证真值不变。 如果只是单纯加长位数但数值变了,这个扩展就是失败的。

相关推荐
词却2 小时前
OpenCV学习:人脸识别
人工智能·opencv·学习
xqqxqxxq2 小时前
AI Agent学习:第四章小结及八道思考题(李博杰《深入理解 AI Agent》第四章观后总结)
大数据·人工智能·学习
Willliam_william3 小时前
QEMU学习之路(13)— 在BusyBox中安装pciutils
学习
曹牧3 小时前
C#:模态对话框
开发语言·c#
咕白m6253 小时前
C# 如何限制 PDF 复制、打印权限?
c#·.net
小白的后端世界3 小时前
数据分析基础学习
人工智能·学习·数据分析
人工智能培训3 小时前
从“捏泥人”到“造世界”:3D生成式AI的技术跃迁与产业落地
大数据·人工智能·学习·生活·ai写作
yume_sibai3 小时前
正则表达式完全指南(基础语法 + 实战应用 + 性能优化 + 最佳实践)
开发语言·正则表达式·c#
鱼很腾apoc3 小时前
【Linux】第15期 讲解IP网络层核心原理与机制
linux·服务器·网络协议·学习·tcp/ip·ip·网络层