在计算机中是以二进制的形式存储的,即机器数其特色是:
原码:
相似于机器码,是有符号位的。
反码:
正数的反码等于其原码自己。
负数的反码等于其原码的符号位保持不变,其他位取反。
补码:
正数的补码=正数的反码=正数的原码
负数的补码=其反码+1
例如:8位的±3ide
有符号数 | 数值 | 原码 | 反码 | 补码 |
---|---|---|---|---|
正数 | +3 | 0000 0011 | 0000 0011 | 0000 0011 |
负数 | -3 | 1000 0011 | 1111 1100 | 1111 1101 |
对于计算机而言,加减乘除是最基本的运算,所以要设计的要尽可能的简单,若是让计算机来辩解符号位,就会增长设计的复杂度,所以工程师设计出了一个方法:让符号位也参加算术的运算, 好比:3-3设计为3 + (-3),于是对于机器而言只有加法没有减法运算;因而就要用到反码和补码。编码
数字 | 二进制 |
---|---|
127 | 0111 1111 |
126 | 0111 1110 |
... | ...... |
1 | 0000 0001 |
0 | 0000 0000 |
-1 | 1000 0001 |
... | ...... |
-127 | 1111 1111 |
-128 | 1000 000 |
floor(x/y) 为地板除法,即所得实数向下取整 或 向上取整-1 floor(2/3) = 0 or [0.667] - 1 = 1 - 1 = 0 floor(-2/3) = -1 or [-0.667] - 1 = 0 - 1 = -1 若是两个整数除以M所获得的余数相等,则称该两个整数对于mo模M是相等的。 如:5 % 3 = 2 ;8 % 3 = 2,则5 和8对于模3是相等的。 用数学公式表示为:5 ≡ 8 (mod 3) 正数同余数:很好理解 5 % 3 = 2 负数同余数:X % Y = X - Y * (floor(X / Y)) 如:-2 % 3 = -2 - 3 * floor(-2/3) = -2 - 3 * (-1) = 1 如何判断一个整数和一个负数是否同余数呢(假设分母为M)? 正数A = M - |负数B|,则异号的A和B对于M同余数。如-2和1对于3同余数。 同余数具备如下性质: 1. 反自身性: X ≡ X % M,即自身的余数与自身同余数 2. 线性性质: x1 ≡ y1 % M && x2 ≡ y2 % M,则 x1 ± x2 ≡ (y1 ± y2) % M x1 * x2 ≡ (y1 * y2) % M 反码:实际上是其一个同余数 如:1的反码:0000 0001->0000 0001 将其当作原码为:1 -1的反码:1000 0001->1111 1110 将其当作原码(去掉符号位):2^7 - 1 -1 = 126 则负数与其反码(去掉符号位对应的机器码)为同余数,即找到了负数的同余数 模为正数+|负数|,如126 + |-1| = 127 2 - 1 ≡ (2 + 126) % 127 在反码的基础上+1,是增长了模的大小,即增长了转一圈的大小, 所以用补码所能表示的范围为-128,128,因为0的存在,因此正数往前移动一个单位 即补码所能表示的范围为:[-128,127]。
左移和右移是在补码上进行操做的,通常会有运算位移和逻辑位移。 运算位移:符号位不变 逻辑为宜:符号位发生变化,用0填充空的位 例如:
int main() { int a = 3; int b = -3; // 数字 原码 反码 补码 右移 // 3 0000 0011 0000 0011 0000 0011 0000 0001 // 去掉最右边的(1) // -3 1000 0011 1111 1100 1111 1101 1111 1110 // 去掉最右边的 // 补码->原码:1111 1110 -1 获得反码:1111 1101 取反获得原码:1000 0010(-2) printf("a = %d and a >> 1 = %d\n", a, a >> 1); printf("b = %d and b >> 1 = %d\n", b, b >> 1); return 0; }
按位与:& # 同为1则为1,反之为0 按位或:| # 只要有一个为1,则为1,反之为0 按位异或:^ # 只要不一样(一个1和一个0)则为真,相同为0
int main() { int num1 = 3; int num2 = 5; int num3 = -3; int num4 = -5; printf("正数的位操做\n"); printf("%d & %d = %d\n", num1, num2, num1 & num2); // 1 printf("%d | %d = %d\n", num1, num2, num1 | num2); // 7 printf("%d ^ %d = %d\n", num1, num2, num1 ^ num2); // 6 printf("负数的位操做\n"); printf("%d & %d = %d\n", num3, num4, num3 & num4); // -7 printf("%d | %d = %d\n", num3, num4, num3 | num4); // -1 printf("%d ^ %d = %d\n", num3, num4, num3 ^ num4); // 6 printf("负数和正数的位操做\n"); printf("%d & %d = %d\n", num1, num4, num1 & num4); // 3 printf("%d | %d = %d\n", num1, num4, num1 | num4); // -5 printf("%d ^ %d = %d\n", num1, num4, num1 ^ num4); // -8 return 0; }
解析:
原码 反码 补码
num1 0000 0011 0000 0011 0000 0011
num2 0000 0101 0000 0101 0000 0101
num3 1000 0011 1111 1100 1111 1101
num4 1000 0101 1111 1010 1111 1011设计
num1 & num2 = 3 & 5 = 0000 0001 补码=反码=原码=1
num1 | num2) = 3 | 5 = 0000 0111 补码=反码=原码=7
num1 ^ num2) = 3 ^ 5 = 0000 0110 补码=反码=原码=63d
num3 & num4) = -3 & -5 = 1111 1001 -> 1111 1000 -> 1000 0111 = -7
num3 | num4) = -3 | -5 = 1111 1111 -> 1111 1110 -> 1000 0001 = -1
num3 ^ num4) = -3 ^ -5 = 0000 0110 -> 补码=反码=原码=6
num1 & num4) = 3 & -5 = 0000 0011 -> 补码=反码=原码=3
num1 | num4) = 3 | -5 = 1111 1011 -> 1111 1010 -> 1000 0101 = -5
num1 ^ num4) = 3 ^ -5 = 1111 1000 -> 1111 0111 -> 1000 1000 = -8code
int main() { // 进行3次的按位异或操做,并将结果分别保存到第一 第二 第一个操做变量中 int a = 3; int b = 4; printf("交换以前:a = %d and b = %d\n", a, b); a = a ^ b; b = a ^ b; a = a ^ b; printf("交换以后:a = %d and b = %d\n", a, b); return 0; }
可是用加减法也能够
缺点:可能会溢出(当两个较大的数相加时)blog
int main() { // 进行1次加法,两次减法操做,并将结果分别保存到第一 第二 第一个操做变量中 int a = 3; int b = 4; printf("交换以前:a = %d and b = %d\n", a, b); a = a + b; b = a - b; a = a - b; printf("交换以后:a = %d and b = %d\n", a, b); return 0; }
6.2 应用-统计一个整数在内存存放的二进制中有多少个1内存
int main() { // 由于任何一个数的二进制的补码的最后一位是1,则其和1进行按位与运算则为1,反之为0,进行1..31次位移 int i; int num; int count = 0; scanf("%d", &num); for (i = 0; i < 32; i++) // 由于num占四个字节,32个比特位 { if (1 == ((num >> i) & 1)) { count++; } } printf("%d\n", count); return 0; }
或者数学
int main() { int num; scanf("%d", &num); int count = 0;//计数 while (num) { count++; num = num & (num - 1); } printf("二进制中1的个数 = %d\n", count); return 0; }