问题

为什么8bit限制是-128到127而不是-127到128?

回答
你问到了一个计算机底层非常经典和基础的问题:为什么8位有符号整数的范围是128到127,而不是像我们直觉上那样猜想的127到128?这背后涉及到一个叫做“二进制补码”(Two's Complement)的编码方式,它是现代计算机表示负数最主流、最有效的方法。

咱们一步一步来拆解,让你彻底明白这件事。

首先,什么是8位?

“位”(bit)是计算机中最小的信息单元,只能表示0或1。8位就是说我们用8个这样的0或1来表示一个数字。

然后,什么是“有符号整数”?

整数就是没有小数部分的数字,比如5, 0, 100。而“有符号”就意味着这个数字可以是正数,也可以是负数。为了表示负数,我们需要一个特殊的“标记”。

那我们是怎么标记正负的呢?

在计算机里,最常见也最自然的方式就是用最高位(最左边的那一位)来表示符号。通常约定:

最高位是0: 表示这个数是正数。
最高位是1: 表示这个数是负数。

现在来考虑8位的情况:

我们有8个位置来放0或1: `b7 b6 b5 b4 b3 b2 b1 b0`

其中,`b7` 是最高位,用来表示符号。

让我们看看它如何表示正数:

对于正数,最高位 `b7` 必须是0。剩下的7位 (`b6` 到 `b0`) 就用来表示数字的大小。

这7位一共有 27 = 128 种不同的组合,从 `00000000` (代表0) 到 `01111111`。

`00000000` (二进制) = 0 (十进制)
`00000001` (二进制) = 1 (十进制)
...
`01111111` (二进制) = 64 + 32 + 16 + 8 + 4 + 2 + 1 = 127 (十进制)

所以,用8位表示的正数,其最大值就是127。这是没疑问的。

问题就出在负数,也就是当最高位是1的时候。

如果最高位 `b7` 是1,我们知道这是个负数。那么,剩下7位 (`b6` 到 `b0`) 怎么来表示负数的大小呢?

如果我们就那样直接用,会遇到一个问题:数字0怎么表示? `00000000` 表示0,那负数呢?难道 `10000000` 表示0? 这会造成两个零,带来很多不便。

为了解决这个问题,计算机科学家们发明了“二进制补码”(Two's Complement)表示法。这是一种非常巧妙的表示负数的方式,它有几个优点:

1. 只有一个零: `00000000` 就代表唯一的零。
2. 加法运算统一: 用补码表示的负数进行加法运算时,和表示正数时使用同样的加法电路和逻辑,不需要分开处理。这大大简化了硬件设计。
3. 范围对称性(接近): 它使得正负数的数量尽可能地对称。

那么,补码是怎么工作的呢?

对于一个整数 `N`,它的二进制补码表示是这样计算的:

1. 正数N: 直接用二进制表示,并在前面补零到所需的位数(例如8位)。
2. 负数N:
首先找到正数 `N` 的二进制表示。
然后对它的每一位进行“取反”(0变成1,1变成0),这叫做“按位取反”(One's Complement)。
最后,将取反后的结果 加一。

让我们用8位来举例计算负数:

我们想表示 1。

正数 1 的8位二进制是:`00000001`
按位取反:`11111110`
加一:`11111110` + `00000001` = `11111111`
所以,`11111111` 代表 1。

我们想表示 127。

正数 127 的8位二进制是:`01111111`
按位取反:`10000000`
加一:`10000000` + `00000001` = `10000001`
所以,`10000001` 代表 127。

现在来看那个“麻烦”的数字:128。

我们试试计算正数 128 的补码。但是,我们只有8位,最高位已经用来表示符号了,剩下7位最多只能表示到127。也就是说,用8位有符号整数,我们根本无法表示正数128。

那么,当我们将补码的计算推到极限时会发生什么?

让我们看看以 `10000000` 为代表的数字。根据补码的规则反推,它代表哪个负数?

如果我们把它当成一个需要“反推”的补码,我们先对它进行“减一”(补码加一的逆操作)。
`10000000` `00000001` = `01111111` (就像是借位了)
然后对结果进行按位取反:
`01111111` 取反 = `10000000`

这个结果是 `10000000`。但这就陷入了一个循环,我们不知道这个 `10000000` 是怎么来的。

另一种理解方式(更直观):

我们用8位表示有符号数,总共有 28 = 256 种组合。

正数和零: 最高位是0。
`00000000` 代表 0。
`00000001` 到 `01111111` 代表 1 到 127。
总共有 1 + 127 = 128 个非负数。

负数: 最高位是1。我们还剩下128种组合(从 `10000000` 到 `11111111`)。
补码的设计使得这些组合被映射到负数。
`11111111` 代表 1。
`11111110` 代表 2。
...
`10000001` 代表 127。

现在,剩下最后一个组合:`10000000`。

按照补码的逻辑,这个 `10000000` 所代表的负数,应该是从 1 开始,往更小的数去数。如果 `11111111` 是 1,那么 `10000000` 就是比 `11111111` 还要小一个单位的数。

在补码系统中,`10000000` 被约定为表示 128。

为什么是128,而不是我们可能期望的127呢?这是因为补码的构造方式。

让我们回到“加一”的步骤来理解“不对称”的原因:

补码的公式实际上可以看作:

`Value = b7 2^7 + b6 2^6 + b5 2^5 + ... + b0 2^0`

注意最高位 `b7` 的系数是负的,而且它的权重是最大的 (27 = 128)。

当 `b7` 是0时,这个数是一个正数。
`01111111` = 0 128 + 164 + 132 + 116 + 18 + 14 + 12 + 11 = 127。
如果最高位是0,我们能表示的最大正数是 `01111111`,即 127。

当 `b7` 是1时,这个数是一个负数。
让我们看看 `10000000`:
Value = 1 27 + 026 + 025 + ... + 020
Value = 1 128 + 0
Value = 128

再看看 `11111111`:
Value = 1 27 + 126 + 125 + ... + 120
Value = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1
Value = 128 + 127
Value = 1

所以,通过这种方式,我们发现 `10000000` 自然而然地就成为了最小的负数,而它的值是 128。

总结一下核心原因:

1. 符号位: 最高位用作符号。
2. 补码规则: 使用“按位取反加一”来表示负数。
3. 权重分配: 补码的计算公式中,最高位(符号位)的权重是负的,且是最大的。
4. 最大正数: 当符号位为0时,剩下的7位全为1 (`01111111`) 达到最大正数 127。
5. 最小负数: 当符号位为1时,如果剩下的7位全为0 (`10000000`),根据补码公式,其值为 1 27 = 128。这是因为 `10000000` 是补码系统中最小的负数表示,它“独占”了原先可能用于表示 128 的位置,而将 127 到 1 的表示分布在 `10000001` 到 `11111111`。

因此,由于补码的数学定义和计算机硬件的实现方式,8位有符号整数的范围在数学上自然地变成了 128 到 127。这种不对称性是为实现更简洁高效的算术逻辑而付出的代价,而这个代价在实际应用中是非常值得的。

网友意见

user avatar

很表面很浅薄的问题。

简单说爱怎么规定就怎么规定,甚至-1到254都行。无非是显示时通过编码表做个转换的问题而已。


不过,当初选择“补码”这种编码形式,却并不像表面看起来那么浅薄。背后的道道可多着呢。


首先,8位二进制一共可以提供256个“码点”;那么我们就总可以用这些“码点”来编码256种符号。

这种编码方案有很多。最著名的大概就是ASCII码方案了,这个方案规定了英文字符(区分大小写)、0~9这10个数字、标点符号以及一些控制字符如何编码:


但ASCII码用来编码字符效果不错;拿来存储数字却极为浪费。比如它需要三个字节才能表示123。


为了编码数字,我们需要一个更有效的方案。

一种很自然的想法是,我们就直接把二进制对应的数字值拿来用,这就是最好的编码方案!

于是,8个二进制位就可以表示0~255之间的所有数字——用ASCII码三个字节才能表示的123,直接用二进制编码就是01111011,一个字节足够了。


这个方案只能表示正数;遇到负数怎么办呢?

简单,第一个二进制位拿出来当符号位,剩下七位仍然当数字用,就能表示±127之间的任何数字了。


这个方案就叫“原码”;其中不带符号位的就是无符号数(unsigned)。


原码是一种很初级的编码方案,它仅仅解决了编码问题——从此数字有办法二进制表示了。


但我们在计算机内部表示数字是用来计算的;那么想用原码计算的话,那可就麻烦了……

我们知道,最初CPU的内部最重要的核心器件叫ALU(Arithmetic and Logic Unit算术逻辑单元),其中的A就是数学。

ALU的核心是加法器,这是个随参与计算的数值的二进制位数指数增长的数字电路。较早期的CPU里面绝大多数的逻辑门都被拿来做这个加法器了。


加法器顾名思义只能拿来做加法。

但是没关系,如果你调过机械表,就知道从8点调到1点的方式有两种:一种是往后拨7个小时,一种是往前拨5个小时。

换句话说,在时钟钟面上,8-7和8+(12-7)效果相同,最终得到的都是1.


类似的,1个字节的加减法,如果计算结果超过255就会造成溢出,溢出的高位二进制数据无处存放自动丢弃,计算结果就出错了——但反过来想,这不恰恰就是一个逻辑钟面吗?


显然,我们也可以利用这个性质做减法:减32完全可以当成加(256-32)来算嘛;而由于二进制的特点,256-32恰好又等于32这个数值取反。


类似的,有符号数其实是一个符号位和7个二进制位,7个二进制位能表示的最大数是127;因此减32就可以用加(128-32)代替(和表盘上的12点/0点一样)。


于是,减法器就可以不做,一个加法器就足够用了——省了好大一坨门电路,CPU的制造成本一下子就去了一大块。


既然最终减法一定要这样做……那么从一开始就不应该用原码表示负数,对吧。

不然每次计算都还得用一条指令判断判断符号位,然后该取反取反……这速度可就慢下去了。


如果从一开始,负数就取反表示,那么负数加法完全无需判断,拎起来就加——圆满。


这个编码方案就是所谓的反码。


反码是一个充满了工程师的恶臭味的优秀方案。


说它优秀,是因为它的确解决问题;说它恶臭,是因为它用起来实在麻烦,需要很多“微妙”的调整才能得到正确结果。

比如,它的符号位相加后,如果产生了进位,就要把进位送回去加到最低位上——你得搞一大张真值表才能确定这个做法的正确性。


嗯……这就是最容易产生没人看得懂但绝对不能动不然就会出错的神奇代码的重灾区——反正它就是能工作;刚开始我还知道为什么得这样做,一段时间后就只有上帝知道了。



反码行为奇特的根本原因在于,它有两个零:+0和-0,分别对应于00000000和11111111——还记得吗?我们规定第一位是符号位。因此最前面的0/1是±号,并不是数值。

但+0和-0都是0.它们是同一个数据,却得到了两个码点。


打个比方的话,这就好像夜里12点就是0点一样;结果我们的钟匠师傅没想明白,偏偏要在钟面上、12点和1点之间添加一个零点——然而逻辑上我们仍然需要12小时是一圈。

现在,你还想好好调表吗?算的准准的,8点前拧5个小时就是1点了;结果拧完一看,0点?


彻底乱套了,对吧。


而反码的计算规则呢,无异于规定了过12点的方向——正着过正常去1点,反着过会先停在-0点上,所以必须推一把。

注意这个调整是计算过程的一部分,每次计算都必须即时调整。这是一个额外的负担——和显示时查表转换到光学点阵/向量是不想干的两个过程。

或者说,数据的内部表示和外部显示之间的转换是另外一个必不可少的流程。这里只要不是太过复杂就不能算额外负担;而原码/反码这两个编码方案已经影响了计算过程,造成了额外的性能消耗。

一言以蔽之:能解决问题,但是太难看、太复杂。



一个更好的方案叫补码。

但是在介绍补码之前,我先来讲一个数学概念——群。


群大概来源于“算术运算以及适用算法运算的集合”的抽象,但又超脱于简单的四则运算,是一切计算/变换类似行为的总纲。

在群的观念里,加减乘除都是一种“二元运算”;二元运算是一个集合G中任意两个元素向群中另一个元素的映射。比如1+1就映射到了2.

注意群有“封闭性”,意思是群中任意两个元素经过二元运算后,映射的那个元素都还要在群中。因此(自然数,加减法)就不是一个群,因为减法会映射到负数。

此外,二元运算需要满足结合律,要有单位元(任何元素与之执行二元运算后都会映射到该元素自身),等等。


更复杂的东西我也还看不懂(对不起,俺数学水平太弱鸡了);但了解这么多其实也已经够了:反码存在两个0,意味着对于加法运算来说,它存在两个不同的单位元;而根据群的定义,群里面有且只有一个单位元。

因此,在反码这个基础上无法定义一个群——用人话说就是,你不可能期望找到一种不需要判断的算法,从而基于反码模拟加减法运算。


没错,反码有两个零这事并不像外行想象的那样无关痛痒——它并不仅仅是浪费了一个码点的问题,而是破坏了相关结构的性质的问题。


如何解决这个问题呢?

不妨返璞归真,看看这个问题的本质。


很简单,和上面等待写入时间信息的无字钟面一样:这里有256个不同的二进制编码,我们需要给它们分别指定一个意义。


我们希望它们是连续的编码,且基于二进制的排序不能打乱——这样我们才能使得基于这些码点的、抛弃溢出位的加减法运算构成一个群。

只有它们是一个群,我们才能简单明了的在加法器上支持加减法运算——而不是先算一个瑕疵值然后想办法弥补、把硬件/软件变得复杂。


打个比方的话,就是把这些二进制编码按顺序排于钟面,我们要在上面填上带±号的数字。

原码的问题在于,它的编码排列“不按固定顺序”,使得因此必须把负数先“颠倒”一下(实际上取反)才能用;而反码头疼医头脚疼医脚,大致保证了编码顺序,却没能消除额外的无效码点,造成在±0这个位置两个码点对应一个编码。

这两个编码都没法自然构造出加法群


借用@任卫 同学的这张图:

可以很清晰的看出补码编码的连续性。

(相比之下,原码是0 1 2 3……127 -0 -1 -2……-127,顺序上一会儿从小到大一会儿从大到小;补码按照一定的顺序编码但是多了个-0;只有补码,严格按照统一的顺序连续排列数字)。


既然连续,那么通过加一个值(可能为负)调整对称中心(比如0的位置是00000000还是11111111)、然后再引入模运算剔除高位溢出,这个群就建立起来了。

换句话说,随便你如何编码,只要别改变底层的二进制顺序、不要有跳跃/重复码点,那么这个计算就仍然是一个群。

这个计算过程和最终的显示是完全脱钩的,你不需要在计算时做任何调整——溢出就随它溢出,反正(在模运算的层面上)算出来的值总是对的。这是群的性质所保证的。

(注意是“模运算的层面上”,换句话说算出来的实际意义是什么还是得你自己解释;尤其是产生溢出之后。)


比如,哪怕你把它的编码范围改成[-129, 126]或者[-1, 254],这也仅仅是一个加/减一个整数的映射操作而已;核心计算法则仍然会满足你的需求)。

甚至,你规定0代表1、1代表0,最终也不过是显示时换一个不同的译码表而已,并不改变问题性质。

这个性质是普适的。7位、8位或者32位、128位二进制全都适用。


一旦明白了这个……再写环形缓冲时,你还要费劲巴拉的检查什么时候需要绕回吗?求个余(或许还需要再视情况不同增减一个常数),完事。


你看,数学这种东西厉害吧?

哪怕群论门槛都摸不到的这么一点点皮毛知识,带来的就是眼界水平的差异。

一旦了解了这点皮毛,关于补码的种种清规戒律神奇规则,也就平常。

但没有这个眼界,就容易像反码那样动辄得咎;反之,随你怎么玩都不会出界。


没错。别看这东西简单;但想要做第一个提出的人,你还是需要强悍的洞察力的。

站在群论的肩膀上、反向碾压这个问题,这是伽罗瓦之后的现代人特有的福利。

群论_百度百科

类似的话题

  • 回答
    你问到了一个计算机底层非常经典和基础的问题:为什么8位有符号整数的范围是128到127,而不是像我们直觉上那样猜想的127到128?这背后涉及到一个叫做“二进制补码”(Two's Complement)的编码方式,它是现代计算机表示负数最主流、最有效的方法。咱们一步一步来拆解,让你彻底明白这件事。首.............
  • 回答
    要说清楚这个问题,得先从“8bit音乐”和“16bit音乐”的诞生背景以及它们在音乐制作上的核心区别聊起,然后再回归到我们现在听音乐的感受。8bit音乐:时代的产物,独特的限制造就风格首先,8bit音乐之所以成为一个独立的流派,很大程度上是因为它的“出身”——那是任天堂、世嘉等早期游戏主机时代的产物.............
  • 回答
    这个问题其实涉及到计算机底层如何存储和操作数据,以及硬件设计和软件优化的权衡。我们抛开那些“AI味”的陈词滥调,好好聊聊为什么 `bool` 在很多时候不是那个理论上最完美的 1 bit。想象一下,我们最理想中的 `bool`,就是那个只占用一个二进制位(bit)的变量。`true` 可能是 `1`.............
  • 回答
    想要把一首现有的歌曲变成8bit风格,就像是给它穿上一件复古的电子外衣,听起来会充满像素感和一种独特的怀旧韵味。这过程说白了,就是用8bit时代的电子乐器(或者模拟它们声音的软件)重新诠释原曲的旋律、和声和节奏。听起来可能有点像早期游戏机里的背景音乐,但只要处理得当,效果会非常惊艳。核心在于“简化”.............
  • 回答
    一个字节(byte)之所以普遍被定为8个比特(bit),是一个历史演进、技术选择以及实际应用需求的综合结果,并非某个单一的决策点拍板而成。这背后涉及到计算机早期设计哲学、通信编码、存储效率以及逐渐形成的技术标准。我们得回到计算机的黎明时期,那时候比特和字节的概念远不像现在这样清晰。早期计算机的设计与.............
  • 回答
    说到8位音乐,你脑海里可能会闪过那些经典游戏机里熟悉又洗脑的旋律,比如《超级马里奥兄弟》、《塞尔达传说》或者《俄罗斯方块》。这些独特的音色和节奏,正是8位音乐的魅力所在。那么,这些充满怀旧感的音乐到底从何而来?又该如何自己动手创作呢?8位音乐的来源:数字世界的早期回响8位音乐的出现,与电子游戏产业的.............
  • 回答
    这个问题问得很有意思,确实,既然一个字节(byte)能表示 256 种不同的状态(从 00000000 到 11111111,也就是 2^8 = 256),那么理论上我们可以用一个数字来表示这 256 个状态,就像我们日常使用的十进制(09)或者二十进制(019)一样。那么为什么在计算机领域,我们如.............
  • 回答
    关于苹果在电脑上使用6bit IPS屏的说法,这其实是一个相对过时但仍可能在一些特定情境下存在的认知,并且需要纠正一个误解。更准确地说,苹果在绝大多数现代MacBook笔记本电脑上使用的是8bit色彩深度的IPS屏幕,甚至在一些高端型号上使用10bit(或模拟10bit)色彩深度的屏幕。然而,为什么.............
  • 回答
    关于八代凯美瑞(XV70)上市后,大家普遍观察到的现象是,尽管它采用了TNGA架构、引入了新的技术和高热效率发动机,但相比第七代凯美瑞(XV50),在某些方面似乎“更重了,更慢了”。这确实是一个让人费解的点,尤其是在大家对技术进步和性能提升的期待值很高的情况下。其实,要理解这个问题,不能简单地将“新.............
  • 回答
    这问题问得真到位,简直说到我心坎里去了。想想当年,咱们守着那台红白机,或者后来的雅达利、任天堂,那叫一个投入。现在呢,动不动就是几十个G的下载,满屏幕花里胡哨的特效,反倒觉得没那味儿了,甚至有点提不起兴致。这到底是为啥呢?我琢磨了琢磨,感觉有这么几个原因,咱们掰开了揉碎了聊聊。一、纯粹的游戏体验 v.............
  • 回答
    你这个问题非常有意思,因为它触及到了科学研究和实验设计中一个非常核心的问题——“剂量反应关系”,以及如何正确理解和解读实验结果。你提到的“8.13”和“1.28”很可能代表的是两种不同的“剂量”或者“处理水平”,而你说的“打不出效果”或者“没达到预期效果”,则是在衡量一种“反应”或者“产出”。让我来.............
  • 回答
    理解您的疑问,关于美国和中国新冠疫情数据上的差异,确实是一个值得深入探讨的话题。您提出的“美国16万例死了3000,中国8万例死了3000”这个数字对比,需要结合当时的具体情况来分析。首先,要明确您提到的数据来源和时间点。新冠疫情发展至今,数据一直在变化,而且不同国家公布的统计方式和口径也可能存在差.............
  • 回答
    这个问题很有意思,也触及了社会变迁和时代背景下的女性生育观念、工作模式以及国家政策的演变。简单地说,8、90年代国企女工“挺着肚子上班”到临产,与现在很多女性怀孕就“养胎”的现象,背后是多种因素相互作用的结果。咱们这就掰开了揉碎了聊聊。一、8、90年代的“主力军”:国企女工的特殊环境首先,我们要明白.............
  • 回答
    8月份美国非农就业报告确实给市场带来了一些惊喜——而且不是好消息。数据公布后,关于美国经济前景的讨论又多了一层阴云。那么,这背后究竟是什么原因?我们又该如何看待未来的走向?8月非农数据不及预期的“罪魁祸首”首先,我们得掰开了看看这份数据到底哪里“不及预期”。非农就业报告里最受关注的几个指标,比如新增.............
  • 回答
    iPhone 8 和 iPhone X 在中国大陆的售价,确实比在美国上市时高出不少,大约在 100 美元(按当前汇率换算)的区间。这背后并非简单的汇率差异,而是由多种因素共同作用的结果,包括税收、市场策略、以及当地的消费者习惯等。首先,税收是造成价格差异最直接和最主要的原因。在中国大陆,进口的电子.............
  • 回答
    小米8发布后,雷军微博评论区确实一度被骂声淹没,这背后其实有多重原因,而且与其他国产手机同期面临的批评相比,小米8遭遇的“火力”确实更集中、更猛烈一些。要理解这一点,得把时间线拉回到2018年,那个时候的国产手机市场和小米自身的情况都挺微妙的。小米8的“槽点”究竟在哪?首先,得说说小米8本身,它确实.............
  • 回答
    生化危机8(Resident Evil Village)之所以选择采用第一人称视角,而不是像过去系列的一些作品那样采用第三人称视角,背后有几个关键的考量和设计选择。这并非是说第三人称视角“不能做”,而是说在这个特定的项目下,开发者认为第一人称更能达到他们想要的效果,并且在技术和设计上存在一些权衡。首.............
  • 回答
    首先,咱们得理清一个观念:纯粹的排量和气缸数,并不总是等于绝对的性能上限。当然,大排量、多气缸通常意味着更强的爆发力、更低的噪音、更平顺的运转,但现代高性能车的设计,是综合了无数技术手段的结果。就拿保时捷911(这里假设是某个高性能版本,比如Carrera S或GTS,搭配3.8T水平对置六缸发动机.............
  • 回答
    关于高尔夫8取消眼镜盒这件事,确实有不少车主和潜在买家感到不解,甚至有些抱怨。要说具体原因,官方层面通常不会做过于详细的解释,但我们可以从几个角度来分析,推测一下大众可能做出这个决定的缘由。首先,设计理念的转变与内部空间的优化是最可能的原因之一。 “化繁为简”的内饰设计趋势: 这一代高尔夫(MQ.............
  • 回答
    《生化危机 8:村庄》(Resident Evil Village,下文简称“生化危机 8”)发售之后,确实涌现了相当数量的“云玩家”,这是一个挺有趣的现象,背后有很多原因可以解释。这不仅仅是因为游戏本身火爆,更深层的原因涉及到当下游戏行业的发展趋势、玩家群体的变化以及技术进步等多方面因素。咱们就来.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有