美文网首页
C 语言学习(11) ---- 进制转换

C 语言学习(11) ---- 进制转换

作者: 特立独行的佩奇 | 来源:发表于2023-05-10 10:55 被阅读0次

    进制转换

    十六进制/十进制/二进制的互转方式如下


    进制转换.jpg
    1. 二进制和十进制的互转
      十进制转二进制采用除2取余数,逆序排列的方法
      二进制转十进制每遇到一个二进制的1,乘以相应的阶数(阶数等于这一位后有多少二进制位)最后相加得到结果


      二进制互转十进制.jpg
    2. 二进制和十六进制的互转
      每四个二进制位代表一个的十六进制位,二进制转十六进制每四位合并为一位,十六进制转二进制每一位展开为4个二进制位


      十六进制互转表.jpg
    1. 十进制和十六进制互转
      十进制转十六进制,除以16,取得到的余数,最后逆序排列
      十六进制转十进制,每遇到一个16进制位,乘以相应的阶数(阶数等于这一位后有多少二进制位),最后相加得到结果


      十进制和十六进制互转jpg.jpg

    正数和负数的二进制表示

    原码:如果想要表示有符号整数,就要将最前面一个二进制位作为符号位,即 0 代表正数,1代表负数,后面 7 位为数值域
    反码:正数的反码与其原码相同;负数的反码是对其原码逐位取反,但符号位除外
    补码:正数的补码与其原码相同,负数的补码是在其反码的末位加1

    引入补码的原因:
    如果负数和正数都采用原码的方式,仅使用符号位区分,数学上,1 + (-1)=0,换算成在二进制中00000001+10000001=10000010,换算成十进制为-2,显然出错了,所以原码的符号位不能直接参与运算,必须和其他位分开,需要设计两种加法电路加以区分,这就增加了硬件的开销和复杂性
    所以引入补码是为了解决计算机中数的表示和数的运算问题,使用补码,可以将符号位和数值域统一处理
    +1 的二进制原码00000001 -1 二进制补码 11111111,相加之后的和为 0,符合数学运算的结果
    负数的补码要保证和对应的正数相加结果为0,包括符号位
    负数的补码是在其反码的末位加1是补码的一个重要性质

    另外,补码下的 0 就只有一个表示方式,因此在判断数字是否为 0 时,只要比较一次即可

    负数二进制补码转换成十进制的

    给出负数补码 1000 1000,如何计算其十进制表示?

    1. 定义法
      对补码先减去1,然后再取反
      比如:1000 1000 减一结果1000 0111,取反后0111 1000,0111 1000 转换成十进制2^6 + 2^5 + 2^4 + 2^3 = 120,,所以为1000 1000 表示 -120

    2. 相加为 0 法
      负数的补码和对应的正数补码(原码)相加为 0
      1000 1000 对应的相加为 0 的原码为 0111 1000,转换成十进制2^6 + 2^5 + 2^4 + 2^3 = 120,所以为1000 1000 表示 -120

    3. 乘以阶数相加法
      符号位对应的结果为乘以阶数,符号位负
      1000 1000 1 作为符号位,对应 -2^7 = -128,准换成十进制 - 2^7 + 2^3 = -128 + 8 = -120

    注意 定义法和相加为 0 法有一个bug,比如带符号的八位二进制数 1000 000,表示的十进制数是多少?
    结果是 -128,这是一个标准定义

    signed char 表示的数据范围

    char 型变量占用 8 个位,对于 signed char 类型,最高位表示符号位,此时有 7 个位用于表示数值。按照数学中的排列组合,7 个位能够表示 2^7 也即 128 个不同的数,若考虑正负号,signed char 类型最多也能表示 2*128 = 256 个不同的数

    但是,如果 signed char 类型能够表示的数值范围是 -127 到 127,那么能够表示的只有 255 个不同的数字了,与理论最大能够表示的不同数字数 256 相比,少了一个,这是因为 -0 和 +0 其实是同一个数字,也即 0b10000000 和 0b00000000 是同一个数字 0
    这对于计算机来说很不友好,同样的一个数字有两种二进制码,在处理时会显得很麻烦

    因为 -0 和 +0 其实是同一个数字,因此原码中 1000 0000 和 0000 0000 都表示数字 0,现在补码下的 0 只有一个表示方式:
    0000 0000,二进制码 1000 0000 就多余出来了,在此定义为 -128

    在C语言中,signed char 型二进制码 0b10000000 的补码仍然为 0b10000000,因此它是“数字a的补码为 -a”原则的例外

    模运算

    对于 8 位字长的有符号整数类型,以 2^8 即 256 为模,对于其加减法运算

    -128 = 128 (mod 256) -127 = 129 (mod 256)...-2 = 254 (mod 256) -1 = 255 (mod 256)
    所以模 256 下的加减法,用 0, 1, 2,…, 254,255 表示其值,或者用 −128, −127,…, −1, 0, 1, 2,…,127 是完全等价的

    −128与128,−127与129,…,−2与254,−1与255 可以互换而加减法的结果不变,需要的 CPU 加法运算器的电路实现与 8 位无符号整数并无不同

    所以负数采用补码表示,等价于去除符号位的正数,-1 可以理解为 255,这样减法运算用加法运算器的电路就可以实现

    8位有符号的运算 -1 + 10 等等价于 255 + 10 二进制表示为 1111 1111 + 0000 1010
    得到结果 1 0000 1001,溢出的进位被自动舍弃,得到结果 0000 10001

    相应的16位整形数的二进制加减法运算,等同于模 65536(2^16)的加法运算
    32位整形数的二进制加减法运算,等同于模 2^32 的加法运算

    浮点数的二进制表示

    浮点数的定义

    浮点数是用科学计数法表示的,这种方式下小数点的位置是漂浮不定的,所以命名为浮点数

    25.125
    25.125 = 0.25125 * 10^2;
    25.125 = 2.5125 * 10^1
    25.125 = 25.125 * 10^0
    25.125 = 251.25 * 10^-1
    25.125 = 2512.5 * 10^-2

    同样的方法,二进制也可以用科学计数法表示,只是基数从 0 换成 2 而已

    浮点数表示数字的方法

    浮点数用科学计数法表示的数字的格式如下:


    科学计数法浮点类型.jpg

    上图中各个变量的含义如下:

    • S:符号位,0 表示正数,1 表示负数
    • M:尾数,用小数表示,例如 3.254*10^-2 中的 3.254 就是尾数
    • R:基数,表示十进制的 R 就是 10,表示二进制的的 R 就是 2
    • E:指数,用整数表示,3.254*10^-2 中的 -2 就是指数

    如果要用 32bit 表示一个浮点数,则需要把以上的变量,填充到对应的 bit 上就可以了:
    一般用 32bit 表示一个 float 类型的浮点数,填充规则如下:


    浮点数的表示方法.jpg
    浮点数的二进制表示

    将 25.125 转换为浮点数,过程如下图所示:


    浮点数转换为二进制.jpg

    用二进制科学计数法表示 25.125
    25.125(D) = 11001.001(BIN) = 1.1001001 * 2^4(BIN)
    按照 32bit 表示浮点数的规则:

    • 符号位 S = 0
    • 尾数 M = 1001001(取小数点后的部分,即1.1001001 的1001001部分 )
    • 基数 R = 2
    • 指数 E = 4 (100)
    浮点数的 IEEE754 标准表示

    浮点数因为定义规则的不同,导致范围和精度都是不一致的:

    1. 指数位越多,则尾数位越小,表示的范围越大,但是精度也会变低
    2. 指数位越少,则尾数位越多,表示的范围越小,但是精度也会变高

    而早期各计算机厂商(如 IBM、微软)都会定义自己的一套浮点数规则,就会导致同一个程序在不同厂商的计算机下做浮点数运算时,必须「先转换」成此厂商规定的浮点数格式,才能计算,则加重了计算成本

    因此业界迫切需要统一的浮点数标准,1985年 IEEE 组织提出了 「IEEE754 浮点数标准」,其统一定义了浮点数的表示形式:
    单精度浮点数 float:32 位,符号位 S 占 1 bit,指数 E 占 8 bit,尾数 M 占 23 bit
    双精度浮点数 float:64 位,符号位 S 占 1 bit,指数 E 占 11 bit,尾数 M 占 52 bit

    为了让其表示范围,精度最大化,还对指数和底数做了下面的规定:

    1. 因为尾数 M 的第一位总是 1(因为 1 <= M < 2),因此这个 1 可以省略不写,它是个隐藏位,这样单精度 23 位尾数可以表示了 24 位有效数字,双精度 52 位尾数可以表示 53 位有效数字
    2. 因为指数 E 是个「无符号」整数,表示 float 时,一共占 8 bit,所以它的取值范围为 0 ~ 255,但因为指数可以是负的,所以规定在存入 E 时在它原本的值 [ 加上一个中间数 127],这样 E 的取值范围为 -127 ~ 128,表示 double 时,一共占 11 bit,存入 E 时加上中间数 1023,这样取值范围为 -1023 ~ 1024(可以理解为指数位表示的值,需要减去127 和 1023

    除了规定尾数和指数位,还做了以下规定,如下图:

    • 指数 E 非全 0 且非全 1:规格化数字,按上面的规则正常计算
    • 指数 E 全 0,尾数非 0:非规格化数,尾数隐藏位不再是 1,而是 0(M = 0.xxxxx),这样可以表示 0 和很小的数
    • 指数 E 全 1,尾数全 0:正无穷大/负无穷大(正负取决于 S 符号位)
    • 指数 E 全 1,尾数非 0:NaN(Not a Number)
    浮点数的32bit表示方法.jpg

    当指数全部为 1 的时候,尾数全 0 表示正负无穷大,尾数不全为 0 表示一个非法的数
    指数全部为 0 时,此时尾数的基数变为 0,表示一个很小的数

    注意上面规则中,没有明确的规则表示 0 的 float 值,可以认为浮点类型可以从正负方向无限接近于0,但是不能明确表示出0

    浮点数为什么有精度损失

    如果想要用浮点数表示小数 0.2 ,会发生循环的现象


    0.2的浮点数表示法.jpg

    所以 0.2(D)= 0.00100...(B)
    因为十进制的 0.2 无法精确转换为二进制小数,而计算机在表示一个数字的时候,宽度是有限的,无限循环的小数存储在计算机中的时候,会被截断,所以会产生小数精度发生损失的情况

    浮点数表示的精度范围是多大

    单精度浮点数(32bit)可以表示的最大数的二进制形式 1.111111(小数点后23个1)* 2^127,二进制的 1.111111 约等于 2,所以float 能表示的最大数是 3.4* 10^38,即 32bit float 的表示范围是 -3.4 * 10^38 ~ 3.4 * 10^38

    它能表示的精度有多小呢?
    float 能表示的最小二进制数为 0.0000….1(小数点后22个0,1个1),用十进制数表示就是 1/2^23
    1/2^23 = 0.00000011920928955078125 约等于0.00000012 也就是小数点后 7 位

    用同样的方法可以算出,double 能表示的最大二进制数为 +1.111…111(小数点后52个1) * 2^1023 ≈ 2^1024 = 1.79 * 10^308,所以 64bit double 能表示范围为:-1.79 * 10^308 ~ +1.79 * 10^308

    double 的最小精度为:0.0000…1(51个0,1个1),用十进制表示就是 1/2^52

    整数值可以和 0 直接比较,浮点类型无法和 0.0f 直接比较,浮点类型只是从正负方向无限接近 0 值,故可以设置一个精度范围,该范围内都可以认为是0值

    const float ESP = 1E-6f;
    if( x >= -ESP && x <= ESP) {
    // x equal to 0
    }

    浮点类型的存储存在精度损失,所以和浮点字面量比较的时候,要特别注意字面值,浮点数不加后缀默认是 double 类型,加上f表示 float 类型,加上L表示 long double 类型

    参考博客:

    https://blog.csdn.net/jiaoyangwm/article/details/129296459
    https://blog.csdn.net/weixin_46039719/article/details/122903907

    相关文章

      网友评论

          本文标题:C 语言学习(11) ---- 进制转换

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