C语言-数据类型(原码、反码、补码)

1、原码、反码和补码的表示方法

(1)    原码:在数值前直接加一符号位的表示法。

例如:      符号位  数值位

[+7]原=   0    0000111  B

[-7]原=   1    0000111  B

     注意:a. 数0的原码有两种形式:

             [+0]原=00000000B    [-0]原=10000000B

           b. 8位二进制原码的表示范围:-128~+127

(2)反码:

     正数:正数的反码与原码相同。

     负数:负数的反码,符号位为“1”,数值部分按位取反。

例如:     符号位 数值位

     [+7]反=  0   0000111  B

     [-7]反=  1   1111000  B

注意:a. 数0的反码也有两种形式,即

         [+0]反=00000000B

         [- 0]反=11111111B

      b. 8位二进制反码的表示范围:-128~+127

(3)补码的表示方法

12)补码的表示:

    正数:正数的补码和原码相同。

    负数:负数的补码则是符号位为“1”,数值部分按位取反后再在末位(最低位)加1。也就是“反码+1”。

例如:       符号位 数值位

      [+7]补=   0   0000111  B

      [-7]补=   1   1111001  B

补码在微型机中是一种重要的编码形式,请注意:

a.             采用补码后,可以方便地将减法运算转化成加法运算,运算过程得到简化。正数的补码即是它所表示的数的真值,而负数的补码的数值部份却不是它所表示的数的真值。采用补码进行运算,所得结果仍为补码。

b.            与原码、反码不同,数值0的补码只有一个,即       [0]补=00000000B。

c.             若字长为8位,则补码所表示的范围为-128~+127;进行补码运算时,应注意所得结果不应超过补码所能表示数的范围。

2.原码、反码和补码之间的转换

由于正数的原码、补码、反码表示方法均相同,不需转换。

在此,仅以负数情况分析。

(1)    已知原码,求补码。

例:已知某数X的原码为10110100B,试求X的补码和反码。

解:由[X]原=10110100B知,X为负数。求其反码时,符号位不变,数值部分按位求反;求其补码时,再在其反码的末位加1。

1  0  1  1  0  1  0  0   原码

 

1  1  0  0  1  0  1  1   反码,符号位不变,数值位取反

                     1   +1

1  1  0  0  1  1  0  0   补码

故:[X]补=11001100B,[X]反=11001011B。

(2)    已知补码,求原码。

分析:按照求负数补码的逆过程,数值部分应是最低位减1,然后取反。但是对二进制数来说,先减1后取反和先取反后加1得到的结果是一样的,故仍可采用取反加1 有方法。

例:已知某数X的补码11101110B,试求其原码。

解:由[X]补=11101110B知,X为负数。求其原码表示时,符号位不变,数值部分按位求反,再在末位加1。

1  1  1  0  1  1  1  0   补码

 

1  0  0  1  0  0  0  1   符号位不变,数值位取反

                     1   +1

1  0  0  1  0  0  1  0   原码

1.3.2  有符号数运算时的溢出问题

请大家来做两个题目:

两正数相加怎么变成了负数???
 
1)(+72)+(+98)=?

0 1 0 0 1 0 0 0 B    +72

     +  0 1 1 0 0 0 1 0 B    +98

        1 0 1 0 1 0 1 0 B    -42

两负数相加怎么会得出正数???
 
2)(-83)+(-80)=?

1 0 1 0 1 1 0 1 B    -83

     +  1 0 1 1 0 0 0 0 B    -80

        0 1 0 1 1 1 0 1 B    +93

   思考:这两个题目,按照正常的法则来运算,但结果显然不正确,这是怎么回事呢?

   答案:这是因为发生了溢出。

如果计算机的字长为n位,n位二进制数的最高位为符号位,其余n-1位为数值位,采用补码表示法时,可表示的数X的范围是   -2n-1≤X≤2n-1-1

当n=8时,可表示的有符号数的范围为-128~+127。两个有符号数进行加法运算时,如果运算结果超出可表示的有符号数的范围时,就会发生溢出,使计算结果出错。很显然,溢出只能出现在两个同符号数相加或两个异符号数相减的情况下。

对于加法运算,如果次高位(数值部分最高位)形成进位加入最高位,而最高位(符号位)相加(包括次高位的进位)却没有进位输出时,或者反过来,次高位没有进位加入最高位,但最高位却有进位输出时,都将发生溢出。因为这两种情况是:两个正数相加,结果超出了范围,形式上变成了负数;两负数相加,结果超出了范围,形式上变成了正数。

而对于减法运算,当次高位不需从最高位借位,但最高位却需借位(正数减负数,差超出范围),或者反过来,次高位需从最高位借位,但最高位不需借位(负数减正数,差超出范围),也会出现溢出。

-----------------------------------------------------

-----------------------------------------------------

 #include <stdio.h>

        int main(void)
        {
            unsigned int un = 3000000000; /* 我使用的编译器 int 是 32 位的 */
            short end = 200;              /* 而 short 是 16 位的      */
            long big = 65537;

            printf("un = %u and not %d\n", un, un);
            printf("end = %hd and %d\n", end, end);
            printf("big = %ld and not %hd\n", big, big);

            printf("Press ENTER to quit...");
            getchar();
            return 0;
        }

程序输出结果如下:

        un = 3000000000 and not -1294967296
        end = 200 and 200
        big = 65537 and not 1
        Press ENTER to quit...

    这个程序表明,错误使用格式限定符会导致意想不到的输出。首先,错误使用 %d 来做无符号整型变量 un 的格式限定符,导致输出的是负数。这是因为我的计算机使用相同的二进制形式来表示 3000000000 和 -129496296 ,而计算机只认识二进制。所以,如果我们使用 %u 告诉 printf 输出无符号整数,输出的就是 3000000000;如果我们误用了 %d,那么输出的就是一个负数。不过,如果我们把代码中的 3000000000 改成 96 的话,输出就不会出现异常。因为 96 没有超出 int 的表示范围。

    然后,对于第二个 printf,无论我们使用 %hd 还是 %d,输出的结果都是一样的。这是因为 C 语言标准规定,当 short 类型值传递给函数时,要自动转化成 int 类型值。之所以转化成 int,是因为 int 被设计为计算机处理效率最高的整数类型。所以,对于 short 和 int 大小不同的计算机来说,把变量 end 转化成 int 类型再传递给函数,速度更快。如此说来,h 好像没有存在意义。其实不然。我们可以用 %hd 来看看较大的整数类型被截断成 short 类型的时候会是什么样的。

    而第三个 printf,由于误用 %hd,导致输出是 1。这是因为,如果 long 是 32 位的话,65537 的二进制形式便是0000 0000 0000 0001 0000 0000 0000 0001,而 %hd 命令 printf 输出 short 类型的值,从而导致 printf 只处理 16 位数据(假设 short 是 16 位的),最终导致输出 1。

整数溢出

    首先请看以下程序:

            #include <stdio.h>

            int main(void)
            {
                /* 32 位 int 表示范围的上限和下限 */
                int i = 2147483647, j = -2147483648;
                unsigned int k = 4294967295, l = 0;

                printf("%d %d %d %d\n", i, i+1, j, j-1);
                printf("%u %u %u %u %u\n", k, k+1, k+2, l, l-1);

                printf("Press ENTER to quit...");
                getchar();
                return 0;
            }

程序输出结果如下:

            2147483647 -2147483648 -2147483648 2147483647
            4294967295 0 1 0 4294967295
            Press ENTER to quit...

    本例中,i+1 是负数,j-1 是正数,k+1 是 0,l-1 是 4294967295 。这是因为加减运算过后,它们的值超出了它们对应的那种整数类型的表示范围,我们把这种现象称为溢出

    unsigned int 型变量的值如果超过了上限,就会返回 0,然后从 0 开始增大。如果低于下限,那么就会到达 unsigned 型的上限,然后从上限开始减小。就好像一个人绕着跑道跑步一样,绕了一圈,又返回出发点。一般,int 型变量溢出的话,会变成负数,或者正数。

C语言为编程者提供了三种不同长度的整数:short int、int和long int,但不管是哪种类型表示的整数总有一定的范围,越出该范围时称为整数的溢出。例如现有算法要求如下:求满足条件1+2+3+…+n≤32767的最大整数n,请考察如下程序段:

int n=1,sum=0;

while(sum<=32767) {sum+=n; n++;}

printf(“n=%d\n”,n-1);

乍看该程序时无错误,但事实上,上列程序中的while循环是一个无限循环,原因在于int型数的表示范围为-32768到+32767,当累加和sum超过32767时,便向高位进位,而对int型数而言,最高位表示符号,故sum超过32767后便得到一个负数,while条件当然满足,从而形成无限循环。此时,最好的解决办法是将sum定义为long int型。

另外google的一道笔试题中也需要意识到溢出的存在

short cal(short x)
{
        if(x==0)
return 0;
        else
            return x+cal(x-1);
}

答案

x==0时,0
x>0时,x+…+1
x<0时,x+(x-1)+…+(-32768)【溢出】+32767+……+1,中途栈溢出
假如是short类型的负数来说,-32768减去1之后,编程32767,就是说对于有符号整数来说,最小的负数-1=最大的整数,最大的整数+1=最小的负数。
假如栈不溢出,那么将递归32768-x+32767次,最后的值按照上面是可以计算出来的
但是栈的空间有限,当栈溢出的时候,错误,强制退出。
在gcc下,测试,假如上述数据类型是char,最后是能计算出值的大小,栈的大小还够用
上面提到的是有符号数的溢出,下面是无符号数的溢出
在c语言的程序开发调试中,经常碰到非法操作导致程序强行终止。这种情况的发生多是因为程序指针的指向错误,数据溢出就是其中的一种,下面我将介绍一下常见的几种溢出情况。

1、无符号整数上溢

示例代码:

bool funcB(char *s1,unsigned short int len1,char *s2,unsigned short int len2)
{
if (1 + len1 + len2 > 64)
return false;
char *buf = (char*)malloc(len1+len2+1);
if (buf) {
memcpy(buf,s1;len1);
memcpy(buf+len1,s2,len2);
}
if (buf) free(buf);
return true;
}


这段代码存在整数上溢问题,当len1等于64,len2是0XFFFF,这段代码就会发生溢出。因为在定义为unsigned short char 类型下1+0xFFFF=0,这样就绕过了1 + len1 + len2 > 64的条件判断。直接导致后面的代码中错误地分配64字节内存,在内存拷贝时将产生溢出错误。
    2、无符号整数下溢

示例代码:

bool funcA(unsigned int cbSize)
{
if (cbSize < 1024)
{
char *buf = new char[cbSize-1];
memset(buf,0,cbSize-1);
delete buf;
return true;
}
else
return false;
}


这是一个整数下溢的例子。当函数调用时,如果参数cbSize赋值为0,由于参数cbSize被定义为unsigned int 型,则(cbSize-1)= (0-1) = 0XFFFFFFFF,分配如此大的内存,后果可想而知!
 
----------------------------------
#include <stdio.h>
 short int fac( short int x)
{
    static  short int y=1;
    y*=x;
    return y;
}
int  main(void)
{
    int s=0;
    short i;
    for(i=1;i<=8;i++)
        s+=fac(i);
    printf("S=%d\n",s);
    return 1;
}
运行结果:S=-19303

运行SETP:
Setp1:i=1 y=1 S=0+1=1
Setp2:i=2 y=2 S=1+2=3
Setp3:i=3 y=6 S=3+6=9
Setp4:i=4 y=24 S=9+24=33
Setp5:i=5 y=120 S=33+120=153
Setp6:i=6 y=720 S=153+720=873
Setp7:i=7 y=5040 S=873+5040=5913
Setp8:i=8 y=40320溢出 
16位内存空间存储情况:1001,1101,1000,0000(即40320的二进制表示)
反求补码:SETP1(减1)得到:1001,1101,0111,1111
SETP2(按位取反)得到:0110,0010,1000,0000(即25216的二进制表示)
故:y=-25216  S=5913-25216=-19303
Setp9:i=9,for循环结束,执行下一句输出:S=-19303
(40320   1001 1101 1000 0000    反码:1110 0010 0111 1111 补码:1110 0010 1000 0000  
  0110001010000000为 25216    )
 
------------------------------------
#include <stdio.h>
void main()
{
char c1 = 128;
unsigned char c2 = 257;
short s1 = 65535;
unsigned s2 = 65537;
printf("%d,%d,%d,%d",c1,c2,s1,s2);
}
unsigned char c2 = 257这一个
在内存中8位是表示不完的
所以需要9位的二进制才能表示它的值
但一个unsigned char只能存8位的值,所以这里就需要截断

char c1 = 128,char这是一个有符号位的类型
所以在计算它的值的时候,需要用补码方式计算
128在内存中是:1000 0000 (最高一位是符号位)
补码计算,按位取反,再加1得:1000 0000=128
因其符号位是1,所以是负数:-128
输出结果:-128,1,-1,65537

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值