IEEE754、VAX、IBM浮点型介绍和.NET中互相转换
【题外话】
最近在做C3D文件的解析,好奇怪的是文件中竟然存储了CPU的类型,原本不以为然,结果后来读取一个文件发现浮点数全部读取错误。查了下发现虽然在上世纪80年代就提出了IEEE754要统一浮点数标准,但是到现在仍然有计算机采用不同方式存储浮点数。在某些非IEEE754标准的计算机产生的二进制文件中,如果拿到其他计算机中读取,如果不进行专门的转换,可能导致数据错误等问题。
【文章索引】
对于x86等常见的CPU,都是采用IEEE754存储和计算浮点型的,当然在.NET平台中浮点型也是IEEE754标准的。首先回顾下本科时学过的计算机组成原理,查了下课本发现是如下介绍IEEE754浮点数的存储的(唐朔飞版课本233页):
其中,S为数符,它表示浮点数的正负,但与其有效位(尾数)是分开的。阶码用移码表示,阶码的真值都被加上一个常数(偏移量),如短实数、长实数和临时实数的偏移量用十六进制表示分别为7FH、3FFH和3FFFH。尾数部分通常都是规格化表示,即非“0”的有效位最高位总是1。
以单精度浮点数为例,如果字节查看应该是如下这个样子的,数符占第1字节的第1位,阶码占第1字节的后7位及第二字节的第1位,其余都是尾数。
SEF S EEEEEEEE FFFFFFF FFFFFFFF FFFFFFFF bits 1 2 9 10 32 bytes byte1 byte2 byte3 byte4
如果设数符为S,阶码为E,尾数的小数部分为F,那么可以通过位运算得到这三位:
Double S = (byte1 & 0x80) >> 7; Double E = ((byte1 & 0x7F) << 1) + ((byte2 & 0x80) >> 7); Double F = ((byte2 & 0x7F) << 16) + (byte3 << 8) + byte4;
由于阶码用移码表示,所以真实的阶码则是E - 0x7F。而尾数由于是规格化的表示,即将尾数规范化为(1.FFFFF……FF)2,但只存小数点之后的部分。由于1 / 2 + 1 / 4 + 1 / 8 + ... + 1 / n = 1 - 1 / 2n,所以可知尾数M(M = 1.0 + F)的范围为1 <= M <= 2 - 1 / 223。
所以可通过如下的公式来计算浮点数的值,其中,C是尾数规范化后减去的常量,B是移码的偏移量,可知A、B、C分别为A = 2、B = 0x7F以及C = 1.0。
V = (-1)^S * (F + C) * A^(E - B)
可见,浮点数就不存在0的概念了,所以只能用极限小来表示,同时为了表示无穷大,规定E取值范围为0 < E < 0xFF,即-0x7F < (E - B) < 0x80。
所以,当E = 0xFF时,指数最大,规定F = 0时为无穷值,其中又有S = 0为正无穷以及S = 1为负无穷;而F != 0时为无效数字(NaN)。
当E = 0时,指数最小,规定F = 0时为0,其中又有S = 0为正0以及S = 1时为-0。
不过表示非常小的数字,允许当E = 0时非规范化的尾数存在。即当E = 0且F !=0时,V = (-1)^S * F * A^-126。
二进制表示 | 十六进制表示 | 含义 | 十进制表示 |
0 11111111 00000000000000000000000 | 7F 80 00 00 | 正无穷 | +∞ |
1 11111111 00000000000000000000000 | FF 80 00 00 | 负无穷 | -∞ |
0 00000000 00000000000000000000000 | 00 00 00 00 | +0 | 0 |
1 00000000 00000000000000000000000 | 80 00 00 00 | -0 | 0 |
0 00000000 00000000000000000000001 | 00 00 00 01 | 最小正数 | 1.401298E-45 |
0 11111110 11111111111111111111111 | 7F 7F FF FF | 最大值 | 3.402823E+38 |
1 11111110 11111111111111111111111 | FF 7F FF FF | 最小值 | -3.402823E+38 |
0 01111111 00000000000000000000000 |
3F 80 00 00 |
+1 | 1 |
而二进制小数转十进制小数的计算可以直接按整数的转换来做,然后除以2n即可,n在这里其实就是尾数的长度,为23。
所以,有了以上的这些信息,我们就可以将浮点数字与字节数组相互转换了(本文假定给定的字节数组都是Litten-Endian):
1 Single ToSingle(Byte[] data) 2 { 3 Double a = 2.0; 4 Double b = 127.0; 5 Double c = 1.0; 6 Double d = -126.0; 7 8 Byte byte1 = data[3]; 9 Byte byte2 = data[2]; 10 Byte byte3 = data[1]; 11 Byte byte4 = data[0]; 12 13 Double s = (byte1 & 0x80) >> 7; 14 Double e = ((byte1 & 0x7F) << 1) + ((byte2 & 0x80) >> 7); 15 Double f = ((byte2 & 0x7F) << 16) + (byte3 << 8) + byte4; 16 Double m = f / Math.Pow(2, 23); 17 18 if (e == 0xFF && f == 0) return (s == 0 ? Single.PositiveInfinity : Single.NegativeInfinity); 19 if (e == 0xFF && f != 0) return Single.NaN; 20 if (e == 0x00 && f == 0) return 0; 21 if (e == 0x00 && f != 0) return (Single)((s == 0 ? 1.0 : -1.0) * m * Math.Pow(a, d)); 22 23 return (Single)((s == 0 ? 1.0 : -1.0) * (c + m) * Math.Pow(a, e - b)); 24 } 25 26 Byte[] GetBytes(Single num) 27 { 28 Double a = 2.0; 29 Double b = 127.0; 30 Double c = 1.0; 31 Double d = Math.Log(2); 32 33 Int32 s = (num >= 0 ? 0 : 1); 34 35 Double v = Math.Abs(num); 36 Int32 e = (Int32)(Math.Log(v) / d + b); 37 38 Double m = (v / Math.Pow(a, e - b)) - c; 39 Int32 f = (Int32)(m * Math.Pow(2, 23)); 40 41 Byte[] data = new Byte[4]; 42 data[3] = (Byte)((s << 7) + ((e & 0xFE) >> 1)); 43 data[2] = (Byte)(((e & 0x01) << 7) + ((f & 0x007F0000) >> 16)); 44 data[1] = (Byte)((f & 0x0000FF00) >> 8); 45 data[0] = (Byte)(f & 0x000000FF); 46 47 return data; 48 }
上述的浮点数转字节数组不能支持NaN和非规范化的情况,当然也可以自己判断下。当然了,上边说了这么多还是为了介绍下边两种浮点数做铺垫。如果实现系统浮点数与字节数组转换的话,用上边这种方法转换就不如用System.BitConverter来的方便了。
首先还是按字节看下VAX和IBM浮点型的存储:
VAX单精度浮点:
SEF S EEEEEEEE FFFFFFF FFFFFFFF FFFFFFFF bits 1 2 9 10 32 bytes byte2 byte3 byte0 byte1
IBM单精度浮点:
SEF S EEEEEEE FFFFFFFF FFFFFFFF FFFFFFFF bits 1 2 8 9 32 bytes byte1 byte2 byte3 byte4
非常有意思的是,VAX存储的结构并不是按顺序存储的,而是采用了一种叫做Middle-Endian的存储方式来存储(并非字节序):对于四字节而言其顺序就是2301,八字节为23016745,十六字节为23016745AB89EFCD。不过总体来说,VAX浮点型与IEEE754还是很类似的,比如VAX也要进行规范化,但是其规范化为(0.1FFFFF..FF)2,所以上述的C就为0.5,其尾数M的范围即为1/2 <= M <= 1 - 1 / 224;而同时其也并没有规定无穷大,不需要单独为无限大留出最大的阶码,所以上述的B为0x80。
而IBM单精度浮点则与上述两种差别更大。首先其阶码并不是8位,而是7位,由于还是使用移码存储的阶码,所以其减去的不能是127或者128,而是64,所以其与VAX一样,也没有无穷值的表示。除此之外,其也不是以2为底计算阶码的,而是以16为底,并且其没有规范化尾数的要求(当然这也与其以16为底有关),所以不需要对尾数进行加减运算,其范围为1/16 <= M <= 1- 1 / 224。
以下是实现VAX浮点字节数组与系统浮点数字相互转化的类:
1 using System; 2 3 namespace DotMaysWind.Numerics 4 { 5 /// <summary> 6 /// VAX单精度浮点数字 7 /// </summary> 8 /// <remarks> 9 /// SEF S EEEEEEEE FFFFFFF FFFFFFFF FFFFFFFF 10 /// bits 1 2 9 10 32 11 /// bytes byte2 byte1 byte4 byte3 12 /// </remarks> 13 public struct VAXSingle 14 { 15 #region 常量 16 private const Int32 LENGTH = 4; 17 private const Double BASE = 2.0; 18 private const Double EXPONENT_BIAS = 128.0; 19 private const Double MANTISSA_CONSTANT = 0.5; 20 private const Double E24 = 16777216.0; 21 #endregion 22 23 #region 字段 24 private Byte[] _data; 25 #endregion 26 27 #region 构造方法 28 /// <summary> 29 /// 初始化新的VAX单精度浮点数字 30 /// </summary> 31 /// <param name="data">VAX单精度浮点数字字节数组</param> 32 /// <param name="startIndex">数据起始位置</param> 33 public VAXSingle(Byte[] data, Int32 startIndex) 34 { 35 this._data = new Byte[VAXSingle.LENGTH]; 36 Array.Copy(data, startIndex, this._data, 0, VAXSingle.LENGTH); 37 } 38 39 /// <summary> 40 /// 初始化新的VAX单精度浮点数字 41 /// </summary> 42 /// <param name="num">系统标准的单精度浮点数字</param> 43 public VAXSingle(Single num) 44 { 45 Int32 s = (num >= 0 ? 0 : 1); 46 47 Double v = Math.Abs(num); 48 Int32 e = (Int32)(Math.Log(v) / Math.Log(2.0) + 1.0 + VAXSingle.EXPONENT_BIAS); 49 50 Double m = (v / Math.Pow(VAXSingle.BASE, e - VAXSingle.EXPONENT_BIAS)) - VAXSingle.MANTISSA_CONSTANT; 51 Int32 f = (Int32)(m * VAXSingle.E24); 52 53 this._data = new Byte[VAXSingle.LENGTH]; 54 this._data[1] = (Byte)((s << 7) + ((e & 0xFE) >> 1)); 55 this._data[0] = (Byte)(((e & 0x01) << 7) + ((f & 0x007F0000) >> 16)); 56 this._data[3] = (Byte)((f & 0x0000FF00) >> 8); 57 this._data[2] = (Byte)(f & 0x000000FF); 58 } 59 #endregion 60 61 #region 方法 62 /// <summary> 63 /// 获取系统标准的单精度浮点数字 64 /// </summary> 65 /// <returns>系统标准的单精度浮点数字</returns> 66 public Single ToSingle() 67 { 68 Byte b1 = this._data[1]; 69 Byte b2 = this._data[0]; 70 Byte b3 = this._data[3]; 71 Byte b4 = this._data[2]; 72 73 Double s = (b1 & 0x80) >> 7; 74 Double e = ((b1 & 0x7F) << 1) + ((b2 & 0x80) >> 7); 75 Double f = ((b2 & 0x7F) << 16) + (b3 << 8) + b4; 76 Double m = f / VAXSingle.E24; 77 78 if (e == 0 && s == 0) return 0; 79 if (e == 0 && s == 1) return Single.NaN; 80 81 return (Single)((s == 0 ? 1.0 : -1.0) * (VAXSingle.MANTISSA_CONSTANT + m) * Math.Pow(VAXSingle.BASE, e - VAXSingle.EXPONENT_BIAS)); 82 } 83 84 /// <summary> 85 /// 获取VAX单精度浮点数据字节数组 86 /// </summary> 87 /// <returns>字节数组</returns> 88 public Byte[] ToArray() 89 { 90 Byte[] data = new Byte[VAXSingle.LENGTH]; 91 92 Array.Copy(this._data, data, VAXSingle.LENGTH); 93 94 return data; 95 } 96 #endregion 97 } 98 }
以下是实现IBM浮点字节数组与系统浮点数字相互转化的类:
1 using System; 2 3 namespace DotMaysWind.Numerics 4 { 5 /// <summary> 6 /// IBM单精度浮点数字 7 /// </summary> 8 /// <remarks> 9 /// SEF S EEEEEEE FFFFFFFF FFFFFFFF FFFFFFFF 10 /// bits 1 2 8 9 32 11 /// bytes byte1 byte2 byte3 byte4 12 /// </remarks> 13 public struct IBMSingle 14 { 15 #region 常量 16 private const Int32 LENGTH = 4; 17 private const Double BASE = 16.0; 18 private const Double EXPONENT_BIAS = 64.0; 19 private const Double E24 = 16777216.0; 20 #endregion 21 22 #region 字段 23 private Byte[] _data; 24 #endregion 25 26 #region 构造方法 27 /// <summary> 28 /// 初始化新的IBM单精度浮点数字 29 /// </summary> 30 /// <param name="data">IBM单精度浮点数字字节数组</param> 31 /// <param name="startIndex">数据起始位置</param> 32 public IBMSingle(Byte[] data, Int32 startIndex) 33 { 34 this._data = new Byte[IBMSingle.LENGTH]; 35 Array.Copy(data, startIndex, this._data, 0, IBMSingle.LENGTH); 36 } 37 38 /// <summary> 39 /// 初始化新的IBM单精度浮点数字 40 /// </summary> 41 /// <param name="num">系统标准的单精度浮点数字</param> 42 public IBMSingle(Single num) 43 { 44 Int32 s = (num >= 0 ? 0 : 1); 45 46 Double v = Math.Abs(num); 47 Int32 e = (Int32)(Math.Log(v) / Math.Log(2.0) / 4.0 + 1.0 + IBMSingle.EXPONENT_BIAS); 48 49 Double m = (v / Math.Pow(IBMSingle.BASE, e - IBMSingle.EXPONENT_BIAS)); 50 Int32 f = (Int32)(m * IBMSingle.E24); 51 52 this._data = new Byte[IBMSingle.LENGTH]; 53 this._data[3] = (Byte)(s + e); 54 this._data[2] = (Byte)((f & 0x00FF0000) >> 16); 55 this._data[1] = (Byte)((f & 0x0000FF00) >> 8); 56 this._data[0] = (Byte)(f & 0x000000FF); 57 } 58 #endregion 59 60 #region 方法 61 /// <summary> 62 /// 获取系统标准的单精度浮点数字 63 /// </summary> 64 /// <returns>系统标准的单精度浮点数字</returns> 65 public Single ToSingle() 66 { 67 Byte b1 = this._data[3]; 68 Byte b2 = this._data[2]; 69 Byte b3 = this._data[1]; 70 Byte b4 = this._data[0]; 71 72 Double s = (b1 & 0x80) >> 7; 73 Double e = (b1 & 0x7F); 74 Double f = (b2 << 16) + (b3 << 8) + b4; 75 Double m = f / IBMSingle.E24; 76 77 if (e == 0 && f == 0 && s == 0) return 0; 78 79 return (Single)((s == 0 ? 1.0 : -1.0) * m * Math.Pow(IBMSingle.BASE, e - IBMSingle.EXPONENT_BIAS)); 80 } 81 82 /// <summary> 83 /// 获取IBM单精度浮点数据字节数组 84 /// </summary> 85 /// <returns>字节数组</returns> 86 public Byte[] ToArray() 87 { 88 Byte[] data = new Byte[IBMSingle.LENGTH]; 89 90 Array.Copy(this._data, data, IBMSingle.LENGTH); 91 92 return data; 93 } 94 #endregion 95 } 96 }
双精度浮点数与单精度浮点数类似,只不过会扩大阶码和尾数的范围罢了。对于IEEE754的双精度浮点而言,不仅尾数的位数增加,还会增加阶码的尾数,字节存储如下:
SEF S EEEEEEE EEEE FFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF bits 1 2 12 13 64 bytes byte1 byte2 byte3 byte4 byte5 byte6 byte7 byte8
可见,其阶码增加了3位,即最大值是原来翻了3翻,为1024。而为了保证能表示无穷值,所以B为1023。除此之外只需要多读取后边增加的尾数即可,步骤与单精度基本相同。
而对于VAX和IBM的双精度浮点,更是没有扩大阶码的范围,而只是扩大了尾数的范围,使得只要多读取增加的4位尾数即可,而常数A、B、C更是无需修改。两者字节存储如下:
VAX双精度浮点:
SEF S EEEEEEEE FFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF bits 1 2 9 10 64 bytes byte2 byte3 byte0 byte1 byte6 byte7 byte4 byte5
IBM双精度浮点:
SEF S EEEEEEE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF bits 1 2 8 9 64 bytes byte1 byte2 byte3 byte4 byte5 byte6 byte7 byte8
【相关链接】
- Transform between IEEE, IBM or VAX floating point number formats and bytes expressions:http://www.codeproject.com/Articles/12363/Transform-between-IEEE-IBM-or-VAX-floating-point-n
- VAX F_FLOAT and D_FLOAT to IEEE T_FLOAT and S_FLOAT (double):http://yaadc.blogspot.com/2013/01/vax-ffloat-and-dfloat-to-ieee-tfloat.html
- IEEE Arithmetic:http://docs.oracle.com/cd/E19957-01/806-3568/ncg_math.html
- Floating-Point:http://andromeda.rutgers.edu/~dupre/231/lecture13.doc
- IBM Floating Point Architecture:http://en.wikipedia.org/wiki/IBM_Floating_Point_Architecture
- VAX floating point to Decimal:http://arstechnica.com/civis/viewtopic.php?f=20&t=171682
如果您觉得本文对您有所帮助,不妨点击下方的“推荐”按钮来支持我! 本文及文章中代码均基于“署名-非商业性使用-相同方式共享 3.0”,文章欢迎转载,但请您务必注明文章的作者和出处链接,如有疑问请私信我联系! |