1. 程式人生 > >浮點數的存儲與表達

浮點數的存儲與表達

key 因此 brush sharp 科學 例如 十進制 類型 ieee754

計算機中的浮點數只是無限接近真實值的近似值。為什麽呢?首先來看一下浮點數在計算機中是如何存儲的。

浮點數的存儲        

計算機中浮點數的存儲遵循IEEE754浮點數標準。單精度用32位存儲,而雙精度用64位存儲。具體的存法如下(以單精度為例):

單精度(32位) = 1位符號位+8位指數位+23位尾數位,見下圖。

技術分享圖片

其中,符號位用來表示數值的符號。關於指數位和尾數位,需要先了解計算機表示浮點數的原理。計算機中,浮點數是二進制科學計數法來表示的。比如10.5(十進制)=1010.1(二進制)=1.0101 * 2^3.那這裏1.0101就叫尾數,3就叫指數。這兩個值會分別存入浮點數的指數和尾數區域。要註意的是:

1. 因為尾數第一位肯定為1,所以在存儲的時候省略,在讀取數據的時候在加上。這樣會將尾數部分實際的存儲能力擴大到24位。

2. 指數也有可能為負數,8位只能表示-127到128。在存儲指數的時候實際是存儲的原數據+127的值,讀取指數數據的時候再減去127。例如:指數3,實際存儲的值是3+127 = 130

指數和尾數的存儲能力直接決定了其能表示數字的範圍和精度:

1. 數字範圍:(-1)*2^(符號位) * 尾數最大數(1.1111...) * 指數最大數(2^128) = (-3.4 * 10^38, 3.4 * 10^38)

2. 精度:尾數最多能存24位,最大值為2^24 = 16777216 這個數字能完整表示 10^7以內的整型數字,因此精度為7位

C# float類型參考

浮點數的精度誤差    

小數的二進制表達:前面其實提到過,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數據類型。

浮點數的存儲與表達