嵌入式系统学习整理-FourDay-原码反码补码详解

目录

一、前言

一、原码、反码、补码定义

1. 原码

2. 反码

3. 补码

二、补码的加减计算


一、前言

        首先,要知道计算机底层都以二进制表示,数字也不例外,那么,如果数字加减运算的话,要怎么算呢?

        在计算机内部只有加法器,既产生数的和的装置,在电子学中,加法器为一种数位电路,可进行加法运算。

        对于十以内的加法运算,有相关的4个量:

  1. 被加数 A
  2. 加数 B
  3. 数字相加之和 S
  4. 相加之后的进位 C

        为什么考虑进位呢?要想想计算机不是人,他要按部就班的进行计算,我们初学加法不也是进行进位运算嘛,所以这一位记录是整个运算算法必要的数据。

        十进制我们了解了,那么二进制有什么不一样呢?

        二进制只有0和1,1+1就会产生进位,如果我们只考虑1+1的话,用上面四个数据就可以了,但是,只有1+1能做什么呢?变成两位怎么算?那每一位要怎么做?

        比如11 + 01(从右向左一次为第一位和第二位)第一位相加要进位,那么对于第二位来说是不是要处理第一位产生的进位呢!这时假设第二位是你要操作的位,就要比第一位多出一个要处理的数据。

        所以,对于二进制普遍处理而言,二进制加法要处理5个量:

  1. 被加数 A
  2. 加数 B
  3. 前一位的进位 CIN
  4. 数字相加之和 S
  5. 相加之后的进位 COUT

        要实现32位的二进制加法,一种自然的想法就是将1位的二进制加法重复32次(即逐位进位加法器)。这样做无疑是可行且易行的,但由于每一位的CIN都是由前一位的COUT提供的,所以第2位必须在第1位计算出结果后,才能开始计算;第3位必须在第2位计算出结果后,才能开始计算,等等。而最后的第32位必须在前31位全部计算出结果后,才能开始计算。这样的方法,使得实现32位的二进制加法所需的时间是实现1位的二进制加法的时间的32倍,并且形成的电路是不规则的,并且需要长线驱动,需要大驱动信号和大扇入门。当位数较多时,这种实现方式不太现实。后续的优化算法大家可以自行了解,这里不多说。

总的来说,加法器相对于加减器好实现,但是十位数转换成二进制进行减法计算就产生问题。这是就出现了原码、反码、补码,以便全部用加法计算。

一、原码、反码、补码定义

1. 原码

        原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:

[+1]原 = 0000 0001

[-1]原 = 1000 0001

        第一位是符号位. 因为第一位是符号位, 所以8位二进制数的取值范围就是:

[1111 1111 , 0111 1111]

        即

[-127 , 127]

        原码是人脑最容易理解和计算的表示方式.

2. 反码

        反码的表示方法是:

        正数的反码是其本身

        负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.

[+1] = [00000001]原 = [00000001]反

[-1] = [10000001]原 = [11111110]反

        可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值. 通常要将其转换成原码再计算.

3. 补码

        补码的表示方法是:

        正数的补码就是其本身

        负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)

[+1] = [00000001]原 = [00000001]反 = [00000001]补

[-1] = [10000001]原 = [11111110]反 = [11111111]补

        对于负数, 补码表示方式也是人脑无法直观看出其数值的. 通常也需要转换成原码在计算其数值.

二、补码的加减计算

        首先, 因为人脑可以知道第一位是符号位, 在计算的时候我们会根据符号位, 选择对真值区域的加减. (真值的概念在本文最开头). 但是对于计算机, 加减乘数已经是最基础的运算, 要设计的尽量简单. 计算机辨别"符号位"显然会让计算机的基础电路设计变得十分复杂! 于是人们想出了将符号位也参与运算的方法. 我们知道, 根据运算法则减去一个正数等于加上一个负数, 即: 1-1 = 1 + (-1) = 0 , 所以机器可以只有加法而没有减法, 这样计算机运算的设计就更简单了。

        于是人们开始探索 将符号位参与运算, 并且只保留加法的方法. 首先来看原码:

        计算十进制的表达式: 1-1=0

1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2

        如果用原码表示, 让符号位也参与计算, 显然对于减法来说, 结果是不正确的.这也就是为何计算机内部不使用原码表示一个数。

        为了解决原码做减法的问题, 出现了反码:

        计算十进制的表达式: 1-1=0

1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0

        发现用反码计算减法, 结果的真值部分是正确的. 而唯一的问题其实就出现在"0"这个特殊的数值上. 虽然人们理解上+0和-0是一样的, 但是0带符号是没有任何意义的. 而且会有[0000 0000]原和[1000 0000]原两个编码表示0。

        于是补码的出现, 解决了0的符号以及两个编码的问题:

1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原

        这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了.而且可以用[1000 0000]表示-128:

(-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]补 + [1000 0001]补 = [1000 0000]补

        -1-127的结果应该是-128, 在用补码运算的结果中, [1000 0000]补 就是-128. 但是注意因为实际上是使用以前的-0的补码来表示-128, 所以-128并没有原码和反码表示.(对-128的补码表示[1000 0000]补算出来的原码是[0000 0000]原, 这是不正确的)

        使用补码, 不仅仅修复了0的符号以及存在两个编码的问题, 而且还能够多表示一个最低数. 这就是为什么8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127].

        因为机器使用补码, 所以对于编程中常用到的32位int类型, 可以表示范围是: [-231, 231-1] 因为第一位表示的是符号位.而使用补码表示时又可以多保存一个最小值.

        首先以十进制5+6为例:(这里用八位二进制数表示)

已知,正数的原码、反码、补码相等。

       十进制        二进制原码        二进制反码        二进制补码

           5        -->  0000 0101        0000 0101        0000 0101

           6        -->  0000 0110        0000 0110         0000 0110

5+6 = 11       -->                                                     0000 1011

                                                    转换为十进制            11

减法运算(注:减法运算要把加法变为加法并用补码运算,运算完成后转换为原码)

-6-5 ==> -6+(-5)

        十进制                二进制原码      二进制反码       二进制补码

         -6                 -->  1000 0110          1111 1001        1111 1010        

         -5                 -->  1000 0101          1111 1010        1111 1011

-6-5 = -11              -->                                                     1111 0101

                                    补码减一                反码                原码

                                    1111 0101 (-1)     1111 0100       1000 1011      

                                                    转换为十进制                    -11

三、数值类型运算越界问题

1.数值类型所占字节

基本类型

        字符类型

                'a'-->字符 "ab"-->字符串 '\n'-->特殊字符

                char 1字节

                unsigned char 无符号字符类型

                signed char 有符号字符类型

        整型

                短整型 short 2字节(存储最大的数65535)

                unsigned signed

                整型 int 4字节(65535*65535)(与操作系统有关16位为16bit,32位为32bit)

                长整型 long 4字节

                长长整型 long long 8字节

        实型(浮点类型)

                单精度浮点类型

                        float 4字节

                双精度浮点类型

                        double 8字节

                多精度浮点类型

                        long double 12字节

        枚举类型

                enum()

        由上述整理可知,同一种无符号数据类型和有符号数据类型可写入的数据长度相同,但数据范围不同,那么以int(32bit)为例,无符号数越界后会怎样呢?

        首先,int类型可以存储4字节(1byte字节 = 8bit ),也就是说可以存储4*8bit(位)的0或1,那么对于无符号数来说,存储的最大数为1111 1111 1111 1111 1111 1111 1111 1111 ,最小数为0000 0000 0000 0000 0000 0000 0000 0000

1111 1111 1111 1111 1111 1111 1111 1111 + 1 结果如下:

1 0000 0000 0000 0000 0000 0000 0000 0000

        已知int类型会事先开辟32位来存储0、1,而超过的数字会被省略掉,那到底是左面的数字被“挤”出去了,还是右面的数字被“挤”出去了呢?

        这就需要引入一个新概念,大端系统和小端系统。

        大端存储模式,是指数据的低位保存在内存的高地址之中,而数据的高位保存在内存的低地址之中,小端的存储模式自然就是,数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中。


int main()
{
	int i = 1;
	int ret = i >> 8;
	if (ret== 0)
		printf("小端存储");
	else
		printf("大端存储");
	return 0;
}

        如果1是小端存储的话那么0000 0001 这最后一个1就在低地址位,在右移的时候就会丢失,那么补全0之后得出来的数字就是0.如果要是大端存储的话1000 0000  这个1就在最前边,如果你右移8位的时候就不会丢失这个1,得出来就不是1。所以能判段是大端还是小端。

         测试后,ubunt系统为小端存储,那么他就是从右向左,依次存储代码,也就是说当他定义的位不够时,最左面超额的部分就会被省略,那么越界后的数值在转换时不会再“合理”,好了,回到上一个问题,32位1的数值+1后的结果到底为什么,现在已经很清楚了吧,为0。

1 0000 0000 0000 0000 0000 0000 0000 0000        1被挤出int范围后面的为产生数值

继续+1

1 0000 0000 0000 0000 0000 0000 0000 0001        结果为1

1 0000 0000 0000 0000 0000 0000 0000 0010        结果为2

...                                                                                ...

1 1111 1111 1111 1111 1111 1111 1111 1111                结果为*********

        到这里你会发现,这些数值在int类型中形成了一个循环,当最大值+1时,会变回最小值,后续的计算会变得不准确,这也就是众多越界的一种越界,所以定义时一定记得检查定义的类型。

         那现在无符号int已知晓,那么有符号int类型是否一样呢?

        我们这里还是以最大值举例(因为最高位为符号位,符号位为1,代表负数)

    0111 1111 1111 1111 1111 1111 1111 1111                原码

    0111 1111 1111 1111 1111 1111 1111 1111 +1

    0111 1111 1111 1111 1111 1111 1111 1111               反码

    0111 1111 1111 1111 1111 1111 1111 1111              补码

    1000 0000 0000 0000 0000 0000 0000 0000          利用补码与1运算(+1)

            两个正数进行+运算时 结果的原码、补码、反码按照正数计算,当结果生成时,若原本的符号位因此发生改变,则产生越界。

  1000 0000 0000 0000 0000 0000 0000 0000              结果

        (有符号int最高位为1时,他发生越界后结果正常为-0,但是对我们人来说,+0、-0并没有意义,所以-0被定义为-2147483648(32bit系统),这时最高位既是符号位也是真值,所以转换成上述值)

        可以看到,这里也被进行循环,这些循环在一些特殊地方有特殊用法,在下还不了解,就不叙述了。

#include <stdio.h>

int main(int argc, const char *argv[])
{
#if 0
    int i = 1;
    int ret = i >> 8;
    if (ret== 0)
        printf("小端存储\n");
    else
        printf("大端存储\n");
#endif
    int a,b = -2147483648;
    a = 0x7fffffff;
    printf("%d\n",a);
    a++;                                                                        
    printf("%d\n",a);
    printf("%#x\n",a);
    return 0;
}

        gcc编译器测试结果如下:

   

不对处,欢迎指正!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

有人叫我注孤生

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

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

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

打赏作者

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

抵扣说明:

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

余额充值