为何4个字节int取值范围是-2^31 到2^31 - 1

前言

最近突然想起之前去参加的一次面试经历,是一家还算不错的公司,怎么说呢,因为公司是做工具类软件的,电脑端网页端手机端都有,软件的用户量达到了3.5亿之多,由于面向用户主要是欧美,因此在国内知道的人就很少。我面试的是iOS岗位,不过一开始技术面试一上来问了我一堆计算机基础底层的问题,其中一个问题就给我留下深刻的记忆,为啥呢?因为他问我int为4个字节时取值范围是多少,他听到我答案后,斩钉截铁的说我肯定错了,但是呢,这个算是属于计算机的常识问题吧,我从大学到现在六七年,一直理解的是这个答案,这次面试却有人态度异常坚决的说我肯定错了。于是,我面试完后回家后就好好的在网上和书上找了答案,果然,我是对的,他是错的。这个给我感触很大,让我感觉到,现在工作中,应该还有很多他这样的人,对一个知识的认知是错的,并且还是那么的坚定。因此,我认为,计算机的知识一定要知其所以然,不然真的很可能坚持一个错误的认知好多年。

正文

如果您现在觉得这个问题的答案不是这个的话,那么您就很有必要好好的看下我接下来写的内容了。如果您现在知道是这个答案,但是不知道为啥会是这个值的话,也可以好好看看我接下来写的。如果你已经知道的原因,其实你也可以看看,说不定能对你有更深层次的理解。

解释

刚说的4字节的int默认是在C/C++语言中的,并且我们默认说也是有符号的int,也即是包括负数的。

解释这个问题之前,我得先给大家说明一个小知识。众所周知,一个字节等于8位(1 byte == 8 bit),为何会有位这个东西呢,原因很简单,这是由于如今大家用到的计算机CPU以及所有集成电路,都是只能识别电信号的,在这些计算机硬件的眼中,电信号可分为两种,一种是高电平,一种是低电平,高电平表示1,低电平表示0,这就是为啥计算机会只能识别0、1这种二进制数的原因了(当然量子计算机除外)。在CPU中,接收高低电平的硬件叫做管脚,就例如我们常说的内存条金手指,一根管脚能接收一个0、1,由于CPU内部的设计,每多一根管脚,能表示的数值是以指数增长的,也就是如果是16根管脚的话,那么CPU的寻址能力就是2^16,同理,32根就是2^32。说到这儿,大家应该也终于是知道了我们常说的位数是啥玩意了吧,没错,就是CPU的管脚数(当然这个仅仅是CPU的位数,操作系统同样也是有位数的,当一台电脑的CPU和系统都是64位的是才能真正完全发挥出64位的功效)。

无符号

这里我们得先好好的说说无符号类型,当一个无符号类型的8位,也就是一个字节时,能表示的数值个数是多少?是滴,就是2^8,不过呢,我也可以好好的解释下这个,当为一位的时候,能表示的数值是0和1,也就是2^1,当为2位时,能表示的数值是0、1、2和3,也就是2^2,以此类推,8位能表示的数值个数就是2^8,相信你对这个不会有什么异议吧。再来,8位能表示的最大值是多少?  答案是,2^8 - 1 ,也就是255,这个同样可以解释下,当为一位时,最大值的二进制就是1,也就是十进制的1,也就是2^1 - 1,当为2位时,最大值的二进制是11,也就是十进制的3,也就是2^2 - 1,同理,8位时,最大值的二进制是8个1,也就是1111 1111,算下来的十进制数值就是255,也就是2^8 - 1。8位能表示的最小值想必都知道,就是八个0,也就是0。

这样子算下来,刚好,从0到2^8 - 1,每一个数值唯一的对应着相应的一个二进制值表示。这里的意思就是,0刚好唯一对应着0000 0000这个二进制数,255刚好唯一对应着1111 1111这个二进制数。这样子,每个数值都唯一对应着一个二进制数表示,所以每个数值都可以很好很和谐的存储着。

有符号

有了上面对无符号的解释,接下来才能好好的给你解释有符号的情况。

所谓的有符号,意思就是,有负数。为啥有负数时,会说成是有符号呢?那我就问你一个问题,按照刚刚上面对于无符号的解释后,你觉得同样是一个8位的数值,同样是每个位只能识别0、1的CPU,他到底要怎样才能知道这个二进制数表示的是正数还是负数呢?前人们也想到这个问题了,为了解决这个问题,就引出了一条规定,当有负数存在的时候,将最高位作为标记位,当标记位为1时,就代表这个数值是个负数,当标记位为0时,就是正数。这个标记的数值刚好决定了是正数"+"这个符号,还是负数"-"这个符号,这就是有符号出现的原因。

为何8位取值范围是-2^7 到2^7- 1

首先我来给大家说一个错误的答案解释。大家现在已经知道了,当有负数的时候,最高位是符号位,也就是说,现在这八位里能真正拿来存储值的位数只剩七位,也就是最大值是2^7 - 1再加上符号位的话,也就是,-2^7 - 1到2^7 - 1。大家看到这里是不是感觉很有道理啊?是不是并感觉不到有错呢?如果你感觉这个很合理的话,你现在的理解就是跟面试我的那个人的想法差不多了,这可能也是为啥他到现在还那么坚定的认为自己是对的的原因吧。

其实呢,出现这样一个错误的认识,最主要还是归结于自己没认真去想过这个问题。现在我就给大家说下,这样的理解存在一个很致命的问题,那就是,0的表示,你会发现,当负0时,对应的二进制数值是1000 0000,当正0时,对应的二进制数值是0000 0000,不知道看到这儿,你有没看出端倪,无论看没看粗来,我都得说粗来,那就是,本来数学上,正0和负0表示的是同一个数,然鹅在这种情况下,正0和负0表示了两个数值,也就是计算机内部用了两个二进制数来表示和存储这两个0,现在是不是觉得恍然大悟呢?

原码和补码

前人大神们为了解决这个问题,就想出了一个绝妙的方法,那就是补码。说到补码的话,就不得不提到的是原码和反码,为啥要说这两个东西呢,因为补码是通过原码和反码算出来的。前人们规定,原码就是那个数值直接算出来的二进制数,例如,1的原码就是,0000 0001,-1的原码就是,1000 0001。反码呢,就是,除符号位外,其他所有位取反,例如,-1的反码就是,1111 1110。我这里为啥不提1的反码呢,因为还有一条规定就是,正数的反码和补码都是他的原码,为啥这么规定呢,其实想想都知道原因很简单,那就是,本来正数的二进制数表示都是一一对应的,因此就没必要再大费周章了,当然,如果不这么规定的话,最终补码的形式也是不会一一对应一个二进制数的。

接下来就是补码了,反码算出来了,补码就很简单了,那就是反码加1,例如,-1的补码就是,1111 1110 + 1 = 1111 1111。这里提个注意点,就是,在计算补码的时候,最高位也是会参与计算的,也就是说,如果反码是1111 1111的话,补码 = 1111 1111 + 1 = 1 0000 0000,这里最高位1已经超出了八位,也就是常说的溢出了,那么就直接忽略了,也就是最终结果是0000 0000,大家猜猜这个补码的原码是誰?没错,就是负0,之后再看看正0,我们说过正数的原码、反码和补码都是原码,也就是0000 0000,看见没,这种情况下,正负0都是同一个二进制表示。这就已经很好的解决了0的问题。

冲突解决

这里就开始好好的跟大家说说这个取值范围。

首先,先说一一对应的事,大家已经知道了,当有负数存在的情况下,最高位是符号位,之后再根据原码、反码和补码的一系列规定和计算,有一点是可以确定的,那就是,负数和正数的二进制表示绝对不可能冲突,意思就是不会存在一个负数的二进制表示和某个正数的二进制表示是一样的,就是因为,负数补码的最高位永远是1,正数补码的最高位永远是0,能让负数补码最高位为0的情况,只有一种,那就是0的时候。为0时刚好就解决了正负数存储时正负0二进制表示不一致的问题,这也是补码的一个作用之一。之后就是正数的一一对应,之前也说过,正数情况下,补码就是原码,所以正数是肯定不可能存在正数之间的数值冲突的。最后再说负数,由于,原码时,每个二进制都是一一对应的(跟正数同理),那么负数的所有反码也都是肯定一一对应的,如果大家理解不了,可以自己试试用二进制看看,你会发现,无论是原码还是反码,每个数值的表示,肯定至少有一位跟其它任何数值都不一样,这就证明的唯一性。既然原码、反码都具有唯一性了,那么再加上一个1的话,仍然具有唯一性。

有符号8位的取值范围

费劲千辛万苦,终于来到了这里。首先,我们这里再回顾一下,就是,当存在符号位时,8位能表示的最大值就是111 1111,也就是7个1,也就是2^7 -1, 所以正数的范围就是0到2^7 - 1,负数就是-(2^7 - 1)  到-1,但是,这样算下来的话,总共表示的数值个数是2^7 + 2^7 - 1 = 2^8 -1,这可是比8位能存储的2^8这个数值少一个呢,这样不就活生生的浪费了一个麽?不知大伙有没注意到一个情况,那就是当为负数时,补码是1000 0000时,我们通过这个补码反向算得反码是0111 1111,原码就是0000 0000是不是感觉很诡异,这不是正0吗?言下之意就是说,补码1000 0000这个二进制位压根不可能有,这就是刚说的存储二进制位中少的那个。但是呢,由于1000 0000本身代表的是128,再加上最高位为1,那么就是个负数,再加上所有的二进制表示又少了一个,因此,1000 0000就顺理成章的成了-128,当然,1000 0000是补码,它没有原码和反码。最后再加上一个计算机内部对负数的运算方式吧,就是对负数整体取绝对值,之后取反加1,算下来就是:-128(取绝对值) -> 128(变成二进制表示) ->1000 0000(取反)->0111 1111(加1) -> 1000 0000(补码)。-127(取绝对值)->127(变成二进制表示)->0111 1111(取反)->1000 0000(加1)->1000 0001(补码)。-126(取绝对值)->126(变成二进制表示)->0111 1110(取反)->1000 0001(加1)->1000 0010(补码)。你会发现,计算机内部对负数补码的运算的结果和我们之前说的运算结果是一毛一样的,从这里也就能清楚的看到,-128通过计算机内部运算之后的补码就是1000 0000

为何4个字节int取值范围是-2^31 到2^31 - 1

这里大伙应该就能清楚明白的知道为何4个字节int取值范围是-2^31 到2^31 - 1了吧。

你可能感兴趣的:(为何4个字节int取值范围是-2^31 到2^31 - 1)