計算機中的浮點數只是無限接近真實值的近似值。爲何呢?首先來看一下浮點數在計算機中是如何存儲的。算法
浮點數的存儲 spa
計算機中浮點數的存儲遵循IEEE754浮點數標準。單精度用32位存儲,而雙精度用64位存儲。具體的存法以下(以單精度爲例):code
單精度(32位) = 1位符號位+8位指數位+23位尾數位,見下圖。orm
其中,符號位用來表示數值的符號。關於指數位和尾數位,須要先了解計算機表示浮點數的原理。計算機中,浮點數是二進制科學計數法來表示的。好比10.5(十進制)=1010.1(二進制)=1.0101 * 2^3.那這裏1.0101就叫尾數,3就叫指數。這兩個值會分別存入浮點數的指數和尾數區域。要注意的是:blog
1. 由於尾數第一位確定爲1,因此在存儲的時候省略,在讀取數據的時候在加上。這樣會將尾數部分實際的存儲能力擴大到24位。ip
2. 指數也有可能爲負數,8位只能表示-127到128。在存儲指數的時候實際是存儲的原數據+127的值,讀取指數數據的時候再減去127。例如:指數3,實際存儲的值是3+127 = 130內存
指數和尾數的存儲能力直接決定了其能表示數字的範圍和精度:ci
1. 數字範圍:(-1)*2^(符號位) * 尾數最大數(1.1111...) * 指數最大數(2^128) = (-3.4 * 10^38, 3.4 * 10^38)get
2. 精度:尾數最多能存24位,最大值爲2^24 = 16777216 這個數字能完整表示 10^7之內的整型數字,所以精度爲7位string
浮點數的精度偏差
小數的二進制表達:前面其實提到過,10.5的二進制是1010.1,整數部分的算法是除二取餘(餘數爲當前位的二進制值)。小數部分的算法是乘二取整數部分做爲當前位的二進制值,小數部分繼續進行乘二取整。因此10.5的二進制表達爲1010.1。
可是現實狀況是大部分小數乘2取整的最後是一個無限循環。好比:0.99,能夠一直執行乘二取整,最後也不會獲得一個乘二後值爲1的數字。因此這種值會變爲二進制時是沒法存儲其精確值的,由於尾數的存儲位數是有限制的。(這裏的單精度爲23位) 所以會有精度丟失,當從內存中取值時獲得的數值實際上是無限接近於真實值的近似值。
好比執行以下代碼:
float fValue = 0.99F; Console.WriteLine("float:" + fValue.ToString("G8"));
// output: 0.99000001
緣由是什麼呢?運用上面的存儲原理能夠作以下解釋:0.99轉換爲二進制後的值爲(只列出前25位):1111110101110000101000111(25bit),可是單精度只能存24位,那實際存儲的值爲:111111010111000010100011(24bit)。這裏就丟掉了一位數據。當還原爲10進制數時就會不夠準確。其實這裏我嘗試還原了一下,可是獲得的結果和預期不太同樣(結果爲:0.9899999499320983886716),可能計算過程當中自己也有偏差致使。還原代碼以下:
static void BinToDecimal(string binString) { decimal result = 0; int index = -1; foreach (var c in binString) { if (c == '1') { result += (decimal)Math.Pow(2, index); } index -= 1; } Console.WriteLine(result); }
上述代碼若是咱們這樣寫:
float fValue = 0.99F; Console.WriteLine("float:" + fValue.ToString()); // output:0.99
結果就是對的,緣由是fValue.ToString() 默認是顯示7位精度。這樣獲得的結果就是0.99。
各類數據類型的ToString()方法默認顯示精度見MSDN
Decimal類型
在財務計算等高精度要求的場景,浮點數的偏差可能會形成嚴重後果。因此decimal類型適用於這種高精度的場景。decimal的存儲方式是:
符號位(1bit) + 數值位(96bit) + 放大因子(31bit) = 128bit
其中數值位用來存儲要表示的完整數字去掉小數點後的整數表示,放大因子決定小數點的位置。所以所能存儲的數字範圍爲(-2^96,2^96)對應十進制:(-79228162514264337593543950336,79228162514264337593543950336).因此decimal一共是能表示精度28位的數字。不存在十進制與二進制的轉換,因此不會有精度偏差。因此對數值精度要求高時推薦使用decimal數據類型。