8位有符号数的范围为-128 ~ +127,为什么不是 -127 ~ 127,-127 ~ 128等等?

这是一个困惑了我几年的问题,它让我对现在的教科书和老师极其不满!

从我二三十年前开始摸电脑时,就几乎在每一本计算机基础、C语言、C++教科书上都说,8位有符号的取值范围是-128~+127,为什么不是-127~+127呢,后来的java int的聚值范围,在32位计算,-2^31 ~ +2^31-1,可是,却从来没有任何一本教科书或一个老师比我解释过这个问题。 后来在工作上或者是什么地方又没有直接遇到它,所以我也一直忽略它,但心里总是有一根刺,直到刚才,拔出来了!几经周折,终于把它搞清楚了:!

 

其实,-128不是规定的,它是计算机底层为了实现数值运算而决定的,涉及非常非常基础的原码,反码,补码知识,一般(99.9999%)都不会用得上. 那0.0001%,估计也就是计算机考试了

用2^8来表示无符号整数的话,全世界的理解都是0 - 255了,那么对于有符号的数值呢?

用最高位表示符号,0为+,1为-,那么,正常的理解就是 -127 至 +127 了. 

这就是原码了,值得一提的是,原码的弱点,有2个0,即+0和-0,还有就是进行异号相加或同号相减时,比较笨蛋,先要判断2个数的绝对值大小,然后进行加减操作,最后运算结果的符号还要与大的符号相同. 

于是乎,反码产生了,原因....略,反正,没过多久,反码就成为了过滤产物,也就是后来补码出现了. 

 

补码( two's complement representation )

计算机中的有符号数有三种表示方法,即 原码、反码、补码

三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位,三种表示方法各不相同。在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理,用加法来替代减法。

整型数据在内存中的存放,是以补码表示的:

1)正数的补码,反码,都与其原码相同

2)负数的补码,将该数的绝对值得二进制形式按位取反再加1

-5 = 1000 0101[原] = 1111 1010[反] + 1 = 1111 1011[补] (计算机内存的都是补码)

根据上面的理论,-5的补码就是256-5=251[1111 1011],但问题是我们正在解决减法问题,现在计算机还不会做减法,计算机怎么得到[1111 1011],

于是先辈们发明了一种算法,先得出原码[1000 0101],然后符号位不变,其余位取反(逻辑非),得到反码[1111 1010],再加1,得到了[1111 1011]。就是说,原反补码可以跳过计算直接得出2进制码。

 

-128 和 +127

补码的知识不说述,只说有关+127和-128的. 

官方的范围定义 [-2^(n-1),2^(n-1)-1],补码的0没有正负之分,即 0 = 0000 0000

原因呢?没有一本书上有说,这也是我这么火的原因,但通过思考,google,再思考,很快找到答案: 

首先,难不免干点白痴般地事情,穷举一下

正数,原码跟补码一样

+127, 0111 1111 
+126, 0111 1110 
+125, 0111 1101 
+124, 0111 1100 
+123, 0111 1011 
+122, 0111 1010 
... 
  +4, 0000 0100 
  +3, 0000 0011 
  +2, 0000 0010 
  +1, 0000 0001 
   0, 0000 0000
  ( 0 无正负之分

下面是负数了,值,原码,符号位不变其它取反,+1 

  -1, 1000 0001, 1111 1110, 1111 1111 
  -2, 1000 0010, 1111 1101, 1111 1110 
  -3, 1000 0011, 1111 1100, 1111 1101 
  -4, 1000 0100, 1111 1011, 1111 1100 
  -5, 1000 0101, 1111 1010, 1111 1011 
  -6, 1000 0110, 1111 1001, 1111 1010 
  -7, 1000 0111, 1111 1000, 1111 1001 
  -8, 1000 1000, 1111 0111, 1111 1000 
  -9, 1000 1001, 1111 0110, 1111 0111 
-10, 1000 1010, 1111 0101, 1111 0110 
-11, 1000 1011, 1111 0100, 1111 0101 
-12, 1000 1100, 1111 0011, 1111 0100 
-13, 1000 1101, 1111 0010, 1111 0011 
-14, 1000 1110, 1111 0001, 1111 0010 
-15, 1000 1111, 1111 0000, 1111 0001 
-16, 1001 0000, 1110 1111, 1111 0000 
-17, 1001 0001, 1110 1110, 1110 1111 
... 
-24, 1001 1000, 1110 0111, 1110 1000 
... 
-99, 1110 0011, 1001 1100, 1110 0100 
... 
-124, 1111 1100, 1000 0011, 1111 1101 
-125, 1111 1101, 1000 0010, 1000 0011 
-126, 1111 1110, 1000 0001, 1000 0010 
-127, 1111 1111, 1000 0000, 1000 0001 

看出点什么了没有? 

-128, 1111 1111, 1000 0000, 1000 0000

当然是 

-128, 先略过,再略过, -128 = 1000 0000 ?

1000 0000,那么,它的原码是什么呢? 

从补码求原码的方法跟原码求补码是一样的 

先保留符号位其它求反:  1111 1111, 再加1,就是 1 1000 0000, 超过了8位了 

对,用8位数的原码在这里已经无法表示了,最高位溢出,结果为:

-128 = 1 1000 0000[原] = 1 1111 1111[反] = 1 1000 0000[补] = -128

关键就在这里,补码 1000 0000 为 -128 是不用怀疑的(上面的穷举), 

那么,回到原码处, -128 的原码和补码,二者都是 1000 0000(超出的自动丢失), 

1000 0000 在原码表示什么呢? -0, 但补码却规定0没有正负之分 

转换一下思路,看看计算机里,是怎么运算的: 

对于负数,先取绝对值,然后求反,加1

-128 -> 128 -> 1000 0000 -> 0111 1111 -> 1000 0000

现在明确了吧. 

所以, 8位有符号的整数取值范围的补码表示 

1000 0000 到 0000 0000, 再到 0111 1111  即 -128 到 0, 再到 127 ,最终 -128 ~ +127 

 

数值   ,     补码(计算机真实存储的二进制是补码)

127    ,   0111 1111
.....    
5       ,   0000  0101
4       ,   0000  0100
3       ,   0000  0011
2       ,   0000  0010
1       ,   0000  0001
0       ,   0000  0000
-128  ,   1000  0000

-127  ,   1000  0001
-126  ,   1000  0010
....
-3  ,   1111  1101
-2  ,   1111  1110
-1  ,   1111  1111

 

补充说明

“一个n位有符号int型数值,其范围为-2^(n-1) ——2^(n-1) -1”,对于这个问题,很多人都是困惑不已。

其实,导致此情况的根本原因是“人们解决问题时,习惯以人的思维思考问题,但是,计算机本身确实以机器的思维进行处理的”。在这里,就表现为“计算机对数据的处理其实是以‘补码’的形式,而非日常生活中人们进行数学运算所采用的‘原码’的形式”,但是,人们在对“此数值范围”进行处理的时候,却习惯性的采用了“原码作为机器码”。

在历史上,针对“数值”计算,计算机先后采用过3种机器码——原码、反码、补码。

具体表示如下:

1. 原码   最高位为符号位,其余为对应数值的绝对值的二进制数值表示;

2. 反码   最高位为符号位,正数=原码 负数=符号位+原码对应的其他位数取反;

3. 补码   最高位为符号位,正数=原码 负数=反码+1。

其中,符号位“0为+,1为-”。

因为,计算机为数据类型分配了n位,超过n位的数值会被自动舍弃,那么, 就可以发现,现在计算机系统中采用的补码,克服了“原码中存在+0和-0”的情况,仅表现为一个0,(对于8位数据类型,即为8个0,具体推导见转载内容的穷举,-128计算补码时,产生的9位数据11000 0000--补码1000 0000)。

对应而言,8位有符号位数值的范围就成为了“-2^(8-1) ~ 2^(8-1) -1”,即“-128 ~ +127”。

问题的关键就在这里了,“计算机为有符号int型数值分配固定的位数n存储数据,当数据位数大于n时,大于n的位数被自动舍弃”。这就是导致数值范围为“-2^(n-1) ~ 2^(n-1) -1”的原因,从而,也导致了数据范围中“模”概念的产生。

 

原码、反码、补码

补码并不是必须从原码、反码推导出来的编码,只是恰好数值上有取反加一的对应关系。

补码是一种有利计算机实现减法的编码方案,和反码仅有部分的数值对应关系,并不是有原码、反码才能计算补码。

补码的意义在于:按照这样一种编码规则(也就是表示正数、负数和零的约定),我们就可以把减法运算变成正数加负数。

1、原码

机器数的一种简单的表示法,其符号位用0表示正号,用1表示负号,数值一般用二进制形式表示。

2、反码

可由原码得到。如果机器数是正数,则该机器数的反码与原码一样;

如果机器数是负数,则该机器数的反码是对它的原码(符号位除外)各位取反而得到的。

3、补码

可由反码直接得到,由原码间接得到。如果机器数是正数,则该机器数的补码与原码一样;如果机器数是负数,则该机器数的补码是对它的原码(除符号位外)各位取反,并在未位加1而得到的。

“除符号位外”的含义是,在整个运算过程中符号位始终不变,哪怕补码除符号位外全部是1,在末位加1以后,最高位溢出。

 

负数 = [ 1 + 非符号位 ]原 = [ 1 + 非符号位取反 ]反 = [反码+1]补 还是说 [ 1 + 反码非符号位+1 ]补

是否对于1个负数无论是补码还是反码,其符号位都是1 ?

128的原码是?

0的原码是 00000000, 127的原码是0111 1111, -1 的原码是 1 000 0001 , -2 的原码是 1000 0010 (形式上是-1的原码加1), -127的原码是 1 111 1111 (形式上是-126的原码加1);

-128的补码是10000000 这是怎么计算出来的? -128的源码要如何表示?

0的原码是 00000000(这个基本是肯定的), -128的原码是 1000 0000 ?

 

拓展知识

1. 模

 “模”实质上是计量器产生“溢出”的量,它的值在计量器上表示不出来,计量器上只能表示出模的余数。

例如,虽然时钟的模=12,但是在时钟的指针并不能真正指向“12点”,“12点”的位置和“0点”是重合的,即12点以0点表示。

换句话说,时钟的范围“0 ~ 11”,则模为12。

n位无符号数值的计量范围0 ~ 2^(n)-1,模=2^(n);

n位有符号数值,数值范围-2^(n-1) ~ 2^(n-1) -1,则模为2^(n-1)

举例说明,8位无符号数值,二进制模为2^8;8位有符号数值,表示的数值范围为0 ~ 2^8-1

 

2、补码的数值轴

以补码定义式为基础,沿数轴列出典型的真值、原码与补码表示,可清楚了解补码的有关性质

真值、原码、补码的关系,如下图:

(1) 在补码表示中,最高位X0 (符号位)表示数的正负,在形式上与原码相同,即 0正数,1负数。但补码的符号位是数值的一部分,由补码定义式计算而得。例如,负小数补码中X0为 1,这个 1是真值(负)加模 2后产生

(2) 在补码表示中,数 0只有一种表示,[+0]补 =[-0]补 =0.000……0

(3) 负数补码表示的范围比原码稍宽,多一种数码组合。对于定点数,若为纯小数,表示范围为:-1 ~ 1-2-n,若为纯整数,表示范围为:-2n ~ 2n-1

 

补码的意义

补码“模”概念的引入、负数补码的实质、以及补码和真值之间的关系所揭示的补码符号位所具有的数学特征,无不体现了补码在计算机中表示数值型数据的优势,和原码、反码等相比可表现在如下方面 

(1) 解决了符号的表示的问题,方便机器码表达与计算,统一了计算

(2) 可以将减法运算转化为补码的加法运算来实现,克服了原码加减法运算繁杂的弊端,可有效简化运算器的设计 

(3) 在计算机中,利用电子器件的特点实现补码和真值、原码之间的相互转换,非常容易

(4) 补码表示统一了符号位和数值位,使得符号位可以和数值位一起直接参与运算,这也为后面设计乘法器除法器等运算器件提供了极大的方便。

总之,补码概念的引入和当时运算器设计的背景不无关系,从设计者角度,既要考虑表示的数的类型(小数、整数、实数和复数)、数值范围和精确度,又要考虑数据存储和处理所需要的硬件代价。因此,使用补码来表示机器数并得到广泛的应用,也就不难理解了。

 

3. 补码的运算规则

[X+Y]补 = [X]补 + [Y]补

[X-Y]补 = [X]补 - [Y]补 = [X]补 + [-Y]补

 

 

参考推荐:

Byte 8位的范围为何是-128~127,而不是-127~128

MySQL 数据库 User表权限以及用户授权详解

MySQL 数据类型

Linux tcpdump命令详解

Java解惑(Java Puzzler)

运维人员常用软件总结

HTTP 协议的历史进化演变

MacBook Pro 修改 mac 地址

区块链的技术简史、应用场景、未来趋势