计算机的整数编码问题与数据上溢

一、问题提出:

大家都知道整数在计算机中的存储,是以二进制的方式来存储的,但是为什么

int main (void)
{
    char ch = 'a';
    int i = 1;
    while(ch > 0)
        ch += 1;
    while(i > 0)
        i += 1;

    printf("ch = %d\n",ch);
    printf("i = %d\n",i);

    return 0;
} 

代码的输出结果为:

ch = -128
i = -2147483648
Press any key to continue

这个结果是乱码吗?如果是乱码,我们将代码改为

int main (void)
{
    int distance = 1;

    while(distance < 9)
    {
        char ch = 'a';
        while(ch > 0)   
            ch += distance;//不同的distance间隔,最终的上溢值不同
        printf("ch = %d\n",ch);

        distance ++;

    }
    printf("\n");

    return 0;
}  

发现输出结果为:

ch = -128
ch = -127
ch = -126
ch = -127
ch = -124
ch = -123
ch = -124
ch = -127
Press any key to continue

虽然这些结果不是清一色的-127,也不是整齐的-127、-126、-125、-124递增…,但是似乎有一种规律告诉我们:

ch = -128;
i = -2147483648;
Press any key to continue
以及
ch = -128;
ch = -127;
ch = -126;
ch = -127;
ch = -124;
ch = -123;
ch = -124;
ch = -127;
Press any key to continue

这些结果都不是乱码。
要想搞明白这些问题,我们就不得不了解计算机中到底是如何存储整数/字符的。

二、问题分析:

计算机中字符是以ASCⅡ(美国国家信息交换标准字符码)的编码形式将字符转换成与之对应的整数来存储,而字符串则是一连串的字符所组成,所以要弄清楚字符以及字符串在内存中的存储形式,就必须了解整数在计算机内存中的存储形式。具体ASCⅡ对照表如下所示:

ASCⅡ标准码

三、整数存储方式:

整数的编码方式有原码、补码、反码、移码等。这四种码都是在机器码(机器码的编码规则与普通二进制代码相同)的基础上产生的,因此要了解数据的存储,首先要从原码开始讲起:

1、原码

(1)、为什么会产生原码:
原码的产生是因为机器码无法表示负数。
(2)、原码的编码规则:
原码将代码分为两部分,一部分(最高位)为符号位(正为0负为1),一部分(剩余n-1位)为数据位。数据位的编码就是该十进制数绝对值的二进制代码(机器码)。
例如:
100(D)=0100 0100(原)【最高位0表示正数】 (8位机)
-200(D)=1000 0000 1100 1000(原)【最高位1表示负数】 (16位机)
(3)、原码的缺点:
a、范围:
8位机:
最大正数 0111 1111;
最小负数 1111 1111;
即范围区间为:[-127,127].

16位机:
最大整数 0111 1111 1111 1111 1111 1111 1111 1111;
最小负数 1111 1111 1111 1111 1111 1111 1111 1111;
即范围区间为:[-32767,32767].

32位机:
范围区间为:[-2147483647,2147483647].

n位机(n=2^m):
范围区间为:[-(2^(n-1)-1),(2^(n-1)-1)].

b、原码区分正负零:
+0(D)=0000 0000(原);
-0 (D)=1000 0000(原);

c、原码表示数时会少表示一个:
0有两种表示(+0和-0),多占了一个位置就少表示了一个数。

d、原码实现二进制算术运算会出错:
例如:
1(D)+(-1)(D)=0000 0001(原)+1000 0001(原)=1000 0010(原)= -2(D)。该结果是错误的。

2、补码:

计算机中整数以补码的方式来存储识别。

(1)、补码的规则:

在原码的基础上,
若X(D)>=0:
X(补)= X(原)。

若X(D)< 0:
X(补)=最高位为符号位(1),数据位为原码数据位按位取反末尾加1。

(2)、补码运算:

a、一般求法:
eg1: (14-12)(D)=?(补)
(14-12)(D)
=(14)(D)+(-12)(D)
=(0000 1110)(补)+(1111 0100)(补)
=(1(上溢)( 0000 0010))(补)
=(0000 0010)(补)=2(D)

eg2: (12-14)(补)=?(补)
(12-14)(D)
=(12)(D)+(-14)(D)
=(0000 1100)(补)+(1111 0010)(补)
=(1111 1110)(补)=(1000 0010)(原)=-2(D)

b、模值求法:
模:是计量设备最大值加1后无法表示的数。如8位机模值为256。
计算方法:(模-X绝对值)的机器码就是X的补码
eg:-7(D)=?(补)
-7(D)=(256-7)(机)=249(机)=1111 1001(补)

(3)、补码特点

a、不区分正负零:
+0(D)=0000 0000(补)
-0(D)=1000 0000(原)取反+1=(1111 1111)+1=(1 (上溢)0000 0000)=(0000 0000)(补)
+0和-0补码表示形式相同

b、范围:
由于补码不区分正负0,所以补码表示范围比原码多一个数(多一个最小负数:-(2^(n-1)))。

8位机:
最大正数 0111 1111;
最小负数 1000 0000;
即范围区间为:[-128,127].

16位机:
最大整数 0111 1111 1111 1111 1111 1111 1111 1111;
最小负数 1000 0000 0000 0000 0000 0000 0000 0000;
即范围区间为:[-32768,32767].

32位机:
范围区间为:[-2147483648,2147483647].

n位机(n=2^m):
范围区间为:[-(2^(n-1)),(2^(n-1)-1)].

c、补码最大值加1为最小值:
eg:8位机下最大值为127(D)=0111 1111(补)
0111 1111(补)+1=1000 0000(补)=-128(补)

四、问题解决:

看到这儿,似乎最开始的问题已经解决了。那么让我们详细看一下:
由于ch = ‘a’;a的ASCⅡ值为97,即0110 0001(补),而当ch++加到0111 1111(补)(127>0)之前,ch>0一直成立,当0111 1111(补)再加1时,ch = 1000 0000(补)(即-128<0),ch>0不成立。所以循环结束最终输出结果为-128(8位最小值)。i输出-2147483648(32位最小值)原因是相同的。

又如ch += 5;输出结果是-124,是由于a的ASCⅡ值为97(0110 0001)(补);
ch += 5的值依次为:
0110 0110(补)(102>0)、0110 0110(补)(107>0)、
0111 0000(补)(112>0)、0111 0101(补)(117>0)、
0111 1010(补)(122>0)、0111 1111(补)(127>0);
而当0111 1111(补)+(0000 0101)(补)
=(11111111111111111111111110000100)(补)
=(-124)(D)。所以会输出-124,显然不是乱码。

五、思考:

对于上面程序输出时,会发现有迟钝,我们可以将代码稍作修改:
测一下要完成整个while()循环需要时间,发现用时4374ms。测时程序与结果如下:

# include <stdio.h>
# include <sys/timeb.h>
int main (void)
{
    struct timeb t1,t2;
    int i = 1;
    int t;
    ftime(&t1);
    while(i>0)
        i += 1;
    ftime(&t2);

    printf("i = %d\n",i);
    t=(t2.time-t1.time)*1000+(t2.millitm-t1.millitm); /* 计算时间差 */
    printf("用时%ld毫秒\n",t);
    return 0;
} 

输出结果为:

i = -2147483648
用时4374毫秒
Press any key to continue

int型n=2^(8*sizeof(int))=2^32
而char型n=2^(8*sizeof(char))=2^8
int型用时t(int)=4374ms,char型按理论就得t(char)=4374*2^(-24)ms这是一个微乎其微的时间。
用程序测得的时间仅仅只有0.000261ms:

    double powe = 4374*pow(2,-24);
    printf("%lf",powe);
0.000261
Press any key to continue
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值