转载请标明原文地址:https://segmentfault.com/a/1190000041768195

Float单精度浮点数

Float二进制表示法

IEEE754标准中规定,单精度浮点数float占4字节32位
Sign(符号1位)|Exponent(指数8位 偏移127)|Mantissa(尾数23位)

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

S、E、M 这3部分是怎么确定的呢?
以11.25为例:

1.确定符号S

11.25不为负数 所以S = 0

2.把值转为二进制科学计数法

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

IEEE754是先把小数转成二进制,用二进制的科学计数法表示该小数。

还是以11.25为例:

(1)整数部分转二进制:

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

11 ÷ 2 = 5 ··· 1
 5 ÷ 2 = 2 ··· 1
 2 ÷ 2 = 1 ··· 0
 1 ÷ 2 = 0 ··· 1
(2)小数部分转二进制:

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

0.25 x 2 = 0 ··· 0.5
0.5  x 2 = 1 ··· 0
(3)整数和小数拼到一起:

IF = 1011.01

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

IF = 1.01101x23

3.确定指数E:

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

130 ÷ 2 = 65 ··· 0 
 65 ÷ 2 = 32 ··· 1
 32 ÷ 2 = 16 ··· 0
 16 ÷ 2 =  8 ··· 0
  8 ÷ 2 =  4 ··· 0
  4 ÷ 2 =  2 ··· 0
  2 ÷ 2 =  1 ··· 0
  1 ÷ 2 =  0 ··· 1

4.确定尾数M:

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

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

S(1位) | E(8位)   | M(23位)
     0 | 10000010 | 01101 (后面补0够23位)
     0 | 10000010 | 0110100 00000000 00000000

11.25转成float二进制最终结果就是:
0 10000010 0110100 00000000 00000000
《在线浮点数转换工具》验证一下,结果正确。
float_1.125e1

二进制转Float

二进制11000000110110000000000000000000转Float怎么转呢?

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

S(1位)| E(8位)   |M(23位) 即
    1 | 10000001 | 1011000 00000000 00000000
符号S: 1(值大于等于0时 S为0 值小于0时 S为1)
指数E: 10000001
尾数M: 1011000 00000000 00000000

1.转换符号S:

S = 1 符号为负-

2.转换指数E:

E = 1000 0001 转整数
转换方法:从低位到高位 按位转成10进制相加

1x2⁷ + 0x2⁶ + 0x2⁵ + 0x2⁴ + 0x2³ + 0x2² + 0x2¹ + 1x2⁰ =
128  + 0    + 0    + 0    + 0    + 0    + 0    + 1    = 129

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

3.转换尾数M:

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

(1)整数部分I转10进制:

I = 110 转成10进制为:6

1x2² + 1x2¹ + 0x2⁰ =
4    + 2    + 0    = 6

I = 6

(2)小数部分F转10进制:

F = .11 转为float为0.75

1x(½)¹ + 1x(½)² =
1/2    + 1/4    =
0.5    + 0.25   = 0.75

4.符号、整数、小数拼到一起:

-(6 + 0.75) = -6.75

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

Float特殊值

IEEE754规定了5个特殊值NaN、Infinity、-Infinity、0f、-0f
这个五个特殊值在转换时需要特殊处理
float_special

一、当指数E全部为1时

1.尾数M全部为0
(1)符号位S为0时 值为正无穷Infinity

float_infinity

(2)符号位S为1时 值为负无穷-Infinity

float_-infinity

2.尾数M不全部为0
不管符号位S是0还是1 值均为NaN

float_nan

float_nan_2

二、当指数E全为0且尾数全为0时

1.符号为0 值为0f

float_0f

2.符号为1 值为-0f

float_-0f

Float序列化代码

private byte[] bytes = new byte[4];

// float序列化
private void WriteFloat(float value) {
    int v = 0;
    unsafe { v = *(int*)&value; }
    bytes[0] = (byte)v;
    bytes[1] = (byte)(v >> 8);
    bytes[2] = (byte)(v >> 16);
    bytes[3] = (byte)(v >> 24);
}

// 反序列化float
private float ReadFloat() {
    uint v = (uint)(bytes[0] | bytes[1] << 8 | bytes[2] << 16 | bytes[3] << 24);
    float value = 0f;
    unsafe { value = *(float*)&v; }
    return value;
}

private void Test() {
    WriteFloat(1.23f);
    float v = ReadFloat();
    Trace.WriteLine(v);
}

// 当需要进行更精细转换时使用
private void FloatConvertTest() {
  // Float转换为二进制
  // 获取Float二进制值的S、E、M三部分
  float value = 1.2345f;
  uint bits = 0;
  unsafe { bits = *(uint*)&value; }
  uint sign = bits >> 31;
  uint exponent = (bits >> 23) & 0xFF;
  uint mantissa = bits & 0x7FFFFF;
  // 判断是否为特殊值
  if (exponent == 0xFF) {
      if (mantissa == 0) {
          return sign == 0 ? float.PositiveInfinity : float.PositiveInfinity;
      } else {
          return float.NaN;
      }
  } else if (exponent == 0 && mantissa == 0) {
          return sign == 0 ? 0f : -0f;
  } else {
      // 二进制转换为float
      bits = sign << 31 | exponent << 23 | mantissa;
      float f = 0f;
      unsafe { f = *(float*)&bits; }
      return f;
  }
}

Double双精度浮点数

Double二进制表示法

IEEE754标准中规定,双精度浮点数double占8字节64位
S、E、M三部分分别为:
S(1位)|E(11位 偏移1023)|M(52位)

以9.625为例:

1.确定符号S

9.625(不为负数) S = 0

2.值转为二进制科学计数法

(1)整数部分转二进制:

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

 9 ÷ 2 = 4 ··· 1 
 4 ÷ 2 = 2 ··· 0
 2 ÷ 2 = 1 ··· 0
 1 ÷ 2 = 0 ··· 1
(2)小数部分转二进制:

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

 0.625 x 2 = 1 ··· 0.25
 0.25  x 2 = 0 ··· 0.5
 0.5   x 2 = 1 ··· 0
(3)整数和小数拼到一起:

IF = 1001.101

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

IF = 1.001101 x 23

3.确定指数E:

E = 3, 加上偏移值1023
E = 1026 转成二进制为:10000000 010

转换方法:整数除以2取余 余数倒序排列(除到整数部分为0)

1026 ÷ 2 = 513 ··· 0
 513 ÷ 2 = 256 ··· 1
 256 ÷ 2 = 128 ··· 0
 128 ÷ 2 =  64 ··· 0
  64 ÷ 2 =  32 ··· 0
  32 ÷ 2 =  16 ··· 0
  16 ÷ 2 =   8 ··· 0
   8 ÷ 2 =   4 ··· 0
   4 ÷ 2 =   2 ··· 0
   2 ÷ 2 =   1 ··· 0
   1 ÷ 2 =   0 ··· 1

4.确定尾数M:

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

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

S(1位)| E(11位 偏移1023)| M(52位)
    0 | 10000000 010   | 0011010... (后面补0够52位)

9.625转成double二进制最终结果就是 0 10000000010 0011010···
《在线浮点数转换工具》验证一下,结果正确。
double_9.625

二进制转Double

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

1 | 10000000101 | 100100010···
S = 1 是负数 符号为-
E = 10000000101
M = 100100010···

1.转换符号S:

S = 1 符号为负-

2.转换指数E:

E = 1000 0000 101 转整数
转换方法为:从低位到高位 按位转成10进制相加

1x2¹⁰ + 0x2⁹ + 0x2⁸ + 0x2⁷ + 0x2⁶ + 0x2⁵ + 0x2⁴ + 0x2³ + 1x2² + 0x2¹ + 1x2⁰ =
1024  + 0    + 0    + 0    + 0    + 0    + 0    + 0    + 4    + 0    + 1    = 1029

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

3.转换尾数M:

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

(1)整数部分I转10进制:

I = 1100100 转成10进制为:100
转换方法为:从低位到高位 按位转成10进制相加

1x2⁶ + 1x2⁵ + 0x2⁴ + 0x2³ + 1x2² + 0x2¹ + 0x2⁰ =
64   + 32   + 0    + 0    + 4    + 0    + 0    = 100

I = 100

(2)小数部分F转10进制:

F = 01 转成10进制为:0.25

0x(½)¹ + 1x(½)² =
0      + 1/4    =
0      + 0.25    = 0.25

F = 0.25

4.符号、整数和小数部分拼接到一起:

-(100 + 0.25) = -100.25

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

Double特殊值

IEEE754规定了3个特殊值NaN、Infinity、-Infinity
分别代表:非数字(Not a number)、正无穷、负无穷
这个三个特殊值在转换时需要特殊处理
double_special

一、当指数E全部为1时

1.尾数M全部为0
(1)符号位S为0时 值为正无穷Infinity

double_infinity

(2)符号位S为1时 值为负无穷-Infinity

double_-infinity

2.尾数M不全部为0
不管符号位S是0还是1 值均为NaN

double_nan

double_nan_2

二、当指数E全部为0且尾数M全部为0时

1.符号位S为0时 值为0d

double_0d

2.符号位S为1时 值为-0d

double_-0d

Double序列化代码

private byte[] bytes = new byte[8];

private void WriteDouble(double value) {
    long v = 0;
    unsafe { v = *(long*)&value; }
    bytes[0] = (byte)v;
    bytes[1] = (byte)(v >> 8);
    bytes[2] = (byte)(v >> 16);
    bytes[3] = (byte)(v >> 24);
    bytes[4] = (byte)(v >> 32);
    bytes[5] = (byte)(v >> 40);
    bytes[6] = (byte)(v >> 48);
    bytes[7] = (byte)(v >> 56);
}

public double ReadDouble() {
    int low = bytes[0] | bytes[1] << 8 | bytes[2] << 16 | bytes[3] << 24;
    int high = bytes[4] | bytes[5] << 8 | bytes[6] << 16 | bytes[7] << 24;
    long v = (uint)low | (long)high << 32;
    double value = 0d;
    unsafe { value = *(double*)&v; }
    return value;
}

private void DoubleTest() {
    WriteDouble(-1.23456d);
    double v = ReadDouble();
    Trace.WriteLine(v);
}

总结:

Float转换规则

Sign(符号1位)|Exponent(指数8位 偏移值127)|Mantissa(尾数23位)

Double转换规则

Sign(符号1位)|Exponent(指数11位 偏移值1023)|Mantissa(尾数52位)

特殊值

Infinity、-Infinity、NaN、0、-0

IEEE754浮点数常见问题

1.有效数字位数限制

Float单精度浮点数大约6位有效数字
Double双精度浮点数大约15位有效数字
也就是说从左侧第一个不0的数字开始,Float能保证6位有效数字,再往后就不保证有效了
比如123.456f、0.123456f有效,12345.12345f(赋值后等于12345.123)、123456.1234f(赋值后等于123456.125)不能保证有效
Double能保证15位有效数字,再往后就不保证有效了
具体细节可以查看官方文档:C# 浮点数精度

2.尾数无限循环问题

由于浮点数转换为二进制时,某些数据比如0.1 小数部分乘2以后不能完全乘尽(0.2、0.4、0.8、0.6、0.2、0.4、0.8、0.6... 无限循环) 所以小数会出现丢失精度的情况,不能完全准确的表达这些乘不尽的情况,所以遇到类似精度丢失,知道是IEEE标准导致的问题即可。float类型换成double提高精度依然会有该问题。想解决精度丢失的问题,建议使用decimal类型,因为decimal是基于十进制实现的,不会产生float、double精度丢失的问题。


冰封百度
233 声望43 粉丝

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


引用和评论

0 条评论