zoukankan      html  css  js  c++  java
  • 计算机的原码,反码,补码以及浮点数的一些知识.(转帖)

    参考连接:https://www.cnblogs.com/zh-dream/p/11663569.html

    一、正数

      正数的源码、反码、补码都是相同的。

    二、负数

     1、负数的源码与反码的相互转换

        负数的源码转换为反码:符号位不变,数值位按位取反。

        例如: 

    源码:1000 1100
    反码:1111 0011

        负数的反码转换为源码:符号位不变,数值位按位取反。

        例如:

    反码:1011 0101
    源码:1100 1010

     2、负数的源码和补码的相互转换

        负数的源码转换为补码:1、先转换为反码(符号位不变,数值位按位取反)2、在反码的基础上末位加一。

           例如:

    源码:1010 0101
    反码:1101 1010
    补码:1101 1011

       负数的补码转换为源码:符号位不变,数值位按位取反,末位加一。

       例如:

    补码:1110 1011
    补码取反:1001 0100
    源码:1001 0101

    3、负数的反码和补码的相互转换

       负数的反码转换为补码:末位加一

         例如:

    反码:1100 1110
    补码:1100 1111

         负数的补码转换为反码(源码的反码):末位减一

         例如:

    补码:1100 1110
    反码:1100 1101 (借位减)

    4、正数取反

      先将正数源码按位取反,得到一个负数,由于负数以补码形式存在,再转换为负数的反码,最后末位加一得到补码。

      例如:

    0000 1100 #源码
    1111 0011 #正数取反得到负数的源码
    1000 1100 #负数的源码取反后得到反码
    1000 1101 #反码加一得到补码

    计算机反码存在的必要:

    数值在计算机中bai表示形式为机器数du,计算机只能识别0和1,使用的是二进制zhi,而在日常生活中人们使用的是十dao进制,"正如亚里士多德早就指出的那样,今天十进制的广泛采用,只不过我们绝大多数人生来具有10个手指头这个解剖学事实的结果.尽管在历史上手指计数(5,10进制)的实践要比二或三进制计数出现的晚."(摘自<<数学发展史>>有空大家可以看看哦~,很有意思的).为了能方便的与二进制转换,就使用了十六进制(2 4)和八进制(23).下面进入正题.

    数值有正负之分,计算机就用一个数的最高位存放符号(0为正,1为负).这就是机器数的原码了.假设机器能处理的位数为8.即字长为1byte,原码能表示数值的范围为

    (-127~-0 +0~127)共256个.

    有了数值的表示方法就可以对数进行算术运算.但是很快就发现用带符号位的原码进行乘除运算时结果正确,而在加减运算的时候就出现了问题,如下: 假设字长为8bits

    ( 1 ) 10- ( 1 )10 = ( 1 )10 + ( -1 )10 = ( 0 )10

    (00000001)原 + (10000001)原 = (10000010)原 = ( -2 ) 显然不正确.

    因为在两个整数的加法运算中是没有问题的,于是就发现问题出现在带符号位的负数身上,对除符号位外的其余各位逐位取反就产生了反码.反码的取值空间和原码相同且一一对应. 下面是反码的减法运算:

    ( 1 )10 - ( 1 ) 10= ( 1 ) 10+ ( -1 ) 10= ( 0 )10

    (00000001) 反+ (11111110)反 = (11111111)反 = ( -0 ) 有问题.

    ( 1 )10 - ( 2)10 = ( 1 )10 + ( -2 )10 = ( -1 )10

    (00000001) 反+ (11111101)反 = (11111110)反 = ( -1 ) 正确

    问题出现在(+0)和(-0)上,在人们的计算概念中零是没有正负之分的.(印度人首先将零作为标记并放入运算之中,包含有零号的印度数学和十进制计数对人类文明的贡献极大).

    于是就引入了补码概念. 负数的补码就是对反码加一,而正数不变,正数的原码反码补码是一样的.在补码中用(-128)代替了(-0),所以补码的表示范围为:

    (-128~0~127)共256个.

    注意:(-128)没有相对应的原码和反码, (-128) = (10000000) 补码的加减运算如下:

    ( 1 ) 10- ( 1 ) 10= ( 1 )10 + ( -1 )10 = ( 0 )10

    (00000001)补 + (11111111)补 = (00000000)补 = ( 0 ) 正确

    ( 1 ) 10- ( 2) 10= ( 1 )10 + ( -2 )10 = ( -1 )10

    (00000001) 补+ (11111110) 补= (11111111)补 = ( -1 ) 正确

    所以补码的设计目的是:

    ⑴使符号位能与有效值部分一起参加运算,从而简化运算规则.

    ⑵使减法运算转换为加法运算,进一步简化计算机中运算器的线路设计


    参考连接:https://zhuanlan.zhihu.com/p/75581822

     
    首发于大话 CS

    一直迷糊的浮点数

     

    之前写了 进制转换,也写了计算机怎么 存整数,那么问题来了,计算机中小数怎么存呢?比如2019.725怎么放到计算机里呢?-2019.725又该怎么办呢?

    我们有什么

    首先想一下我们有什么,计算机怎么存数。假如我们的计算机是存 32 位的,那么它就长下边的样子。

     

     

    由于我们写数字,习惯于先写高位,所以把最低位画到了最右边。

    我们需要做的就是把01放到上边的方框中,来表示2019.725。换言之,我们就是需要制定一个规则,把2019.725转成01字符串存进去。然后读取的时候,再把01字符串转成2019.725显示出来。

    思路一

    简单粗暴些,我们人为的把32个位置分成三部分,最高位0表示正数,1表示负数,然后是整数部分,再然后是小数部分。

     

     

    10 位存小数部分,接下来的 21 位存整数部分,最高 1位存符号位。所以我们只需要把2019725分别转成二进制,放进去就可以了。二进制位数不足的,高位补零就可以。

     

     

    仔细想想,规则还不够完善,如果存10.310.03,小数部分怎么存呢?肯定不能都存3,这样两个数就不能区分了。这里我想到两种方案:

    倒序

    小数部分我们逆转以后去存,10.3小数部分存310.03小数部分存30

    补零

    我们的小数部分是10个比特位,所以最多存 [公式] 个数,十进制的话就是0 - 1023。为了方便,把1000以上的数省略掉,范围变成000-999,也就意味着我们能精确的表示小数点后三位的数,如果一个数小数位数不足3位,我们就用零补齐。

    10.3的话,可以看成10.300,小数部分存30010.03的话可以看成10.030,小数部分就存30

    能表示的数字范围

    整数部分我们有21个比特位,能表示的数是 [公式] 个,十进制的话就是 0 - 2097152

    小数部分如果用的倒序方案,那么我们的范围就是0 - 1023

    综上,数字范围就是 -2097152.1023 ~ 2097152.1023

    应用

    MySQL 中的定点数DECIMAL就是采取的上边的思想,将整数部分和小数部分分别存储。不同之处是MySQL采用了变字长存储,根据十进制的大小,利用不同的字节去存储,所以理论上它能存的范围是无限的。

    详细的介绍可以参考 你可能不知道的MySQL中的定点数类型

    思路二

    上边的方案有一个问题,表示的数字的范围有些小,最大也才两百多万。限制范围的原因就是,上边的方案整数部分只用了21个比特位去存。

    换一种思路,我们通过移动小数点把小数转成整数,然后再记录小数点移动的位数。

    我们用3个比特位来记录移动的位数,这样我们就可以把小数点移动7位。此外,整数部分就可以有28个比特位来表示了,范围大大增加。

     

     

    其实可以看做是科学计数法的形式,比如2019.725,可以看做是 [公式] ,我们只需要把 2019725和指数3存起来就可以了。

     

     

    能表示的数字范围

    28个比特位来表示整数部分,能表示的数的个数就是 [公式] ,对应的十进制范围就是0 - 268435455

    又因为最多可以移动 7 位的小数,所以数字范围就是下边的

    -268435455.0000000 ~ 268435455.0000000

    可以看到比之前的方案扩大了两个数量级,精度也提高了。但是我们要意识到一件事情,两种方案都用了32个比特位,所以最多就是表示 [公式] 个数。思路二表示的范围虽然扩大了,并不是这个范围内的所有数都能精确表示,能表示的数的范围是0 - 268435455,也就是能表示的有效数字最多只有九位。

    对于思路一的最大数2097152.1023,如果用思路二,虽然它在-268435455.0000000 ~ 268435455.0000000之内,但是由于它的有效数字位数是11位,但我们最多存储9位的,所以我们只好把小数点后的最后两位舍去,存储2097152.10

    应用

    对于C#中的System.Decimal是用的类似于思路二的思想,具体的话参考 没有神话,聊聊decimal的“障眼法”

    思路三

    上边的思路一和思路二都是站在十进制数的角度上先考虑对数字的分割、转换,然后将十进制的整数转为二进制进行存储的。再换一种思路,先把十进数转成二进制数,再去存呗。

    对于2019.725转成二进制就是11111100011.1011100110...,为啥出现了省略号?进制转换中已经讨论过这个问题了,那就是大部分十进制数并不能精确的转到二进制,这是一个固有的事实,我们也没啥办法,假装没有省略号,继续讨论吧。

    尾数部分

    这里存储的话,可以借鉴思路二,我们可以把二进制小数转换成科学计数法的形式,统一规格再去存储。这里用个小技巧,我们知道所有的数字除了0之外,一定会含有一个1,所以我们把数字转成下边的形式。

    [公式]

    为什么要用上边的形式呢,这样做的一个好处就是存的时候,我们只需要存xxxxxxxxx...的部分,显示数字的时候再考虑它的最高位还有一个1。这样如果用23个比特位来存数字,相等于存储了24位。

    指数

    区别于思路二的指数,思路二我们是把原来的小数转为整数,所以指数一定是个负数,直接存它的绝对值就行。但在这里如果之前的数字是 0.000001这样的,那么指数E是负数,但如果是1110011.1这样的,指数E就是正数了。

    如果指数E是用8个比特位来存储,那么总共就是 [公式] 个数,范围就是0 - 255,那么怎么存负数呢?最简单的方案,人为规定呗,将一部分正数映射到负数上,就对半分吧。

    0    1        125  126  127 128 129      254 255
    -127 -126 ... -2   -1   0   1   2    ... 127 128

    要表示的指数加上127映射到0 - 255。反过来显示指数真正值的时候,需要减去127

    综上所述,我们就用 1 个比特位来存符号位,8个比特位来存指数,存的时候记得加上偏移,剩下的23个比特位来存有效数字,而事实上我们其实可以看做存了24位。

     

     

    这样的话,求出2019.725的二进制是 11111100011.1011100110011。把它规格化,小数点需要向左移动10位,也就代表指数是 10,存的话还需要加上127,也就是137了,二进制表示是10001001。所以规格化就是下边的数了:

    [公式]

    所以计算机中就是下边这样存的了。

     

     

    特殊数字

    我们还有个问题没有解决,上边规格化的时候我们默认了所有数字至少有一个1,但是0怎么办呢?规则是我们自己定的,继续定呗。

    当指数是-127时,一个很小很小的数了,此时对应的指数加上127,也就是0。我们规定当E0并且M全为0,我们就说这个数字是0

    -127用了以后,负指数最小就是-126了。

    让我们再规定几个特殊的数字。

    当指数是128时,一个很大很大的数,也就是当 E 全为1。分几种情况。

    • 有效数字M每一位全为0
    • S0,就代表正无穷。
    • S1,就代表负无穷。
    • 有效数字 M 每一位不全为 0,代表NaN,表示不是一个数字。

    所以此时正指数最大就是127了。

    能表示数的范围

    最大数和最小数

    最大的数,指数最大取1111 1110,也就是254,减去127,代表指数是127。有效数字全部取1,也就是1.111...1,小数点后边231。然后把它转成十进制的话,用一个技巧,先给它加 [公式] ,这样所有的1都产生进位,变成了10.0000000,这样的话转成十进制就是2了,当然我们还要减去 [公式]

    综上,最大的数字用十进制表示就是 [公式] ,也就是[公式]

    所以能表示的范围就是 [公式][公式]

    再看一下能表示的最小正数。指数取最小为 0000 0001,减去127,代表指数是-126。有效数字全部取0,其实也就代表1.000...0000,总共有230,转成十进制的话就[公式] ,也就是 [公式]

    有没有发现一个问题,我们要找最小的数,但因为之前的规定,最高位却能是1。但理论上我们找一个0.000...1这样的数才代表最小的正数。怎么得到这样的数呢?

    再加个规则吧。当时我们规定当E0的并且M每一位全为0,我们就说这个数字是0。那M不全为零呢?

    之前是假设省略了最高位1,我们加一个新规则,当E全为0时,我们就说最高位不再省略1。所以最小的尾数就可以是0.00...1了,小数点后22个零,转成十进制就是 [公式] 。指数还是之前的-126,所以比之前更小的正数就是 [公式] ,也就是 [公式]

    精度

    我们用23个比特位表示尾数部分,最多能表示 [公式] 个数,也就是8388607,从十进制的角度,所以它的有效数字最多有7位。

    应用

    上边介绍的其实就是IEEE 754标准了,计算机中存储32位浮点数基本上都是这个规则。比如java中的float类型,可以看一下jdk源码中的一些定义。

    /**
         * A constant holding the largest positive finite value of type
         * {@code float}, (2-2<sup>-23</sup>)&middot;2<sup>127</sup>.
         * It is equal to the hexadecimal floating-point literal
         * {@code 0x1.fffffeP+127f} and also equal to
         * {@code Float.intBitsToFloat(0x7f7fffff)}.
         */
    public static final float MAX_VALUE = 0x1.fffffeP+127f; // 3.4028235e+38f
    
    /**
         * A constant holding the smallest positive normal value of type
         * {@code float}, 2<sup>-126</sup>.  It is equal to the
         * hexadecimal floating-point literal {@code 0x1.0p-126f} and also
         * equal to {@code Float.intBitsToFloat(0x00800000)}.
         *
         * @since 1.6
         */
    public static final float MIN_NORMAL = 0x1.0p-126f; // 1.17549435E-38f
    
    /**
         * A constant holding the smallest positive nonzero value of type
         * {@code float}, 2<sup>-149</sup>. It is equal to the
         * hexadecimal floating-point literal {@code 0x0.000002P-126f}
         * and also equal to {@code Float.intBitsToFloat(0x1)}.
         */
    public static final float MIN_VALUE = 0x0.000002P-126f; // 1.4e-45f

    可以看到float的最大值,和正数的两个最小值和我们分析的完全一致。

    当然,上边的我们分析的是32位的情况,64位的话和32位基本是一样的,对应于java中的double类型。

     

     

    除了位数变多以外,其它的分析和32位都是一样的,此时指数不再加127,而是加1023

    一些问题

    0.1 + 0.2 == 0.3 //false

    这样就可以理解上边的经典问题了,之所以两边不相等,第一个原因就是大部分十进制并不能精确的转为二进制,第二个原因就是存储的位数有限,会进行一些舍去。

    如果对于一些能精确表示的浮点数,判断相等就不会出问题了。

    0.125 + 0.125 == 0.25 //true

    另一个问题,当我定义一个变量

    float a = 0.1;

    既然不能精确表示0.1,那么我们把它输出的时候为什么显示的是0.1

    进制转换 中我们算过,0.1 转成二进制的话是0.0 0011 0011 0011...,因为我们只能存23位有效数字,所以我们只能存0.000 1 1001 1001 1001 1001 1001 101

    因为我们最高位可以省略个 1,所以就是第一个1后边有23位。

    此外,最后 3 位本来是100,因为100后边一位是1,所以相当于产生进位变成101

    这个二进制数还原为10进制的话就是0.10000000149011612

    让我们来验证一下

    System.out.println(String.format("%.18f", 0.1f));
    //0.100000001490116120

    和我们预想的完全一样,而如果不加限制直接输出0.1,显示0.1只是因为默认的有效位数比较少,其实只是输出了近似值0.1

    结束

    浮点数的探究到此结束了,回顾一下,前两种方法我们先从十进制的角度来考虑,通过存整数来存小数,从而保证了我们可以精确存数。思路三,我们先把十进制转成二进制去存,这就产生了不能精确的存储数的问题。同时有两个技巧也值得注意,第一个就是用存储指数的时候,我们用正数表示负数。第二个就是,默认规定最高位是1,从而用23个比特位存了24位的数据。

    编辑于 2019-07-28
    计算机组成原理
    浮点数
    IEE754
     

    文章被以下专栏收录

    大话 CS
    大话 CS
    更新一些学计算机过程中可能遇到的疑惑。

    推荐阅读

    详解二进制浮点数

    详解二进制浮点数

    (二十六)通俗易懂理解——浮点与定点的计算机表示及互转

    在神经网络当中,为了尽快落地就需要考虑到数据存储以及速度问题,这时候将浮点数转为定点数就是一种比较常规的做法,也就是涉及到Binary neural networks和quantization,这部分有待下一篇…

    浮点数精度问题透析:小数计算不准确+浮点数精度丢失根源

    在知乎上上看到如下问题:浮点数精度问题的前世今生?1.该问题出现的原因 ?2.为何其他编程语言,比如java中可能没有js那么明显3.大家在项目中踩过浮点数精度的坑?4.最后采用哪些方案规避…

    动手做计算机CPU的核心部件-二进制加法器

    动手做计算机CPU的核心部件-二进制加法器

    11 条评论

    • cat
      cat2019-10-09
      10.3的话,可以看成10.300,小数部分存300,10.03的话可以看成10.030,小数部分就存30。


      这个为什么存30 这样可以表示030吗
    windliang
    windliang (作者) 回复cat2019-10-09
    因为假设了只有3位小数
    cat
    cat回复windliang (作者) 2019-10-10
    迷迷糊糊的
    windliang
    windliang (作者) 回复宇宙之王小马哥2019-10-15

    哈哈,慢慢学就懂了

    windliang
    windliang (作者) 回复熊镇2019-10-15

    自己规定的,想多少多少,只是一个思路

    windliang
    windliang (作者) 回复资深教授2019-11-25

    哈哈,感谢支持

      • 我是大一新生 我好痛苦呀 求作者安慰
      • 熊镇
        熊镇2019-10-15
        思路一,为什么后10位是存小数,往前21位是存整数
      • 熊镇
        熊镇2019-10-15
        哥,我也是大一的,听到这个浮点数就不懂了
      • 资深教授
        资深教授2019-11-25

        忍不住评论下,写的真好。多看几遍。

      • 作者写的很棒哦,我之前一直以为指数是采用补码表示的呢,没想到竟然是直接粗暴的加127
  • 相关阅读:
    macOS下iTerm2+zsh+oh-my-zsh+powerlevel10k打造最强终端
    个别服务器通过公网登录MySQL数据库慢
    Linux永久添加静态路由
    mtr网络工具常用命令
    Fping常用的网络连通性测试技巧
    Linux 7修改网卡名称后配置文件中的默认网关不生效
    Mac TimeMachine备份数据到自建NAS(通过samba共享)
    Mac iTerm2使用lrzsz
    SSH登录服务器慢
    Linux服务器惨遭挖矿
  • 原文地址:https://www.cnblogs.com/sidianok/p/13219347.html
Copyright © 2011-2022 走看看