【題外話】php
最近在作C3D文件的解析,好奇怪的是文件中居然存儲了CPU的類型,本來不覺得然,結果後來讀取一個文件發現浮點數所有讀取錯誤。查了下發現雖然在上世紀80年代就提出了IEEE754要統一浮點數標準,可是到如今仍然有計算機採用不一樣方式存儲浮點數。在某些非IEEE754標準的計算機產生的二進制文件中,若是拿到其餘計算機中讀取,若是不進行專門的轉換,可能致使數據錯誤等問題。html
【文章索引】express
對於x86等常見的CPU,都是採用IEEE754存儲和計算浮點型的,固然在.NET平臺中浮點型也是IEEE754標準的。首先回顧下本科時學過的計算機組成原理,查了下課本發現是以下介紹IEEE754浮點數的存儲的(唐朔飛版課本233頁):oracle
其中,S爲數符,它表示浮點數的正負,但與其有效位(尾數)是分開的。階碼用移碼錶示,階碼的真值都被加上一個常數(偏移量),如短實數、長實數和臨時實數的偏移量用十六進制表示分別爲7FH、3FFH和3FFFH。尾數部分一般都是規格化表示,即非「0」的有效位最高位老是1。ide
以單精度浮點數爲例,若是字節查看應該是以下這個樣子的,數符佔第1字節的第1位,階碼佔第1字節的後7位及第二字節的第1位,其他都是尾數。this
SEF S EEEEEEEE FFFFFFF FFFFFFFF FFFFFFFF bits 1 2 9 10 32 bytes byte1 byte2 byte3 byte4
若是設數符爲S,階碼爲E,尾數的小數部分爲F,那麼能夠經過位運算獲得這三位:spa
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。code
因此可經過以下的公式來計算浮點數的值,其中,C是尾數規範化後減去的常量,B是移碼的偏移量,可知A、B、C分別爲A = 二、B = 0x7F以及C = 1.0。orm
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
【相關連接】