ハードウェアに 32 ビット浮動小数点ハードウェア除算器を実装しようとしていますが、異なるアルゴリズム間のトレードオフについて何か提案が得られるかどうか疑問に思っています。
私の浮動小数点ユニットは現在、乗算と加算/減算をサポートしていますが、これを融合型乗加算 (FMA) 浮動小数点アーキテクチャに切り替えるつもりはありません。これは、領域の使用を最小限に抑えようとしている組み込みプラットフォームだからです。
ハードウェアに 32 ビット浮動小数点ハードウェア除算器を実装しようとしていますが、異なるアルゴリズム間のトレードオフについて何か提案が得られるかどうか疑問に思っています。
私の浮動小数点ユニットは現在、乗算と加算/減算をサポートしていますが、これを融合型乗加算 (FMA) 浮動小数点アーキテクチャに切り替えるつもりはありません。これは、領域の使用を最小限に抑えようとしている組み込みプラットフォームだからです。
むかしむかし、当時の軍用FPUで使用されていた、このきちんとした実装が簡単な浮動小数点/固定小数点除算アルゴリズムに出くわしました。
入力は符号なしでシフトする必要がありx < y
、両方が範囲内にある< 0.5 ; 1 >
sh = shx - shy
シフトと元のサインの違いを保存することを忘れないでください
find f
(反復による) so y*f -> 1
.... その後 x*f -> x/y
の除算結果
x*f
後ろにシフトしてsh
結果の符号を復元する(sig=sigx*sigy)
これは、次のx*f
ように簡単に計算できます。
z=1-y
(x*f)=(x/y)=x*(1+z)*(1+z^2)*(1+z^4)*(1+z^8)*(1+z^16)...(1+z^2n)
どこ
n = log2(num of fractional bits for fixed point, or mantisa bit size for floating point)
z^2n
固定ビット幅のデータ型でゼロになったときに停止することもできます。
[編集 2] 少し時間があったので、ここで 32 ビット IEEE 754 C++ 実装
将来の読者の混乱を避けるために、古い (bignum) 例を削除しました (必要に応じて、編集履歴から引き続きアクセスできます)。
//---------------------------------------------------------------------------
// IEEE 754 single masks
const DWORD _f32_sig =0x80000000; // sign
const DWORD _f32_exp =0x7F800000; // exponent
const DWORD _f32_exp_sig=0x40000000; // exponent sign
const DWORD _f32_exp_bia=0x3F800000; // exponent bias
const DWORD _f32_exp_lsb=0x00800000; // exponent LSB
const DWORD _f32_exp_pos= 23; // exponent LSB bit position
const DWORD _f32_man =0x007FFFFF; // mantisa
const DWORD _f32_man_msb=0x00400000; // mantisa MSB
const DWORD _f32_man_bits= 23; // mantisa bits
//---------------------------------------------------------------------------
float f32_div(float x,float y)
{
union _f32 // float bits access
{
float f; // 32bit floating point
DWORD u; // 32 bit uint
};
_f32 xx,yy,zz; int sh; DWORD zsig; float z;
// result signum abs value
xx.f=x; zsig =xx.u&_f32_sig; xx.u&=(0xFFFFFFFF^_f32_sig);
yy.f=y; zsig^=yy.u&_f32_sig; yy.u&=(0xFFFFFFFF^_f32_sig);
// initial exponent difference sh and normalize exponents to speed up shift in range
sh =0;
sh-=((xx.u&_f32_exp)>>_f32_exp_pos)-(_f32_exp_bia>>_f32_exp_pos); xx.u&=(0xFFFFFFFF^_f32_exp); xx.u|=_f32_exp_bia;
sh+=((yy.u&_f32_exp)>>_f32_exp_pos)-(_f32_exp_bia>>_f32_exp_pos); yy.u&=(0xFFFFFFFF^_f32_exp); yy.u|=_f32_exp_bia;
// shift input in range
while (xx.f> 1.0f) { xx.f*=0.5f; sh--; }
while (xx.f< 0.5f) { xx.f*=2.0f; sh++; }
while (yy.f> 1.0f) { yy.f*=0.5f; sh++; }
while (yy.f< 0.5f) { yy.f*=2.0f; sh--; }
while (xx.f<=yy.f) { yy.f*=0.5f; sh++; }
// divider block
z=(1.0f-yy.f);
zz.f=xx.f*(1.0f+z);
for (;;)
{
z*=z; if (z==0.0f) break;
zz.f*=(1.0f+z);
}
// shift result back
for (;sh>0;) { sh--; zz.f*=0.5f; }
for (;sh<0;) { sh++; zz.f*=2.0f; }
// set signum
zz.u&=(0xFFFFFFFF^_f32_sig);
zz.u|=zsig;
return zz.f;
}
//---------------------------------------------------------------------------
シンプルにしたかったので、まだ最適化されていません。たとえば、all*=0.5
と*=2.0
by exponentを置き換えることができinc/dec
ます...float
演算子で FPU の結果と比較すると/
、ほとんどの FPU は 80 ビットの内部形式で計算し、この実装は 32 ビットでのみ実行されるため、これは少し正確ではありません。
ご覧のとおり、私は FPU から使用してい+,-,*
ます。次のような高速 sqr アルゴリズムを使用することで、処理を高速化できます。
特に大きなビット幅を使用したい場合...
正規化やオーバーフロー/アンダーフローの修正を実装することを忘れないでください。