科學計算當中會用到不少浮點數的操作,這些浮點數可能是16位,32位,64位,80位甚至是128位。開源專案SoftFloat提供了一個高效的浮點運算實現,可以在沒有硬體支援的情況下,高效模擬浮點數的各種操作。
那麼,浮點數之間的比較,基本運算這些究竟是怎麼實現的呢,可以拿32位浮點數作為例子。
這是32位浮點數加法的實現,首先宣告瞭一個結構體float32_t。
typedef struct { uint32_t v; } float32_t;
這提供了32位浮點數的底層位表示,同時還宣告瞭一個union。
union ui32_f32 { uint32_t ui; float32_t f; };
一方面儲存了浮點數的位表示,另一方面也可以轉換為32位無符號整型直接進行比較,這在後面的演算法當中會直接涉及。先看看加法。
float32_t f32_add( float32_t a, float32_t b )
{
union ui32_f32 uA;
uint_fast32_t uiA;
union ui32_f32 uB;
uint_fast32_t uiB;
#if ! defined INLINE_LEVEL || (INLINE_LEVEL < 1)
float32_t (*magsFuncPtr)( uint_fast32_t, uint_fast32_t );
#endif
uA.f = a;
uiA = uA.ui;
uB.f = b;
uiB = uB.ui;
#if defined INLINE_LEVEL && (1 <= INLINE_LEVEL)
if ( signF32UI( uiA ^ uiB ) ) {
return softfloat_subMagsF32( uiA, uiB );
} else {
return softfloat_addMagsF32( uiA, uiB );
}
#else
magsFuncPtr =
signF32UI( uiA ^ uiB ) ? softfloat_subMagsF32 : softfloat_addMagsF32;
return (*magsFuncPtr)( uiA, uiB );
#endif
}
這裡uiA和uiB是儲存無符號整型的,signF32UI是提取符號位的。signF32UI(uiA ^ uiB)判斷符號位是否相同,如果相同則呼叫加法,如果符號位不相同則呼叫減法,因為沒有浮點數,所以只能透過整型去模擬,另外,union儲存浮點和整型有一個名詞,似乎叫型別雙關技術?不過這裡union儲存的只是位表示,並不是真的浮點數。
float32_t f32_sub( float32_t a, float32_t b )
{
union ui32_f32 uA;
uint_fast32_t uiA;
union ui32_f32 uB;
uint_fast32_t uiB;
#if ! defined INLINE_LEVEL || (INLINE_LEVEL < 1)
float32_t (*magsFuncPtr)( uint_fast32_t, uint_fast32_t );
#endif
uA.f = a;
uiA = uA.ui;
uB.f = b;
uiB = uB.ui;
#if defined INLINE_LEVEL && (1 <= INLINE_LEVEL)
if ( signF32UI( uiA ^ uiB ) ) {
return softfloat_addMagsF32( uiA, uiB );
} else {
return softfloat_subMagsF32( uiA, uiB );
}
#else
magsFuncPtr =
signF32UI( uiA ^ uiB ) ? softfloat_addMagsF32 : softfloat_subMagsF32;
return (*magsFuncPtr)( uiA, uiB );
#endif
}
減法則是在判斷符號那裡反過來,其它一樣。這時候可以看看比較運算怎麼做。
bool f32_le( float32_t a, float32_t b )
{
union ui32_f32 uA;
uint_fast32_t uiA;
union ui32_f32 uB;
uint_fast32_t uiB;
bool signA, signB;
uA.f = a;
uiA = uA.ui;
uB.f = b;
uiB = uB.ui;
if ( isNaNF32UI( uiA ) || isNaNF32UI( uiB ) ) {
softfloat_raiseFlags( softfloat_flag_invalid );
return false;
}
signA = signF32UI( uiA );
signB = signF32UI( uiB );
return
(signA != signB) ? signA || ! (uint32_t) ((uiA | uiB)<<1)
: (uiA == uiB) || (signA ^ (uiA < uiB));
}
最後的表示式有點繞,一步一步拆分。首先符號不相等(一正一負)的話,如果A的符號是1,也就是負數,肯定比B小,否則走 || 後的分支。把A和B的最高位(符號位)剔除,判斷是否相同,也就是+0和-0的情況,這裡記得別漏了前面的!符號,因為判斷兩者是否都為0;如果A和B同號的話,如果都是正數則直接比較,如果都是負數,則前面的signA會對結果取反。
結語
最近處於校招階段,正在準備,有時間會分享自己的心得和體會,希望儘早上岸。
本部落格參考豆莢加速器。轉載請註明出處!