Float32转二进制

C#中浮点数的二进制格式遵循IEEE754标准(IEEE二进制浮点数算术标准)。

以小数11.25为例,float32的二进制值为:
01000001001101000000000000000000

这个值是怎么来的呢?
IEEE754是一个关于浮点数的标准,它把浮点数分成3个部分:
Sign(符号)|Exponent(指数)|Mantissa(尾数)

Sign(符号) 表示浮点数的正负(大于等于0为0,小于0为1)
Exponent(指数) 表示浮点数的指数(类似科学计数法的指数部分)
Mantissa(尾数) 表示有效数字(类似科学计数法的有效数字)
以下内容简称这3部分分别为S、E、M

Float32占32位,8位一字节共4字节:

S(1位)|E(8位 偏移127)|M(23位) 即:
0 | 1000 0010 | 01101000000000000000000

S、E、M 这3部分是怎么确定的呢?

比如11.25 表示成十进制的科学计数法: 1.125x101
符号是+,指数是1,有效数字是1.125

IEEE754是先把小数转成二进制,用二进制的科学计数法表示该小数。
还是以11.25为例:
S为0 (这个数字大于等于0 符号S=0,如果小于0,符号S=1)
把浮点数的整数部分和小数部分分别转成二进制,再拼到一起。

整数部分转二进制:

转换方法:整数除以2取余 倒序排列(除到整数部分为0)
整数部分I为11,转成二进制为:1011

$$ 2\sqrt{11}=5···1 \qquad\enspace \\=2···1 \\=1···0 \\=0···1 $$

小数部分转二进制:

转换方法:小数乘以2取整 正序排列(乘到小数部分为0,即余数为0)
小数部分F为0.25,转成二进制为:01

$$ 0.25\times2=0···0.5 \qquad\enspace\enspace \\=1···0 $$

整数和小数拼到一起:

IF = 1011.01

以二进制科学计数法表达:

IF = 1.01101x23

指数部分E:

E = 3, IEEE754规定这个值要加127
E = 130 转成二进制为:1000 0010
整数除以2取余 倒序排列(除到整数部分为0)

$$ 2\sqrt{130}=65···0 \qquad\enspace\enspace \\=32···1 \\=16···0 \\=\enspace 8···0 \\=\enspace 4···0 \\=\enspace 2···0 \\=\enspace 1···0 \\=\enspace 0···1 $$

尾数部分M:

M = 101101
由于二进制的科学计数法首位一定为1, 1可以省略不写,尾数部分就变成了01101
M = 01101

S、E、M三部分拼到一起:

0 | 1000 0010 | 01101 (后面补0够23位)
0 | 1000 0010 | 0110 1000 0000 0000 0000 000

11.25转成二进制最终结果就是 0 1000 0010 0110 1000 0000 0000 0000 000
《在线浮点数转换工具》验证一下,结果正确。

二进制转Float32

二进制11000000110110000000000000000000转Float32怎么转呢?
还是先把二进制分为S、E、M三部分,分别转换后计算最终结果。

1 | 1000 0001 | 1011 0···
S = 1 符号为- 是负数
E = 1000 0001
M = 1011 0···

指数E转整数:

E = 1000 0001 转整数
转换方法为:从低位到高位 按位转10进制相加
1x27 + 0x26 + 0x25 + 0x24 + 0x23 + 0x22 + 0x21 + 1x20 =
128 + 0 + 0 + 0 + 0 + 0 + 0 + 1 = 129

减去127(转二进制之前加了127 还原时需要减回来)
E = 129 - 127 = 2

尾数M转小数:

M = 1011
首位补1(为了节省空间 转换时去掉了首位的1 还原时需要补回来)
M = 11011
有效数字 科学计数法
M = 1.1011 x 22
M = 110.11
整数部分 I = 110 小数部分 F = .11

整数部分I转10进制:

I = 110 转成10进制为:6
1x22 + 1x21 + 0x20 =
4 + 2 + 0 = 6
I = 6

小数部分F转10进制:
1x(1/21) + 1x(1/22) =
1x0.5 + 1x0.25 =
0.5 + 0.25 = 0.75
F = 0.75

整数和小数部分拼接到一起:
6 + 0.75 = 6.75

加上符号位:
S = 1 (负数 符号为-)
float32 = -6.75

二进制11000000110110000000000000000000 转成十进制最终结果就是 -6.75
《在线浮点数转换工具》验证一下,结果正确。

Float64转二进制

IEEE754标准中规定,Float64占64位共8字节 S、E、M三部分分别为:

S(1位)|E(11位 偏移1023)|M(52位)

以9.625为例:

S = 0 (正数)

整数部分转二进制:

转换方法:整数除以2取余 倒序排列(除到整数部分为0)
整数部分 I = 9 转二进制为:1001

$$ 2\sqrt{9}=4···1 \qquad \\=2···0 \\=1···0 \\=0···1 $$

小数部分转二进制:

转换方法:小数乘以2取整 正序排列(乘到小数部分为0,即余数为0)
小数部分F为0.625,转成二进制为:101

$$ 0.625\times2=1···0.25 \qquad\enspace\enspace\enspace\enspace \\=0···0.5 \\=1···0\enspace $$

整数和小数拼到一起:

IF = 1001.101

以二进制科学计数法表达:

IF = 1.001101 x 23

指数部分E:

E = 3, 加上偏移值1023
E = 1026 转成二进制为:1000 0000 010
转换方法:整数除以2取余 倒序排列(除到整数部分为0)

$$ 2\sqrt{1026}=513···0 \qquad\enspace\enspace\enspace \\=256···1 \\=128···0 \\=\enspace 64···0 \\=\enspace 32···0 \\=\enspace 16···0 \\=\enspace\enspace 8···0 \\=\enspace\enspace 4···0 \\=\enspace\enspace 2···0 \\=\enspace\enspace 1···0 \\=\enspace\enspace 0···1 $$

尾数部分M:

M = 1001101
由于二进制的科学计数法首位一定为1, 1可以省略不写,尾数部分就变成了001101
M = 001101

S、E、M三部分拼到一起:

0 | 1000 0000 010 | 001101 (后面补0够52位)

9.625转成二进制最终结果就是 0 1000 0000 010 001101 0···
《在线浮点数转换工具》验证一下,结果正确。

二进制转Float64

二进制110000000101100100010···转Float64怎么转呢?
还是先把二进制分为S、E、M三部分,分别转换后计算最终结果。

1 | 1000 0000 101 | 1001 0001 0···
S = 1 符号为- 是负数
E = 1000 0000 101
M = 1001 0001 0···

指数E转整数:

E = 1000 0000 101 转整数
转换方法为:从低位到高位 按位转10进制相加
1x210 + 0x29 + 0x28 + 0x27 +
0x26 + 0x25 + 0x24 + 0x23
1x22 + 0x21 + 1x20 =
1024 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 4 + 0 + 1 = 1029

减去1023(转二进制之前加了023 还原时需要减回来)
E = 1029 - 1023 = 6

尾数M转小数:

M = 1001 0001
首位补1(为了节省空间 转换时去掉了首位的1 还原时需要补回来)
M = 1100 1000 1
有效数字 科学计数法
M = 1.10010001 x 26
M = 1100100.01
整数部分 I = 1100100 小数部分 F = .01

整数部分I转10进制:

I = 1100100 转成10进制为:100
1x26+ 1x25 + 0x24 + 0x23 + 1x22 + 0x21 + 0x20 =
64 + 32 + 0 + 0 + 4 + 0 + 0 = 100
I = 100

小数部分F转10进制:
0x(1/21) + 1x(1/22) =
0x0.5 + 1x0.25 =
0 + 0.25 = 0.25
F = 0.25

整数和小数部分拼接到一起:
100 + 0.25 = 100.75

加上符号位:
S = 1 (负数 符号为-)
float64 = -100.25

二进制110000000101100100010··· 转成十进制最终结果就是 -100.25
《在线浮点数转换工具》验证一下,结果正确。

总结:

大多数程序语言中,关于浮点数的表示都遵循IEEE754标准,都是由S、E、M三个部分组成,类似科学计数法的表示规则,为了尽量节约空间做了某些特殊规定。由于转换时,某些数据比如0.1 小数部分乘2以后不能完全乘尽(0.2, 0.4, 0.8, 0.6 …… 无限循环) 所以小数会出现丢失精度的情况,不能完全准确的表达这些乘不尽的情况,所以遇到类似精度丢失,数据变大了一点点,变小了一点点的问题,知道是这个原理导致的问题,既不是你写错了,也不是计算机出bug了,完全是转换规则的问题,无法避免。float类型换成double提高精度依然会有该问题。想解决精度丢失的问题,可以把数字小数部分当成整数计算 比如a=1.1 b=1.3 计算a+b 可以这样(Math.Round(a10) + Math.Round(b10)) / 10 注意数字别溢出最大范围


冰封百度
233 声望43 粉丝

Unity游戏程序员一枚。生命不息,学习不止。