原码、补码以及正数/负数的左移和右移

一 、原码和补码

对数据用n位二进制数编码后,机器数X表示为: X = X n X n − 1 X n − 2 . . . X 2 X 1 X=X_nX_{n-1}X_{n-2}...X_{2}X_{1} X=XnXn1Xn2...X2X1

1.1 原码表示法

1)原码的定义:一个数的原码表示由符号位直接后跟数值位构成,因此,也称“符号-数值 (sign and magnitude)“表示法。原码表示法中,正数和负数的编码表示仅符号位不同,数值部分完全相同。

2)原码编码规则如下:

  • 当X为正数时, X n = 0 X_n=0 Xn=0,后面的位数表示数值。

  • 当X为负数时, X n = 1 X_n=1 Xn=1,后面的位数表示数值。

3)原码0有两种表示形式:

  • [ + 0 ] 原 = 000 … 0 [+0]_原 = 000…0 [+0]=0000

  • [ − 0 ] 原 = 100 … 0 [-0]_原=100…0 [0]=1000

4)原码表示范围(对于n位二进制编码): − ( 2 n − 1 − 1 ) -(2^{n-1}-1) (2n11) ( 2 n − 1 − 1 ) (2^{n-1}-1) (2n11)

1.2 补码表示法

1)补码的定义:正数的补码是它本身;负数的补码等于模与该负数绝对值之差。(对于n位二进制编码,它的模是 2 n 2^n 2n

2)补码0的表示形式:

  • [ + 0 ] 补 = [ − 0 ] 补 = 2 n + ( 士 0 ) = 100 … 0 = 00 … 0 ( m o d 2 n ) [+0]_补=[-0]_补=2^n+(士0)=100…0=00…0(mod 2^n) [+0]=[0]=2n+(0)=1000=000(mod2n)

从上述结果可知,补码0的表示是唯一的。这带来了以下两个方面的好处:一是减少了+0和-0之间的转换。二是少占用一个编码表示,使补码比原码能多表示一个最小负数。

3)求一个数的补码(在原码的基础上):

  • 对于正数,符号位取0,数值部分不变。
  • 对于负数,符号位取1,对数值部分“各位取反,末尾加1”。

4)补码表示范围(对于n位二进制编码): − ( 2 n − 1 ) -(2^{n-1}) (2n1) ( 2 n − 1 − 1 ) (2^{n-1}-1) (2n11)


二、C++正数/负数的左移和右移

在机器中,数的二进制码的表示形式是补码。

2.1 正数的左移和右移

正数的移位比较简单,左移是在二进制数的右边补0,右移是在二进制数的左边补0(因为符号位是0)。

#include <iostream>
using namespace std;
int main() {
	int a = 3;   // 0000 0000 0000 0000 0000 0000 0000 0011
	cout << (a << 1) << endl;  // 0000 0000 0000 0000 0000 0000 0000 0110:6
	cout << (a >> 1) << endl;  // 0000 0000 0000 0000 0000 0000 0000 0001:1
	return 0;
}

2.2 负数的左移和右移

1)负数的左移:左移是在二进制数的右边补0。一个负数在左移的过程中会出现有正有负的情况,因为最高的符号位可能变成0,所以要清楚负数左移不会特殊处理符号位。如果一直左移,最终会变成0。

#include <iostream>
using namespace std;
int main() {
	int a = -3;  // 1111 1111 1111 1111 1111 1111 1111 1101
	cout << (a << 1) << endl;  // 1111 1111 1111 1111 1111 1111 1111 1010:-6
	cout << (a << 30) << endl;  // 0100 0000 0000 0000 0000 0000 0000 0000:2^30=1073741824
	cout << ((a << 30) << 2) << endl;  // 0000 0000 0000 0000 0000 0000 0000 0000:0
	return 0;
}

2)负数的右移:右移是在二进制数的左边补1(因为符号位是1)。如果一直右移,最终会变成-1,即二进制数变成全1,而全1在补码中就表示为-1。并且后面不管继续右移多少位,结果还是-1。

#include <iostream>
using namespace std;
int main() {
	int a = -3;  // 1111 1111 1111 1111 1111 1111 1111 1101
	cout << (a >> 1) << endl;  // 1111 1111 1111 1111 1111 1111 1111 1110:-2
	cout << (a >> 2) << endl;  // 1111 1111 1111 1111 1111 1111 1111 1111:-1
	cout << (a >> 3) << endl;  // 1111 1111 1111 1111 1111 1111 1111 1111:-1
	return 0;
}

2.3 扩展(对正负数都适用,下面以正数为例)

1)移位里一个比较特殊的情况是当移位的位数等于或超过该数值类型的最大位数时,编译器会用移位的位数去模该类型的最大位数,然后按余数进行移位,如:

#include <iostream>
using namespace std;
int main() {
	// 这里int类型是32位
	int a = 3;
	cout << (a << 33) << endl;  // 33 % 32 = 1,左移1位,a变成6
	cout << (a >> 33) << endl;  // 33 % 32 = 1,右移1位,a变成1
	return 0;
}

2)另外注意,如果移位的位数没有等于或超过该数值类型的最大位数,即使移位相同的位数(通过几次移位),结果也是不一样的,如:

#include <iostream>
using namespace std;
int main() {
	int a = 3;
	a = a << 31;
	cout << (a << 2) << endl;  // 结果为:0
	return 0;
}

这里和1)中一样总共左移了33位,但结果是0,而不是6,要注意这一点。

3)移位负数(即移位的位数是负数)

移位负数的计算是,用被移动数的数值类型的最大位数和该负数相加,再移动所得结果即可。(已测试过,不是用负数的数值类型的最大位数,而是用被移动数的)

例如:左移-31,因为a用int类型存储,而我这里int类型是32位存储,所以32+(-31)=1,即左移1位。

#include <iostream>
using namespace std;
int main() {
	int a = 1;
	cout << (a << -31) << endl;  // 32+(-31)=1,即左移1位。结果为:2
	return 0;
}

注:移位负数这里我查了一些资料并测试过,但还不能肯定是这样计算,如有错,望指正。

  • 10
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值