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

进制转换

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


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


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


    十六进制互转表.jpg

这里的转换表经常记不住,可以用一个简单的方法,记住 a 和 c 对应的二进制数
a(hex) = 1010(BIN) 类似于 a 就是一个眼睛
c(hex) = 1100(BIN) 类似于 c 就是一个门

  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,这是一个标准定义,计算机中8bit 有符号数的计算规律如下所示:


8bit signed.png
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
将100.125 转换为浮点数的二进制表达示例:

浮点数二进制表达.png

使用程序验证:

bool printfloatBinary() {
    union floatUnion {
        float a;
        uint32_t value;
    };

    union floatUnion fv;
    fv.a = 100.125;
    std::cout << std::showbase << std::setprecision(6) << "float a:" << fv.a << " binary:" << std::hex << fv.value << std::endl;

    fv.a = 123.456;
    std::cout << std::showbase << std::setprecision(6) << "float a:" << fv.a << " binary:" << std::hex << fv.value << std::endl;

    fv.a = 100.12;
    std::cout << std::showbase << std::setprecision(6) << "float a:" << fv.a << " binary:" << std::hex << fv.value << std::endl;
    return true;
}

结果如下:

float a:100.125 binary:0x42c84000
float a:123.456 binary:0x42f6e979
float a:100.12 binary:0x42c83d71
浮点数为什么有精度损失

如果想要用浮点数表示小数 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容