iOS開發浮點數計算精度問題

一、浮點數運算帶來的問題

CGFloat badnum = 1.05f;
NSLog(@"badnumX100 = %f",badnum*100);
//輸出
//badnumX100 = 104.999995  
複製代碼

在平常工做中涉及到浮點數(float、double)的運算git

二、浮點數運算精度的解決方案

NSDecimalNumber的實現

數字19.99表示方法

#define NSDecimalMaxSize (8)
    // Give a precision of at least 38 decimal digits, 128 binary positions.

#define NSDecimalNoScale SHRT_MAX

typedef struct {
    signed   int _exponent:8;//冪指數
    unsigned int _length:4;     // length == 0 && isNegative -> NaN
    unsigned int _isNegative:1;//符號
    unsigned int _isCompact:1;
    unsigned int _reserved:18;
    unsigned short _mantissa[NSDecimalMaxSize];//存儲數據
} NSDecimal;
複製代碼

使用NSDecimalNumber進行浮點數的運算

//100.0轉化成NSDecimalNumber
    NSDecimalNumber *g_100 = [NSDecimalNumber decimalNumberWithString:@"100"];
    //1.05轉化成NSDecimalNumber
    NSDecimalNumber *g_105 = [NSDecimalNumber decimalNumberWithString:@"1.05"];
    //兩個數相乘 1.05X100
    NSDecimalNumber *goodnum = [g_105 decimalNumberByMultiplyingBy:g_100];
    NSLog(@"goodnum 1.05X100 = %@",goodnum);

    //輸出
    //goodnum 1.05X100 = 105
複製代碼

浮點數判等

因爲浮點數內部存儲地不精確,在比較兩個浮點數是否相等時,不能簡單地使用 == 符號來判斷。 判斷兩個浮點數 A, B 是否相等,須要轉化成求這兩個浮點數差的絕對值 C,即 C = fabs(A - B),而後看這個值 C 是否小於一個極小數。 若是小於一個極小數,則能夠認爲這兩個浮點數是相等的。 根據實際工程中的須要,一般這個極小數的參考值是 1e-6 或 1e-8 。bash

三、浮點數在計算機中的存儲方式致使精度問題

浮點數在計算機中的存儲方式

不管是 float 類型仍是 double 類型,在存儲方式上都是聽從IEEE的規範:spa

float 聽從的是 IEEE R32.24;double 聽從的是 IEEE R64.53;.net

單精度或雙精度在存儲中,都分爲三個部分:code

符號位 (Sign):0表明正數,1表明爲負數; 指數位 (Exponent):用於存儲科學計數法中的指數數據; 尾數部分 (Mantissa):採用移位存儲尾數部分;cdn

單精度和雙精度的存儲方式.png

R32.24 和 R64.53 的存儲方式都是用科學計數法來存儲數據的,好比:blog

8.25 用十進制表示爲:8.25 X 10^0 120.5 用十進制表示爲:1.205 X 10^2ip

而計算機根本不認識十進制的數據,他只認識0和1。因此在計算機存儲中,首先要將上面的數更改成二進制的科學計數法表示:內存

8.25 用二進制表示爲:1000.01 能夠表示爲1.0001 X2^3 120.5 用二進制表示爲:1110110.1 能夠表示爲1.1101101 X2^6ci

任何一個數的科學計數法表示都爲1. xxx * 2n ,尾數部分就能夠表示爲xxxx,因爲第一位都是1,因此將小數點前面的1省略。由此,23bit的尾數部分,能夠表示的精度卻變成了24bit,道理就是在這裏。

對於指數部分,由於指數可正可負(佔1位),因此8位的指數位能表示的指數範圍就只能用7位,範圍是:-127至128。因此指數部分的存儲採用移位存儲,存儲的數據爲元數據 加上 127

元數據 加上 127

「指數」從00000000開始(表示-127)至11111111(表示+128) 因此,10000000表示指數1 (127 + 1 = 128 --> 10000000 ) ; 指數爲 3,則爲 127 + 3 = 130,表示爲 01111111 + 11 = 10000010 ;

8.25二進制存儲.png

120.5二進制存儲.png
二進制反推出浮點數: 以下內存數據:01000010111011010000000000000000, 將該數據分段:0  10000101  11011010000000000000000
image
計算出這樣一組數據表示爲:

1101101*10(133-127=6) =1.1101101 * 2= 1110110.1=120.5

2.2和2.25的區別

單精度的 2.2 轉換爲雙精度後,精確到小數點後13位以後變爲了2.2000000476837 而單精度的 2.25 轉換爲雙精度後,變爲了2.2500000000000

2.25** 的單精度存儲方式表示爲:**0 10000001 00100000000000000000000

2.25** 的雙精度存儲方式表示爲:**0 10000000 0010010000000000000000000000000000000000000000000000000

這樣 2.25 在進行強制轉換的時候,數值是不會變的。

**將十進制的小數轉換爲二進制的小數的方法是:****將小數*2****,取整數部分。**

   0.2×2=0.4,因此二進制小數第一位爲0.4的整數部分0;

   0.4×2=0.8,第二位爲0.8的整數部分0;

   0.8×2=1.6,第三位爲1;

   0.6×2=1.2,第四位爲1;

   0.2×2=0.4,第五位爲0;

   ...... 這樣永遠也不可能乘到=1.0,獲得的二進制是一個無限循環的排列 00110011001100110011...
複製代碼

對於單精度數據來講,尾數只能表示 24bit 的精度,因此2.2的 float 存儲爲:

2.2的二進制存儲

可是這種存儲方式,換算成十進制的值,卻不會是2.2。

由於在十進制轉換爲二進制的時候可能會不許確(如:2.2),這樣就致使了偏差問題!

而且 double 類型的數據也存在一樣的問題!

因此在浮點數表示中,均可能會不可避免的產生些許偏差!

在單精度轉換爲雙精度的時候,也會存在一樣的偏差問題。

相關文章
相關標籤/搜索