美文网首页
由byte取值[-128,127]理解Java基本数据类型及其运

由byte取值[-128,127]理解Java基本数据类型及其运

作者: 子期归否 | 来源:发表于2019-02-19 15:11 被阅读0次

    byte占8个字节,最高位为符号位,最大值是[0111111],最小值是[1111111],转成10进制,所以byte取值范围是[-127,127]?

    在开始这个问题的时候,我从没想过里面的逻辑跟我之前的认识完全不同,例如我以为先有反码而后有补码,或者补码等于反码加1之类的说法。查询了一些网上的资料大都雷同,但是当我深入了解时,却发现更我想的完全不一样。

    计算机是怎样运算的?

    计算机只会加法。事实是计算机的减法和乘除都是借用加法和位移来实现的,例如:3 - 5 = 3 - (-5);3 x 5 相当于将3重复加上5次,或许有的处理器有一些位移的运算;而3/5,除法又可以转为减法,如3一直减去5,等于0,余3。但是不管怎么说,计算机必须引入正负号来实现运算。

    正数的符号位为0,负数的符号位为1

    回到原码:
    3 + 5 = 00000011 + 00000101 = 00001000 = 8 这个是对的。
    -3 - 5 = -3 + (-5) = 3 + (-5) = 10000011 + 10000101 = 00001000 = 8 这个有点问题,但是问题不大,符号出错。
    3 - 5 = 3 + (-5) = 00000011 + 10000101 = 10001000 = -8 出错!

    当原码来做正负数加法时,正正相加没有问题;负负相加问题也不大,它的数值方面是正确的,在符号位我们可以强行把符号置为1来解决它;但是一正一负相加时,数值出错。

    补码

    补码的思想很平易近人

    补码的思想来自时钟。
    如果当前是三点,那么什么时候时针会指向一点,很简单,把时针往回转两个小时,或者把时针往前转10个小时,因为时钟一圈只有12个小时。

    那么我们就可以得出,在一圈12个小时的情况下:3 - 2 = 1 = 3 + 10 = 13,13点在时针上跟1点指的是同一个点,也就是说减2和加10等价,而 2 + 10 刚刚好等于12。

    这个12被称为模。在模为12的情况下,-2的运算,统统可以用+10来代替,-2和10则互为补数,也就是说互为补数的两个不同数绝对值之和等于模的值,并且模范围内的数,它的自身也是自身的补数,因为在运算中,你自己当然可以替代自己,虽然没人这么做。
    那么在模确定的情况下,我们永远可以找到一个正数代替负数来进行加法运算,但是这里有一个小问题,如下:

    3 - 2 = 3 + 10 = 13 ,但是13终究!= 1,但是在12为模的情况下13和1确实是又是同一个点,这里:

    • 1 = 13 = 25 = 37 = 1 + 12* n;
      12是模,那么假设X<0, X的正补数= (模 + X)(mod 模)
    补码的概念由补数而来。
    一个正数虽然可以找到它的负补数,但是在计算机运算中没有意义,所以正数的补码就是它自身
    一个负数的补码就是它的正补数
    

    到这里,我们终于引出了补码的概念。
    对于一个整数x(注意是整数,小数这篇文章不涉及),它的补码的定义:

    这里的n指的是二进制下的位数,如byte n=8,int n=32

    虽然这是从计算机组成原理书上截取下来的,但是读到这里,应该水到渠成了。
    减法运算的解决方案似乎(注意这个似乎!!!)已经找到了,就是找负数的补码代替运算。

    实例

    以byte数做运算,一个byte等于8bit,得出模为2^(8+1),那么在byte范围内进行减法运算,从之前的补码概念中,很容易就可以实现减法,例如:
    3-2 = [00000011] + [10000101] = 3 + (2^(8+1) - 2) = [00000011] + ([1000000000] - [00000010]) =[00000001] = 1;

    不要关注溢出的高位,byte8位,高于8位直接舍弃,就像13点,溢出高位12点,结果就等于1点,这是补码的核心

    上面的3-2的运算,实际上没有完全解决减法的问题,因为补码的获得如上是通过2^(8+1) - 2,出现了减2的操作

    这就尴尬了,补码就是为了避免减法,使用补码来做加法代替减法,但是获取补码的过程中又出现了减法,怎么能不使用减法来获取一个负数的补码呢,像2^(8+1) - 2的结果能通过别的方法拿到吗,反码终于派上用场了!

    反码

    还是通过byte举例:
    假设一个负数 X = 1X1X2X3X4X5X6X7,求他的补码,根据补码的定义得出模 28+1 = 1000000000;
    所以: 28+1 + (X)= 1000000000 + 1X1X2X3X4X5X6X7 = 111111111 - 0X1X2X3X4X5X6X7 + 00000001 = 1\underline{X}1\underline{X}2\underline{X}3\underline{X}4\underline{X}5\underline{X}6\underline{X}7 + 00000001;这一步的计算很重要,可以自己算一下。
    Xi\underline{X}i的关系互为0,1,意思是X如果为0,\underline{X}就为1,反之亦然,很容易得出结果,当拿1去减Xi时,当Xi等于1,结果等于0,当Xi等于0,结果就等于1,总是和Xi相反。

    这里我们得出了结论,一个负数的补码可以这样获得,即符号位不变,其他位置按位取反,然后再加1.

    而我们把符号位不变,其他位置按位取反之后的结果称为反码
    反码就是获取补码中间的一个过渡,它既不重要也不关键
    

    到这里我们就可以完全避开减法了。

    这里可以总结减法:
    A - B = A[补] + (-B)[补] = 结果;这里有一个结果转换,补码加法运算完的结果=>
    如果是负数要转成原码,与获取补码的过程相反,就是先减去一,然后符号位不变,其他位按位取反。
    如果是正数,因为正数的补码就是自己,所以不用转换
    

    这里就回到开头提出的问题,一个byte数,最大值和最小值的问题,最大值就是01111111,而最小数本该是11111111?

    但是由于计算机里存储的和运算的都是补码

    那么就存在一个问题 => 在原码中,8位bit所能表示的数来说,有这样两个数:00000000和10000000,表示+0和-0,没有问题,在原码的理解上10000000就是代表-0,在反码的理解上它代表-127,但是计算机理解10000000这个数时,它表示补码,为了不浪费编码位置,因为+0和-0都代表0,所以人为规定了10000000这个数代表-128

    我不想创造逻辑去解释为啥补码10000000代表-128,在我看来,这就是人为规定的编码,因为从逻辑上看,一个8位有符号数,补码为10000000的原码还是10000000,原码中这就是-0,为了不浪费,规定它代表-128.

    同样,32的int数,10000000 00000000 00000000 00000000 代表 -232,其他位也一样。

    总结

    没想到竟然是人为规定的结果,但是如果你不懂补码的来历,不懂补码的逻辑,你或许就不知道为啥可以强制规定了。

    相关文章

      网友评论

          本文标题:由byte取值[-128,127]理解Java基本数据类型及其运

          本文链接:https://www.haomeiwen.com/subject/tgkmeqtx.html