环境:
- window 10
- 处理器 11th Gen Intel® Core™ i7-11700
- .net 6
- vs2022
1. 基础准备:2进制、10进制、16进制转换
-
常见2进制、10进制、16进制数据:
-
10进制整数转2进制算法:
-
10进制小数转2进制算法:
-
10进制数转2进制:
-
2进制整数转10进制:
-
2进制小数转10进制:
2. 基础准备:科学计数法
- 10进制: 123 => 1.23*102
- 2进制:11 => 1.1*21
3. 基础准备:vs调试,如何查看内存?
下图显示,如何在vs调试时观察变量i
的内存值:0x00000064 => 100
同样,查看数组在内存中的结构:
那么,查看float类型在内存中的结构(虽然还不明白float在内存中的存储规则。。。):
延伸一下,自己指定4个字节,让程序自动将它读取成float:
4. 基础准备:float类型存储规则简单示例
float是单精度浮点类型:使用32bit位(4字节)存储,存储时将32个bit位分为三部分,如下:
- 第一部分:1个bit位,用S表示,存储0或1,分别表示正或负;
- 第二部分:8个bit位,用E表示,存储的是指数的偏移值(原值+127);
- 第三部分:23个bit位,用F表示,存储的是小数点后的数字;
先来个示例,看下102.25f
在内存中怎么存储的?
- 第一步:
102.25f
转为2进制1100110.01
- 第二步:转成科学计数法为 1.1001_1001 * 2110,也可以表示为:1.1001_1001 * 26;(注意:小数点前面大多数为1,所以规定只存小数点后面的)
- 第三步:分别填充
- S位:正数,填充0;
- E位:指数为6,加上偏移127后等于133,转为2进制填充为:
1000_0101
; - F位:使用
1001_1001
填充,右侧补零,最终为:1001100_10000000_00000000
- 第四步:最终
102.25f
的内存结构应为:
使用vs调试工具验证:
5. 基础准备:从float内存结构反推具体值
核心是将上面的规则反走一遍。
还是以102.25f
,为例,已知它的内存结构如下:
- 第一步:分别读取S、E、F部分:
- S部分为0,表示正数;
- E指数部分为:
133
,减去偏移量127后,最终为:6
; - F尾数部分读取后是
1.10011001
(2进制),因为指数为6,所以要移6位,最后为:1100110.01
(2进制);
- 第二步:转成十进制(
1100110.01
=>102.25
)
6. 认识IEE754
其实,通过上面的示例,应该大致懂了float类型数据的存储规则,但是还不够细致。
下面就往细了说。
还是这张图:
问题:
- 将10进制小数换算成2进制小数能换算尽吗?将2进制小数换算成10进制小数能换算尽吗?
- 打印出来的float值真的是内存中存储的值吗?
- 为什么微软文档说float类型的精度是:6-9位,而网上大多说是:7位?
- 为什么float f=1.0058_5938f 打印出来是:“1.0058_5938f”,而
float f3 = 1.0058_5939f
打印出来也是:“1.0058_5938f”?
待续。。。。
10进制比二进制要精细,10进制的小数0.1的意思是1/10,那么用2进制怎么精确表示呢?
答案是:无法精确表示出来!
这就好比:有一块肉,我只需要平分10块取其1。而你有1把刀,每次只能将肉平分,这样无论你切多少次,切出来肉的数量只可能是:2,4,8,16,32,64。。。无论是哪一份我们都不可能挑出1/10的肉。
所以,float类型无法精确表示0.1f
,但是float可以精确表示0.5f
,无法表示0.11f
,但可以精确表示0.25f
。。。
如果我们把F
位的23个bit位缩小到5个bit,它们能精确表示的离散10进制数是:
那么上面的数据有多少个呢? Math.Pow(2,5) = 32 个。
因为只有32个,所以我们可以通过4舍五入的方法将这32个离散数缩小到十进制数:
1位小数:可以表示10个;
2位小数:虽然不能表示所有,但可以表示0.25和0.75两个;
3位小数:同上,可以表示4个;
4位小数:同上,可以表示8个;
5位小数:上面已占用了24个(10+2+4+8),而我有16个,超过32个了,所以5位小数的都不要表示了;
注意,到了2位小数的时候就已经不能覆盖它的全部了,所以再往后其实意义不大,所以再往下多1位小数即可,最终表示的如下:
所以,5个bit位表示的小数范围是:1-3,其中1位小数能全部表示,而2、3位小数都只能是1部分。
又因为float类型指数部分取值可能是0或1,当它是1时,所能表示的精度要再加一,如:1.125的精度是4,
最终,5个bit位的精度是1-4(25=32)。
按照这个推算就不难理解为什么微软说float类型的精度是6~9了:
223=8388_608,所以表示6位小数肯定没问题,而7位、8位小数也可以表示1部分,又因为指数部分再加一,所以最终是:6-9位精度。
从2进制存储反推10进制的时候,对于5个bit位,它的精度是1-4,当反算出来的小数精度大于4时,比如:1.9375,因为它不在可表示范围内,所以要进行4舍5入,这时候分别对0.0005,0.007,0.03进行四舍五入,又因为四设五入后均不能满10进1达到缩短小数位数的效果,所以只对最后一位进行四舍五入即可,最终显示的结果为:1.938。
看float的数字:0x00C0803F,它的存储反推为:1.0058_5937_5,因为精度超过9(精度为10),所以要进行四设五入,而对
0.0000_003、0.0000_0007、0.0000_0000_5进行四舍五入均不能满足10进1达到缩小小数位数的效果,所以只能是将最后的5四设五入,所以最终得到:0.0058_5938f