mthoutai

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

在 C 语言中。 signed char 类型的范围为 -128~127,每本教科书上也这么写。可是没有哪一本书上(包含老师)也不会给你为什么是 -128~127,这个问题貌似看起来也非常easyeasy。 以至于不用去思考为什么,不是有一个整型范围的公式吗:  -2^(n-1)~2^(n-1)-1 (n为整型的内存占用位数)。所以 int 类型 32 位那么就是 -(2^31) ~ 2^31-1 即 -2147483648~2147483647,可是为什么最小负数绝对值总比最大正数多 1 。这个问题甚至有的工作几年的程序猿都模棱两可。由于没有深入思考过,仅仅知道书上这么写。

于是。我不得不深入思考一下这个被很多人忽视的问题。

对于无符号整数,非常easy,所有位都表示数值,比方 char 型,8位。用二进制表示为 0000 0000 ~ 1111 1111,1111 1111 最大即为十进制255。所以 unsigned char 的范围为 0~ 255,在这里普及一下 2 进制转十进制的方法。 二进制每一位的数值乘以它的位权(2^(n-1),n为自右向左的位),再相加,可得到十进制数。比方 :1111 1111 = 1*2^7 + 1*2^6 + 1*2^5 + 1*2^4 + 1*2^3 + 1*2^2 + 1*2^1 + 1*2^0 = 255 。


可是对于有符号整数,二进制的最高位表示正负,不表示数值。最高位为 0 时表示正数,为 1 时表示负数。这样一来,能表示数值的就剩下( n-1 )位了,比方 char a = -1;   那么二进制表示就为 1 0000001。1 表示为 0 0000001,所以 signed char 型除去符号位剩下的 7 位最大为 1111 111 = 127,再把符号加上,0 1111111 = 127,1 1111111 = -127。范围应该为 -127~127 。同理 int 类型也一样,可是问题出来了,教科书上是 -128~127 啊,以下就剖析一下这个惊人的奇葩。


再普及一下计算机内部整数存储形式。大家都知道计算机内部是以二进制来存贮数值的,无符号整数会用所有为来存储,有符号的整数。最高位当做符号位 ,其余为表示数值


这样貌似合理, 却带来一个麻烦。当进行加法时,1+1 :

       0000 0001

+     0000 0001

—————————

       0000  0010    ……………… 2 


当相减时 1-1=?

 因为计算机仅仅会加法不会减法,它会转化为 1 + (-1) ,因此:

      0000 0001

+    1000 0001

____________________

          1000 0010     …………… -2    


1-1 = -2?

 这显然是不正确了,所以为了避免减法运算错误。计算机大神们发明出了反码,直接用最高位表示符号位的叫做原码, 上面提到的二进制都是原码形式,反码是原码除最高位其余位取反,规定:正数的反码和原码同样,负数的反码是原码除了符号位,其余为都取反,因此 -1 的源代码为 1 0000001 ,反码为 1 1111110。


如今再用反码来计算 1+(-1) :

      0000 0001

+    1111 1110

————————

      1111 1111       ………… 再转化为原码就是 1000 0000 = -0  


尽管反码攻克了相减的问题,却又带来一个问题:-0 。既然 0000 0000 表示 0。那么就没有 -0 的必要, 出现 +0 = -0 = 0 ,一个 0 就够了。为了避免两个 0 的问题。计算机大师们又发明了补码。补码规定: 整数的补码是其本身。负数的补码为其反码加一 。所以。负数转化为反码需两个步骤, 第一,先转化为反码,第二: 把反码加一。


这样 -1 的补码为 1111 1111 ,1+(-1) :

     0000 0001

+   1111 1111

________________

  1  0000 0000  ……………………  因为 char 为 8 位。最高位 1 被丢弃结果为 0 ,运算正确。

 

-0 :原码 1000 0000 的补码为 1 0000 0000 。因为 char 是 八位 ,所以取低八位 00000000。   
+0 :原码 0000 0000 ,补码为也为 0000 0000 ,尽管补码 0 都是同样的,可是有两个 0 。既然有两个 0 ,况且 0 既不是正数,也不是负数, 用原码为 0000 0000 表示即可了。


这样一来,有符号的 char,原码都用来表示 -127~127 之间的数了。只有剩下原码 1000 0000 没实用。用排列组合也能够算出来,0 ???????,能表示 2^7 = 128 个数,刚好是 0~127。1 ???????。也能表示 128 个数,总共 signed char 有 256 个数,这与 -127~127 中间是两个 0 刚好吻合。


如今再来探讨一下关于剩下的那个 1000 0000。既然 -127 ~ 0 ~ 127 都有相应的原码与其相应,那么 1000 0000 表示什么呢,当然是 -128 了,为什么是 -128 呢。网上有人说 -0 即 1000 0000 与 128 的补码同样。所以用 1000 0000 表示 -128,,这我实在是不敢苟同。或者说 -128 没有原码。仅仅有补码 1000 0000,胡扯,既然没有原码何来补码,还有说 -128 的原码与 -0(1000 0000) 的原码同样,所以能够用 1000 0000 表示 -128,我仅仅能说。回答得不要那么牵强, 原码 1000 0000 与 -128 的原码实际上是不同的。


但为什么能用它表示 -128 进行运算。假设不要限制为 char 型(即不要限定是 8 位)。再来看,-128 的原码:1 1000 0000 ,9位,最高位符号位。再算它的反码:1 0111 1111。进而。补码为:1 1000 0000,这是 -128 的补码,发现和原码一样,1 1000 0000 和 1000 0000 同样?假设说一样的人真是瞎了眼了,所以,-128 的原码和 -0(1000 000) 的原码是不同的,可是在 char 型中,是能够用 1000 000 表示 -128 的,关键在于char 是 8 位,它把 -128 的最高位符号位 1 丢弃了,截断后 -128 的原码为 1000 000 和 -0 的原码同样。也就是说 1000 0000  和 -128 丢弃最高位后余下的 8 位同样,所以才干够用 -0 表示 -128,这样。当初剩余的 -0(1000 0000)。被拿来表示截断后的 -128,由于即使截断后的 -128 和 char 型范围的其它 (-127~127) 运算也不会影响结果。 所以才敢这么表示 -128。


比方 -128+(-1) :

     1000 0000  ------------------丢弃最高位的-128

+     1111  1111   -----------------   -1

________________

   10111  1111    ------------------char 取八位,这样结果不对,只是没关系 ,结果-129本来就超出char型了,当然不能表示了。


比方 -128+127:

     1000 0000

  + 0111 1111

————————

     1111 1111 --------------  -1 结果正确, 所以,这就是为什么能用 1000 0000 表示-128的原因。


从而也是为什么 char 是 -128~127。而不是 -127~127 。short int 相同如此 -32768~32767  由于在 16 位中,-32768 为原码为 17 位,丢弃最高位剩下的 16 为 - 0 的原码相同。



另一个问题,既然 -128 最高位丢弃了。那么为什么还能打印出 -128 ?

//在内存中以补码1 1000 0000 存储,但因为是 char。所以仅仅存储 1000 0000
char a= -128;

//既然最高位丢弃了。输出时应该是 1000 000 的原码的十进制数-0,但为什么能输出-128呢。

printf("%d",a);

我猜想是计算机内部的一个约定。就像 float 一样 ,能用 23 位表示 24 位的精度 。由于最高位默觉得 1,到时候把 23 位取出再加 1 便可。


-128 也是相同的原理,当数据总线从内存中取出的是 1000 000 。CPU 会给它再添最高一位,变为 1 1000 0000 这样才干转化为 -128 输出,不然 1000 0000 怎样输出?这当然是我的一种判断,详细怎么实现还得问 CPU 的设计者了。


再看一个样例:

char a=-129;
printf("%d",a); //会输入多少?? 结果为 127 ,为什么呢? 
-129 在补码为 10 0111 1111 仅仅取后八位存储,即 0111 111 这个值刚好是 127 了。同理 -130 截断后为 126.....


如此按模轮回,关于模就先不探讨了。


那么:

unsigned  char a=  -1;
if( 1 > a){  
	printf("大于");
}else{
    printf("小于");
}

结果是什么呢?  出人意料的是:小于。而不是大于。猫腻在你哪呢,还是存储问题:

a 为 unsigned 无符号。 它的八位都用来存储数值, 没有符号位,编译器把 -1 转换为补码为 1111 1111。但因为是无符号。计算机会把 1111 11111 当做是无符号来对待。自然就是 2^8 -1  = 255 了,所以相当于是 if( 1 > 255) 肯定是 printf("小于"); 了。


本文转自:http://blog.csdn.net/daiyutage

posted on 2017-06-07 17:44  mthoutai  阅读(424)  评论(0编辑  收藏  举报