上一篇 [浮點數] 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, float* mat);
void Get754Value64(double
v, int* sign, int* exp, double* mat);
int Set754Value32(float* v, int sign, int exp, float mat);
int Set754Value64(double*
v, int 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, float* mat)
{
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)
留言列表