原码-反码-补码的理解

第一 机器码和真值

在计算机中是以二进制的形式存储的,即机器数其特色是:
  • 是有符号的(最高位表示,1:负数;0:整数)
  • 其他位表示真正的数字(即真值)

    第二 原码 反码 补码

    原码:
    相似于机器码,是有符号位的。
    反码:
    正数的反码等于其原码自己。
    负数的反码等于其原码的符号位保持不变,其他位取反。
    补码:
    正数的补码=正数的反码=正数的原码
    负数的补码=其反码+1
    例如:8位的±3ide

有符号数 数值 原码 反码 补码
正数 +3 0000 0011 0000 0011 0000 0011
负数 -3 1000 0011 1111 1100 1111 1101

第三 反码和补码的做用

对于计算机而言,加减乘除是最基本的运算,所以要设计的要尽可能的简单,若是让计算机来辩解符号位,就会增长设计的复杂度,所以工程师设计出了一个方法:让符号位也参加算术的运算, 好比:3-3设计为3 + (-3),于是对于机器而言只有加法没有减法运算;因而就要用到反码和补码。编码

  • 若是使用反码进行运算:
    3 - 3 = 3 + (-3)
    = 0000 0011 + 1000 0011 #原码
    = 0000 0011 + 1111 1100 #反码
    = 1111 1111 #反码
    = 1000 0000 #原码
    = -0
    当用反码进行计算时,真值部分是对的,可是符号位带有±,对于0而言是没有正负的,若是用正负是没有意义的。且会有两个二进制来编码0.
  • 使用补码进行计算
    3 - 3 = 3 + (-3)
    = 0000 0011 + 1000 0011 #原码
    = 0000 0011 + 1111 1100 #反码
    = 0000 0011 + 1111 1101 #补码
    = 0000 0000 #补码
    = 0000 0000 #原码
    = 0
    这样0用0000 0000来表示,则-0则不存在啦,所以下列二进制分别表示8位的127:-128
数字 二进制
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

6.1 应用-不借助中间变量交换两个数

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;
}
相关文章
相关标签/搜索