[回目錄]

 

上一篇 [浮點數] C 語言取出/設定 IEEE754 欄位(hex) 裡筆者提到,

如何直接取出/設定各欄位之 16 進制值,

事實上一些 IEEE754 online converter 網站,用這二個功能就完成一半,

此篇文章將續述,如何取出 signed、exponment、mantissa 各欄位之實際數值。

 


 

單精度浮點數填入 IEEE754 欄位

 

這次筆者直接套例子做為說明,假設欲存入之浮點數為單精度(Single Precision) 浮點數,

32 bits,C 語言資料型態為 float,假設存入之數值為 11.625,怎麼存入 float ?

先再看一次 Single Precision 欄位

 

Single precision (32 bits)

Signed (正負號) :佔 1 bit ,b31。

Exponment (指數):佔 8 bits,b30~b23。

Matnissa (尾數):佔 23 bits,b22~b20。

 

步驟如下

 

(1) Signed Field

11.625 大於 0 ,為正數,Singed Field(1bit) 填入 0;

若填入之數值為負數,則將 Signed Field 填入 1。

欄位變成

0 eeeeeeee sssssssssssssssssssssss

 

(2) 先將 11.625 化為二進位

11.625 (10) = 1011.101(2)

 

(3) 將 1011.101 做成科學表示法,移動至小數前只有 1 個 1

1011.101(2) = 1.011101 * 2^3 (2)

移動完後,小數點為 011101,故 mantissa(23 bits) 填入 011101,

欄位變成

0 eeeeeeee 01110100000000000000000

 

(4) 指數部份加上 base value = 2 ^ (exponment bits-1)-1

由於 Single Precision 之 exponment 欄位,其 bit 數為 8,

其 base value 即為 2^ (8-1) - 1 = 128 - 1 = 127,

而 11.625 (10) = 1011.101(2) = 1011.101(2) = 1.011101  * 2^3 (2)

指數部份為 3,3 + base value (127) = 130 ,

故在 exponment field 填入 130 之二進位,即 10000010,

欄位變成

0 10000010 01110100000000000000000

直接翻成16 進位,即 0x413a0000。

 


 

倍精度浮點數填入 IEEE754 欄位

 

倍精度填入的原理也如上所述,一樣拿 11.625 存入 double ,過程如下

 

步驟如下

 

(1) Signed Field

11.625 大於 0 ,為正數,Singed Field (1bit) 填入 0;

若填入之數值為負數,則將 Signed Field 填入 1。

欄位變成

0 eeeeeeeeeee ssssssssssssssssssssssssssssssssssssssssssssssssssss

 

(2) 先將 11.625 化為二進位

11.625 (10) = 1011.101(2)

 

(3) 將 1011.101 做成科學表示法,移動至小數前只有 1 個 1

1011.101(2) = 1.011101 * 2^3 (2)

移動完後,小數點為 011101,故 mantissa(52bits) 填入 011101,

欄位變成

0 eeeeeeee 0111010000000000000000000000000000000000000000000000

(4) 指數部份加上 base value = 2 ^ (exponment bits-1)-1

double precision 之 exponment field 有 11 bits,

base = 2^ (11-1) - 1 = 1024 - 1 = 1023,

而 11.625 (10) = 1011.101(2) = 1011.101(2) = 1.011101  * 2^3 (2)

指數部份為 3,3 + base value (1023) = 1026 ,

故在 exponment field (11 bits) 填入 1026 之二進位,即 10000000010,

欄位變成

 0 10000000010 0111010000000000000000000000000000000000000000000000

直接翻成16 進位,即 0x4027400000000000。

 

 


 

IEEE754 欄位解釋數值

 

基本上,會填入就會取出,這裡以 float ,取出 0x413a0000 為例

0x413a0000(16) = 0 10000010 01110100000000000000000 

---->  (-1)^0 * 1.01110100000000000000000  * 2^( 10000010 - base)

要扣掉 base value 原因是,在存進去時有加  base value,自然取出也要扣除,

由於是 float,base value = 127,於是變成

(-1)^0 * 1.01110100000000000000000  * 2^( 10000010 - base)

= (-1)^0 * 1.01110100000000000000000  * 2^( 130 - 127)

= 1.01110100000000000000000  * 2^( 3)

= 1011.101(2) = 11.625(10)

 

double precision 便不再舉例說明,若認為此處說明不夠清楚,

可參考 IEEE754, C/C++ 浮點數誤差 這篇文章。

 

 


精準度

 

既已提到此,筆者順道一提一個常讓初學者感到困惑的事。

筆者以

float f= 0.1f 為例,

float 之精準度為小數點以下 7 位數 (可能道聽塗說來的),

為何最後以 printf("%.8f\n", f); 出來不會是剛好 0.1f ?

明明 float 只用了小數點下面一位數,結果卻一點都不精準。

 

這裡有幾個謬誤存在

(1) float 之精準度為小數點以下 7 位數,這說法並非完全正確;也可能敘述者觀念是正確的,但接受者卻誤會了其意。

(2) 電腦是用二進位存數值,而不是 10 進位存數值。

 

上面第二點我該補充一下,那是「大多」之情況,因在 IEEE754 規格裡面,

也存在著以 10 進位模式存放,但常見之高階程式語言,還是以二進位方式存放,

講到這裡就晃然大悟了。

0.1 (10) = 0.00011 00011 00011 00011 00011 00011 00011.... (2)

float 為單精度浮點數 ( 32 bits ),其 mantissan 佔 23 bits,

正規化後,其 mantissa 欄位變成

1.100011 00011 00011 00011 00011 00011 00011 00011 ....

只會存入藍色之 23 bits 下來,灰色部份全都截掉 (截尾誤差)。

這種化為二進位為循環小數的數字,

最終存在電腦是有限的,不會全部存下來,存多少 bits ?

視 mantissa 欄位而定,單精度為存 23 bits ,若再加上正規化

(下篇文章將提到正規化) 之隱性位元,實際上可表達精度為 24

(也有人覺得寫 23.5 較好,此處寫 24),

但那 24 位元之精度,是「二進制」,如果要表示成 「幾位數十進制」的話,

公式是   log(2 ^ 24 ) = 24 * log2 = 7.22 ,

所以可表達的約是 7.22 位數之十進位,

但不代表 7 位數以內的十進位就絕對不會有誤差。

至於 double ,其 mantissa 為 52 bits,加上隱性位元,其十進位之精度為

log( 2 ^ (52 + 1) ) = 53 * log2 = 15.95 位元。

 

這一大串的謬誤,我想還是會有人持續問下去。

 


 

C 語言取出正規化之浮點數欄位

 

鑑於以上說明,這次的程式是以 bit wise 之方式,

取出 IEEE754 之 sign、exponment、mantissa 欄位,

並轉換其對應之數值,即根據以下式子,取出各欄位對應數值

floating = (-1) ^ sign * 1.mantissa * 2^(exponment+base)

同時也再寫另一組做反向操作。

 

細節有幾個地方要注意

假設 matissa 取出來是 1010000000000000 時,

初值 rst = 1.0 (記得正規化時,小數點前面是 1),

mantissa = 1010000000000000 , pwr = 0.5 ,    rst = rst + 0.5 = 1.5
mantissa = 1010000000000000 , pwr = 0.25,   rst = 1.5
mantissa = 1010000000000000 , pwr = 0.125, rst = 1.5 + 0.125 = 1.625

而在丟進去的時候,必需注意


(a) 寫入 mantissa 時,轉 16 進位,不得超過所使用之位元數 (float 23 位元,double 52 位元)。
(b)  寫入 exp 時,必須確保加上 base value 後,其值可值入 exponment 內 ( float 8 bits,double 11 bits)。

 

程式碼

 

/*******************************************************************/
/*                                                                 */
/*     filename : FloatingValue.c                                  */
/*     author   : edison.shih/edisonx                              */
/*     compiler : Visual C++ 2008                                  */
/*     date     : 2011.03.07                                       */
/*                                                                 */
/*         A.L.L.      R.I.G.H.T.S.     R.E.S.E.R.V.E.             */
/*                                                                 */
/*******************************************************************/
#include <stdio.h>

typedef unsigned uint;
typedef unsigned long long ull;
//--------------------------------------------------
#define FLT_SIGN_BITS 1           // float signed     field using bit
#define FLT_EXP_BITS  8           // float exponment  field using bits
#define FLT_MAT_BITS  23          // float mantissa   field using bits

#define FLT_SIGN_MASK 0x80000000U // float signed bit
#define FLT_EXP_MASK  0x7f800000U // float exponment bits
#define FLT_MAT_MASK  0x007fffffU // float mantissa bits

#define DBL_SIGN_BITS 1           // double signed    field using bit
#define DBL_EXP_BITS  11          // double exponment field using bits
#define DBL_MAT_BITS  52          // double mantissa  field using bits

#define DBL_SIGN_MASK 0x8000000000000000ULL
#define DBL_EXP_MASK  0x7ff0000000000000ULL
#define DBL_MAT_MASK  0x000fffffffffffffULL

#define FLT_BASE_VAL  ((1U  <<(FLT_EXP_BITS-1))-1U  ) // float  base value
#define DBL_BASE_VAL  ((1ULL<<(DBL_EXP_BITS-1))-1ULL) // double base value
//--------------------------------------------------
void   Get754Value32(float  v, int* sign, int* exp, floatmat);
void   Get754Value64(double v, int* sign, int* exp, double* mat);
int  Set754Value32(floatvint sign, int exp, float  mat);
int  Set754Value64(double* vint sign, int exp, double mat);


///////////////////////////////////////////////////////////////////

int main()
{
     float  f = -3456.482f, f_mat;
     double d = -24.687, d_mat;
     int sign, exp, ret;

     // ------------------------------------------------------
     // Get Single Precision Field Value
     sign = exp = ret = 0 ;//
清除
     Get754Value32(f, &sign, &exp, &f_mat); // 取得欄位
     printf("%f = -1^%d * %f * 2^%d\n", f, sign, f_mat, exp);

     // ------------------------------------------------------
     // Set Single Precision Field Value 
     f = 0.0f, ret = Set754Value32(&f, sign, exp, f_mat); //
設定欄位
     printf("after set value: %f(ret = %d)\n", f, ret);

     // ------------------------------------------------------
     // Get Double Precision Field Value
     sign = exp = ret = 0 ;//
清除
     Get754Value64(d, &sign, &exp, &d_mat); // 取得欄位
     printf("\n%lf = -1^%d * %lf * 2^%d\n", d, sign, d_mat, exp);

     // ------------------------------------------------------
     // Set Double Precision Field Value
     d = 0.0, ret = Set754Value64(&d, sign, exp, d_mat); //
設定欄位
     printf("after set value: %lf(ret = %d)\n", d, ret);

     return 0;
}

///////////////////////////////////////////////////////////////////
//--------------------------------------------------
//
單精度取得正規化之欄位
void   Get754Value32(float  v, int* sign, int* exp, floatmat)
{
     float pwr=0.5f;
     uint value   = *(uint*)&v;
    
     *sign = (int)((value & FLT_SIGN_MASK) >> (FLT_MAT_BITS + FLT_EXP_BITS));
     *exp  = (int)((value & FLT_EXP_MASK) >> FLT_MAT_BITS  ) - FLT_BASE_VAL;

     // calculate mantissa field
    
     *mat=1.0f, value<<=(FLT_SIGN_BITS + FLT_EXP_BITS);
     while(value){
           if(value & FLT_SIGN_MASK) *mat+=pwr;
           pwr*=0.5f;
           value<<=1;
     }
}
//--------------------------------------------------
//
倍精度取得正規化之欄位
void   Get754Value64(double v, int* sign, int* exp, double* mat)
{
     double pwr=0.5;
     ull value   = *(ull*)&v;

     *sign = (int)((value & DBL_SIGN_MASK) >> (DBL_MAT_BITS + DBL_EXP_BITS));
     *exp  = (int)((value & DBL_EXP_MASK) >> DBL_MAT_BITS  ) - DBL_BASE_VAL;

     // calculate mantissa field
     *mat=1.0, value<<=(DBL_SIGN_BITS + DBL_EXP_BITS);
     while(value){
           if(value & DBL_SIGN_MASK) *mat+=pwr;
           pwr*=0.5;
           value<<=1;
     }
}
//--------------------------------------------------
//
單精度正規化數值寫入欄位, 成功傳回,失敗傳
int Set754Value32(float* f, int sign, int exp, float  mat)
{
     uint  *pv =  (uint*)f;
     uint    m = *(uint*)&mat;
     uint mask =  (1U<<(FLT_MAT_BITS-1));
    
     // error defect
     exp+=FLT_BASE_VAL;
     if(exp<0 || exp>= (1U << FLT_EXP_BITS))return 0;
     if(mat>=2.0f || mat<1.0f) return 0;
     *pv=0U;

     // set sign and exp
     if(sign) *pv|=FLT_SIGN_MASK;
     *pv|=(exp << FLT_MAT_BITS);

     // cal and set mantissa
     mat-=1.0f;
     while(mask && mat!=0.0f) {
           if(mask & m) *pv|=mask;
           mat*=2.0f;
           if(mat>1.0f) mat-=1.0f;        
           mask>>=1;
     }
     return 1;
}
//--------------------------------------------------
//
倍精度正規化數值寫入欄位, 成功傳回,失敗傳
int Set754Value64(double* d,int sign, int exp, double mat)
{
     ull  *pv =  (ull*)d;
     ull    m = *(ull*)&mat;
     ull mask =  (1ULL<<(DBL_MAT_BITS-1));

     // error defect
     exp+=DBL_BASE_VAL;
     if(exp<0 || exp>= (1ULL << DBL_EXP_BITS))return 0;
     if(mat>=2.0 || mat<1.0) return 0;
     *pv=0ULL;

     // set sign and exp
     if(sign) *pv|=DBL_SIGN_MASK;
     *pv|=((ull)exp << DBL_MAT_BITS);

     // cal and set mantissa
     mat-=1.0; 
     while(mask && mat!=0.0) {
           if(mask & m) *pv|=mask;
           mat*=2.0;
           if(mat>1.0) mat-=1.0;     
           mask>>=1;
     }
     return 1;
}


執行結果

 

-3456.481934 = -1^1 * 1.687735 * 2^11
after set value: -3456.481934(ret = 1)

-24.687000 = -1^1 * 1.542938 * 2^4
after set value: -24.687000(ret = 1)

 

[回目錄]

arrow
arrow
    全站熱搜

    edisonx 發表在 痞客邦 留言(0) 人氣()