原码、反码、补码

本文深入探讨了机器数表示中的原码、反码和补码的概念,解释了补码能够解决正负零和正负数相加问题的原因,并详细解析了补码的计算原理及其在二进制加减法中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:每次声明整形变量时都会思考一个问题,为什么最大值和最小值不是关于“0”点对称的两个数,比如在java中byte类型的最大值是127,最小值是-128。其实这个问题并不难,只要知道机器数的存储方式就能找出答案,但是因为这个问题引发了我之后的一系列思考……

 

现在就从最初的问题开始,记录下我思考的过程。

一、机器数

机器数是将符号"数字化"的数,是数字在计算机中的二进制表示形式。机器数有2个特点:一是符号数字化,二是其数的大小受机器字长的限制。机器数主要有三种表现形式:原码、反码、补码。其中又以补码最为重要,以我目前的理解在现代计算机内存中实质上只有一种形式的机器数——补码。但从原码到补码是一个过程,我将进行完整的介绍。

1、原码

将数的真值形式中“+”号用“0”表示,“-”号用“1”表示时,叫做数的原码形式,简称原码。比如十进制127在Java中byte类型的原码表示为0111 1111,其中首位“0”表示符号为正,后7位表示数值(111 1111转换成十进制就是127),由此我们会想到最小值的原码形式1111 1111,只要将符号取反就可以了,最小值是-127。

2、反码

对正数来说,其反码和原码的形式相同;对负数来说,反码为其原码的数值部分各位取反。正数的反码自不必说,反码与原码相同,保持不变即可,对于负数也并不难保持符号位不变,数值部分取反即可,比如-125的原码表示形式为:1111 1101,转换为反码形式就是:1000 0010。

3、补码

整数的补码就是其本身,负数补码的最简单计算方式是将反码加1,注意这里所说的是计算方式,其含义不可这么简单的理解。按照这样的规则,我们的-125用补码形式表示在反码1000 0010的基础上加1,就是1000 0011。

 

至此我们已经可以回答最初关于最值的问题了,byte类型在java中占一个字节(8bit),最大值的补码形式(同原码)为0111 1111(127),显然使用8位的原码(含符号位)无法表示-128,也就无法用上述的方法计算补码,至于补码为什么可以就是下一个问题了,我们暂时使用递推的方法找出-128的补码表示方式:

十进制

原码

反码

补码

-126

1111 1110

1000 0001

1000 0010

-127

1111 1111

1000 0000

1000 0001

-128

N/a

N/a

1000 0000

可以看到当补码仅有符号位时就是byte的最小值。

 

二、原码、反码、补码存在的“真相”

所谓“真相”只是我眼前能看到的真相,是我目前能力和认知所及的“真相”,其实这三种机器数的表示形式由来已久,有很多成熟的计算法则,在这里我要介绍的是这三种码的有缺点以及他们存在的意义。

1、原码

这应该是机器数最简单的表示方法了,只要了解二进制和十进制的转换,就能很容易的看出其所表示的数值,这一点也许就是它唯一的优点——直观,至于缺点主要有以下两个方面:

我们前面讲到8位(含符号位)的原码无法表示出-128,而补码却可以,究其原因就是因为原码中存在着两个“0”,正“0”(原码:0000 0000)和负“0”(原码:1000 0000),在补码中我们舍弃了负“0”,使它可以表示一个更有意义的值(最小值),在这里只要知道是负“零”让出了位置就可以了,至于补码表示的原理会在后面讲到。

另外一个缺点是无法运算,在两个加数都为正数或都为负数时没有问题(符号位不参加运算),比如:

8(原码:0000 1000)+7(原码:0000 0111)=15(原码:0000 1111)

-5(原码:1000 0101)+(-6(原码:1000 0110 ))=-11(原码:1000 1011)

但当两个加数一个为正,一个为负时就会出现错误,比如:

8(原码:0000 1000)+(原码:-5(1000 0101))=?

我们通过原码无法计算出上面式子的正确结果,当然我们可以把“8+(-5)”简化成“8-5”,这是“8”和“5”都是两个正数了,但新的问题是我们需要设计一种运算减法的电路。

2、反码

反码和原码的缺点是一致的,正负“0”的问题一样没有得到解决,至于正负数相加的问题,只在两个加数绝对值相等(-2+2、-3+3……)的情况下会得到正确的结果,所以我现在还没有理解它存在的意义,是不是需要一个补偿数才能使加法具有通用性?网上找了很久没有找到答案,暂时把他当作原码到补码简便计算的中间步骤。

3、补码

补码同时解决了正负“零”、正负数相加的问题(符号位参加运算),正负数可以相加就意味着减法可以转换成加法进行运算,比如:

8-5=>8(补码:0000 1000)+(-5(1111 1011))=3(0000 0011)

-5-4=>(-5(1111 1011))+(-4(1111 1100))=-9(1111 0111)

因正数的补码就是其本身,而原码在正数范围内不含符号位的加法运算之前已经验证过了,两个数的符号位“0”在加法不溢出的情况下不可能变成“1”,所以其结果必然是准确的,这里就不再举例子了。

我们可以看到补码为二进制的加、减法运算找到了一种统一的解决方式,可以轻易的将减法转变为加法,这也就意味着计算机中只需要一种物理电路来实现加法运算即可。

三、补码的原理

之前我们将原码取反加“1”计算补码的方式是一种便捷计算方法,从中我们不容易看出其本质,其实因为我们是用二进制数来表示所以称之为“补码”,推广到更一般的数学含义应该叫做“补数”,更一般的也就是说我们生活中“十进制”计数法中一样存在着补数概念。数位所能表示数字的个数称之为模,比如:一位十进制数可以表示“0-9”,共计10个数,所以一位十进制数的模就是“10”,两位十进制数可以表示“0-99”,共计100个数,所以一位十进制数的模就是“100”,正数的补数是其本身,负数加上模即可求得其补数,我们以两位十进制数举例:

两位十进制数模(mod)=100

-29+71(mod=100)

-32+68(mod=100)

-51+49(mod=100)

当我们进行计算时

①70-29=>70+(-29)=>70+71=141

②87-32=>87+(-32)=87+68=155

③-29-32=>71+68=139

④-51-32=>49+68=117

上面的式子看上去都是错误的,都不是我们预取的结果,但是当我们把①②两个式子减去其模(去掉溢出部分):

①-mod=141-100=41

②-mod=155-100=55

这就变成了我们所期望的结果,对于③④两个式子,我们同样减去其模(去掉溢出部分):

③-mod=139-100=39

④-mod=117-100=17

但这看上去仍然不是我们要的结果,其实已经是了,只不过这个结果是补数形式,①②中的结果其实也是补数形式,只不过我们知道正数的补数是其本身,所以无需再做转换,③④两个式子的结果显然是负数,所以我们需要用取补数的逆运算将其转换回去,用补数减去其模:

(③-mod)-mod=(139-100)-100=-61

(④-mod)-mod=(117-100)-100=-83

上面两个式子第一次减去模去掉溢出部分得到补数,再减一次模转换成原数,最终我们也得到了预期的结果,这种规律放在时钟表盘上更加便于理解,时钟从1点-12点共可表示12个数,其模为12,我们从12点逆时针拨3个格子(-3),指针会停在9点的位置,也就是说等同于我们顺时针波动9个格子,-3-3+mod-3+129,虽然两者再最终的状态上是一致的,但两种拨动方式指针的实际行程正好相差了模(12)的值,所以在计算最后的结果时我们要减去模。我相信这不难理解,但是请原谅我无法用严谨的数学语言描述和证明。

最后我们回到二进制中,8位补码,去掉符号位剩余7位,可表示十进制000 0000(十进制:0)-111 1111(十进制:127),共计1000 0000(十进制:128)个数,其模为1000 0000,以原码1000 0101(十进制:-5)为例计算补码:

-000 0101+1000 0000= 111 1011

所以原码1000 0101对应的补码就是1111 1011,上面的式子稍加改变:

-000 0101+(111 1111+1)

将“1000 0000”拆分成“111 1111+1”的形式,“111 1111-000 0101”这就是取反操作,在加上剩下的“1”就是我们之前所说的便捷运算。

同样的,二进制的加法运算中也要减去模,只不过在寄存器中已经被“合理溢出“了,所谓的合理的溢出是指实际计算结果没有超过最大(小)值的范围,在补码运算时产生的溢出,在合理的取值范围内我们考虑如下几种补码运算情况:

正数和正数相加

负数和负数相加

正数和负数相加

第一种情况相当于原码的加法计算,因为正数的补码就是源码,不存在“合理溢出”,以八位补码为例,两个正数相加一定不能超过0111 1111(十进制:127),一旦超过这个值就会向首位(符号位)进位产生一个负数,两个正数相加当然不可能产生负数,这也就是我们在编程中要合理选择数据类型的原因。

第二种情况后7位必须要有一次“合理溢出”,否则就是真的溢出了,以八位补码为例,负数的取值范围是1111 1111(十进制:-1)-1000 0000(十进制:-128),两个边界相加(-1-128)就是刚好溢出,因为符号为变为“0”,计算结果变成了一个正数,两个负数相加当然不可能产生正数。在其结果不溢出的情况下,后7位的计算中一定会产生一个“合理溢出”向符号位进位,让其依旧为“1”,比如我们用1111 1111(十进制:-1)加上1000 0001(十进制:-127)得到1 1000 0000,舍去首位溢出的“1”,刚好是byte的最小值 1000 0000(十进制:-128),后7位产生了“合理溢出”。

第三种情况相对复杂一些,正数和负数相加其结果可以是正数、负数、0,但只要是一个合理的正数与一个合理的负数相加一定不会产生真正的溢出,以八位补码为例:

0000 1000(十进制:8)+1111 1000(十进制:-8)=1 0000 0000

舍去首位的溢出,刚好为0,我们可以试着改变两个加数的值,最终我们会发现后7位溢出或不溢出都是有其合理性的,恰好表示了结果的正负。

 

 

 

 

 

 

 

### 原码反码补码的概念及区别 在计算机系统中,数值的表示和运算依赖于原码反码补码这三种编码形式。它们的核心区别在于对负数的表示方式不同,并且在加减法运算中的处理逻辑也有所差异。 #### 原码 原码是最直观的二进制表示方法,其中最高位为符号位(0 表示正数,1 表示负数),其余位表示数值的绝对值。例如: - +1 的 8 位原码为 `00000001` - -1 的 8 位原码为 `10000001` 原码的优点是表示直观,但缺点是在进行加减运算时需要额外判断符号位,导致计算复杂度较高 [^1]。 #### 反码 反码是对原码的改进形式,主要用于简化补码的生成或解析过程: - 正数的反码原码相同。 - 负数的反码为符号位保持不变,其余位逐位取反(0 变 1,1 变 0)。 例如: - +1 的反码为 `00000001` - -1 的反码为 `11111110` 需要注意的是,在 8 位系统中,+0 和 -0 的反码分别为 `00000000` 和 `11111111`,这会导致两个不同的编码表示同一个数值 [^1]。 #### 补码 补码是现代计算机中最常用的数值表示方式,它解决了原码反码中存在的多个问题,尤其是简化了加减法运算的实现: - 正数的补码原码相同。 - 负数的补码反码加 1。 例如: - +1 的补码为 `00000001` - -1 的补码为 `11111111` 补码的一个重要特性是其可以表示一个比原码范围更广的数值。例如,在 8 位系统中,原码的表示范围为 -127 到 +127,而补码的表示范围为 -128 到 +127。其中 `-128` 的补码为 `10000000`,这个值没有对应的原码表示 [^2]。 #### 计算方法总结 | 类型 | 正数 | 负数 | |--------|--------------------------|--------------------------------------| | 原码 | 符号位为 0,其余为数值本身 | 符号位为 1,其余为数值的绝对值 | | 反码 | 与原码相同 | 原码符号位不变,其余位取反 | | 补码 | 与原码相同 | 反码加 1 | #### 在计算机底层的应用 补码被广泛用于计算机的底层数值存储和运算,主要原因如下: 1. **统一加减运算**:使用补码可以将减法转换为加法,从而简化硬件设计。 2. **唯一零表示**:在补码系统中,0 的表示是唯一的(全 0)。 3. **溢出处理**:补码支持模运算,因此可以自然地处理溢出情况。 例如,在 Java 中,当整数类型发生截断时,结果会自动以补码形式解释。以下代码展示了如何通过强制类型转换截断高位,得到补码表示的数值: ```java public class Main { public static void main(String[] args) { int a = 300; // 00000000 00000000 00000001 00101100 byte b = (byte)a; // 00101100 -> 44 int c = 200; // 00000000 00000000 00000000 11001000 byte d = (byte)c; // 11001000 -> -56 System.out.println(b); // 输出 44 System.out.println(d); // 输出 -56 } } ``` 上述代码表明,当高位被截断后,低位部分按照补码规则重新解释为有符号整数 [^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值