【位运算进阶之----按位取反(~)】(附补码,原码讲解)

今天我们来谈谈按位取反这件事。
简单来说,按位取反就是先将一个数写成其二进制表达形式,然后1变0,0变1。下面就让我们展开深入地讨论吧!
在这里插入图片描述



一、预备知识:

1. 原码:

定义:

✨原码是一种用二进制表示有符号整数的编码方式。其中,最高位表示符号位0为正1为负其余位是数值位,表示数值的绝对值。

  • 举个“栗子”来说:
    • 1的原码是:001 ;
    • -1的原码是:101 ;

✨我们可以看出,互为相反数的两个数的原码,除了第一位的符号位不一样以外,其余位都相同。(那么,可能有聪明的小伙伴们就想到了0,是不是0也有两种表达方式呢?果然聪明!在原码中,0-0的表示是不一样的。)

优缺点:

  • ❤️优点:简单直观,易于理解和计算。
  • 💔缺点:加减法运算复杂,需要考虑符号位的处理。-0多占了一个数的表达位置,导致原码也可表示的总数减少了1个。

✨那可能又有小伙伴要问了,一个数很多吗?哈哈,一个-0单单看起来不多,但是一群-0不就多了吗?(这就告诉了我们"人多力量大"的道理,一个人可以走得很快,但一群人才能走得更远)。另外,-0的存在还会带来一些其他的问题:比如1️⃣(0)+(-0)=?2️⃣ 0和-0哪个大?

😢怎么一个原码事这么多?烦呐!下面就让我们来看看更为合适的编码方式–补码。


2. 补码:

❤️计算机中存储数据时,通常采用补码编码方式。

定义:

✨补码也是一种用于表示有符号整数的编码方式。其中,最高位(最左侧位)是符号位0表示正数,1表示负数其余位是数值位

  • 对于正数,补码就是其二进制原码本身。(看来不讲原码还不能开补码),如:数值+3的补码就是0011。
  • 对于负数,补码是其对应正数的原码的各位取反,末位加一,符号位取1。

如:-3的补码可以通过三步得到:(因为+3的原码是0011)
1️⃣各位取反:1100
2️⃣末位加一:1101
3️⃣符号位取1:1101


优缺点:

  • ❤️优点:
    1️⃣加减法运算可以直接按位运算,无需特殊处理符号位。(相对原码来说更加方便)
    2️⃣解决了原码中存在的正零和负零的问题。(-0的位置让给了最小的数)
    3️⃣表示范围更加均衡。(补码的最小值的绝对值比最大值的绝对值多1)

  • 💔缺点:
    1️⃣加减法时可能溢出。
    2️⃣对称不明显。(看吧,上帝为你打开一扇窗的同时,也会为你关闭一扇门)

🐒当然,补码的知识宝库偌大无比,我现在只不过是揭开了其冰山一角,但对我们解决一般的问题来说,足够了。


二、按位取反:

✨将一个数的二进制表达的每一位取反,即0变为11变为0取反符号用 "~" 表示。(从最低位(最右侧位)开始,逐位进行取反操作。)

例如,对于一个8位的二进制数10101010,按位取反后的结果是01010101。(注意,这是补码的取反哦)

下面来看几个具体的代码:
1️⃣1的按位取反:

#include<stdio.h>

int main(void)
{
	int a = 0b1;
	printf("%d", ~a);
	return 0;
}

😄猜猜它的输出是多少? 输出0是不是?哈哈哈,是0你就上当了!
在这里插入图片描述
咦❓为什么❓为什么不是0❓ 1->0 没有问题啊❗️到底是哪里出错了❓会是哪里错了呢❓
在这里插入图片描述
阁下莫慌,我们慢慢来解释。

  1. 首先,a是一个int类型,一般是4个字节(Byte),而一个字节是8位(8个bit),所以总共是32位。0b1是1的二进制表达,具体可以写为:00000000 00000000 00000000 00000001.
  2. 下面我们对它进行取反操作,得:11111111 11111111 11111111 11111110,但它是一个补码,我们以%d的形式输出就要得到它的十进制表达(可以理解为真值),那么问题来了,补码怎么转换成真值呢?考虑到具体的定义过于枯燥乏味,所以我直接告诉你转换的方法:可以先将补码转换为机器数,而计算机中的机器数一般用原码来表示,所以问题就转化为了补码->原码
  3. 将补码的符号位不变,其余各位取反,末位加一。(这好像和原码转补码有点类似啊!)也就是:10000000 00000000 00000000 00000001+1---> 10000000 00000000 00000000 00000010,再转换成十进制整数就是-2。

2️⃣0的按位取反:(%d的形式输出)

#include<stdio.h>

int main(void)
{
	printf("%d", ~0);
	return 0;
}

在这里插入图片描述

2️⃣0的按位取反:(%u的形式输出)

#include<stdio.h>

int main(void)
{
	printf("%u\n", ~0);
	printf("%lld", ((long long)1 << 32) - 1);
	return 0;
}

在这里插入图片描述
咦❓为啥同样是0,但取反后的输出结果不一样呢?

  • %d:有符号十进制整数的输出 (signed int): -2 ^ 31 -- 2 ^ 31 - 1
  • %u:无符号十进制整数的输出 (unsigned int) : 0 -- 2 ^ 32 - 1

这个有无符号具体就体现在是否有符号位,有符号位就少一位数值位,没有符号位就都是数值位。

  • 0的补码:00000000 00000000 00000000 00000000
  • ~0的补码:11111111 11111111 11111111 11111111

✨对于有符号整数来说,有正有负,且第一位是符号位。~0第一位是1,所以要转换成原码再输出:
10000000 00000000 00000000 00000001,其值为-1;
✨对于无符号整数来说,没有符号位,32个位都是数值位,所以可以直接计算。因此,两者输出不同。


三、拓展应用:

1. 巧求相反数:(不用-

✨相反数相必大家都知道,1的相反数是-1,0的相反数是0,简单来说一个数的相反数就是再其前面加个-

那么,不用这种方法还可以求相反数吗?

哈哈😄,其实这个问题我们上面已经说过了,直接利用补码的定义,求一个数的相反数,就是各位取反,末位加一。即-x=~x+1

#include<stdio.h>
int main(void)
{
	int x = -18;
	printf("%d的相反数是%d\n",x, ~x + 1);

	int y = 18;
	printf("%d的相反数是%d",y, ~y + 1);
	return 0;
}

在这里插入图片描述
(当然了,要注意数据的溢出问题。)

思考:既然一个数x的相反数可以通过~x+1来得到,记为y

  • 则y=~x+1 -> ~(y-1)=x,所以,求x的相反数也可以用 ~(x-1)
#include<stdio.h>
int main(void)
{
	int x = 18;
	printf("%d的相反数是%d\n", x, ~(x-1));

	int y = 18;
	printf("%d的相反数是%d", y, ~y + 1);
	return 0;
}

显然输出也是正确的:
在这里插入图片描述


2. 代替减法(不用-实现减法)

✨现给定int类型的两个正数,不用-如何实现减法操作?
根据减法定义,减去一个数就等于加上这个数的相反数;
所以a - b = a + (-b) = a + (~b + 1);

#include<stdio.h>
int main(void)
{
	int a=1,b=2;
	printf("%d-%d=%d\n",a, b,a+~b+1);
	return 0;
}

在这里插入图片描述


3. 代替加法(不用+实现加法)

✨给定两个int类型的正数,不用+实现加法。根据加法定义,加上一个数等于减去一个数的相反数。即:a + b = a - (-b) = a - (~b +1)=a - ~b - 1;

#include<stdio.h>
int main(void)
{
	int a=1,b=2;
	printf("%d+%d=%d\n",a, b,a-(~b + 1));
	return 0;
}

在这里插入图片描述


好了,今天的讲解就到这里了,相信你也是收获满满吧!
终于肝完了,好累!!!

在这里插入图片描述

二进制补码表示法是计算机科学中一种高效的数值表示方式,特别是在执行加法和减法运算时。在开始之前,推荐您阅读《补码加减法运算详解及实例》,这本资料详细讲解补码加减法运算的原理及其在计算机组成原理中的应用。 参考资源链接:[补码加减法运算详解及实例](https://wenku.csdn.net/doc/780dcsqzac?spm=1055.2569.3001.10343) 首先,定点整数的加法运算可以直接通过补码来完成。例如,有两个定点整数x和y,其补码分别为X补和Y补,那么它们的和S补可以通过简单的二进制加法得出: S补 = X补 + Y补 而减法运算可以转化为加法运算来处理。具体来说,如果我们需要计算x-y,可以等价于x加上y的补码,即x + (-y)补码。这里,(-y)补码可以通过对y的原码除符号位外的所有位取反后再加1来获得。举个例子,如果y的原码为10011(表示-19),那么(-y)补码就是01101。 溢出检测通常是通过结果的符号位和进位标志来完成的。在加法运算中,如果两个正数相加结果为负数,或者两个负数相加结果为正数,那么表明发生了溢出。同样,在减法运算中,如果减数和被减数符号位不同,并且运算结果的符号位与预期不符,也可以判断为溢出。 处理溢出的方法依系统而异。一些常见的处理方式包括截断溢出部分,设置溢出标志位,或者在某些系统中,使用模运算确保结果仍然有效。了解这些细节对于编写可靠和高效的程序至关重要。 综上所述,补码加减法运算是计算机组成原理中的基础知识点,正确理解和应用这些概念对于深入理解计算机内部的运算过程和设计高效的算法非常重要。在你掌握这些基础概念后,可以进一步探索《补码加减法运算详解及实例》中的进阶内容,从而获得更全面和深入的理解。 参考资源链接:[补码加减法运算详解及实例](https://wenku.csdn.net/doc/780dcsqzac?spm=1055.2569.3001.10343)
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

追逐远方的梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值