17

ARMのソフトウェアラスタライザーで使用するために、Goldschmidt除算を使用してQ22.10で固定小数点の逆数を計算しています。

これは、分子を1に設定するだけで実行されます。つまり、分子は最初の反復でスカラーになります。正直なところ、私はここでウィキペディアのアルゴリズムを盲目的にフォローしています。この記事によると、分母がハーフオープン範囲(0.5、1.0]でスケーリングされている場合、最初の適切な推定値は分母のみに基づくことができます。Fを推定スカラー、Dを分母とすると、F =2- D。

しかし、これを行うと、私は多くの精度を失います。512.00002fの逆数を見つけたい場合は言います。数値を縮小するために、シフトアウトされた小数部の精度が10ビット失われます。だから、私の質問は次のとおりです。

  • 正規化を必要としないより良い見積もりを選択する方法はありますか?なんで?なぜだめですか?これが可能であるか不可能であるかについての数学的証明は素晴らしいでしょう。
  • また、級数がより速く収束するように、最初の推定値を事前に計算することは可能ですか?現在、平均して4回目の反復後に収束します。ARMでは、これは約50サイクルの最悪のケースであり、clz/bsrのエミュレーションもメモリルックアップも考慮されていません。可能であれば、そうすることでエラーが増えるかどうか、そしてどれだけ増えるかを知りたいです。

これが私のテストケースです。注:clzオンライン13のソフトウェア実装は、私の投稿からのものです。必要に応じて、組み込みに置き換えることができます。clz先行ゼロの数を返し、値0の場合は32を返す必要があります。

#include <stdio.h>
#include <stdint.h>

const unsigned int BASE = 22ULL;

static unsigned int divfp(unsigned int val, int* iter)
{
  /* Numerator, denominator, estimate scalar and previous denominator */
  unsigned long long N,D,F, DPREV;
  int bitpos;

  *iter = 1;
  D = val;
  /* Get the shift amount + is right-shift, - is left-shift. */
  bitpos = 31 - clz(val) - BASE;
  /* Normalize into the half-range (0.5, 1.0] */
  if(0 < bitpos)
    D >>= bitpos;
  else
    D <<= (-bitpos);

  /* (FNi / FDi) == (FN(i+1) / FD(i+1)) */
  /* F = 2 - D */
  F = (2ULL<<BASE) - D;
  /* N = F for the first iteration, because the numerator is simply 1.
     So don't waste a 64-bit UMULL on a multiply with 1 */
  N = F;
  D = ((unsigned long long)D*F)>>BASE;

  while(1){
    DPREV = D;
    F = (2<<(BASE)) - D;
    D = ((unsigned long long)D*F)>>BASE;
    /* Bail when we get the same value for two denominators in a row.
      This means that the error is too small to make any further progress. */
    if(D == DPREV)
      break;
    N = ((unsigned long long)N*F)>>BASE;
    *iter = *iter + 1;
  }
  if(0 < bitpos)
    N >>= bitpos;
  else
    N <<= (-bitpos);
  return N;
}


int main(int argc, char* argv[])
{
  double fv, fa;
  int iter;
  unsigned int D, result;

  sscanf(argv[1], "%lf", &fv);

  D = fv*(double)(1<<BASE);
  result = divfp(D, &iter); 

  fa = (double)result / (double)(1UL << BASE);
  printf("Value: %8.8lf 1/value: %8.8lf FP value: 0x%.8X\n", fv, fa, result);
  printf("iteration: %d\n",iter);

  return 0;
}
4

3 に答える 3

12

私はあなたの問題に1時間を費やすことに抵抗できませんでした...

このアルゴリズムは、Jean-MichelMullerによる「Arithmetiquedesordinateurs」のセクション5.5.2で説明されています(フランス語)。これは実際には、開始点として1を使用するニュートン反復の特殊なケースです。この本は、N / Dを計算するためのアルゴリズムの簡単な定式化を示しており、Dは範囲[1 / 2,1 [:

e = 1 - D
Q = N
repeat K times:
  Q = Q * (1+e)
  e = e*e

正しいビット数は、反復ごとに2倍になります。32ビットの場合、4回の反復で十分です。e変更するには小さすぎるまで繰り返すこともできますQ

正規化が使用されるのは、結果の有効ビットの最大数を提供するためです。また、入力が既知の範囲にある場合に必要なエラーと反復回数を計算する方が簡単です。

入力値が正規化されると、逆数になるまでBASEの値を気にする必要はありません。0x80000000から0xFFFFFFFFの範囲で正規化された32ビットの数値Xがあり、Y = 2 ^ 64 / Xの近似値を計算します(Yは最大2 ^ 33です)。

この簡略化されたアルゴリズムは、Q22.10表現に次のように実装できます。

// Fixed point inversion
// EB Apr 2010

#include <math.h>
#include <stdio.h>

// Number X is represented by integer I: X = I/2^BASE.
// We have (32-BASE) bits in integral part, and BASE bits in fractional part
#define BASE 22
typedef unsigned int uint32;
typedef unsigned long long int uint64;

// Convert FP to/from double (debug)
double toDouble(uint32 fp) { return fp/(double)(1<<BASE); }
uint32 toFP(double x) { return (int)floor(0.5+x*(1<<BASE)); }

// Return inverse of FP
uint32 inverse(uint32 fp)
{
  if (fp == 0) return (uint32)-1; // invalid

  // Shift FP to have the most significant bit set
  int shl = 0; // normalization shift
  uint32 nfp = fp; // normalized FP
  while ( (nfp & 0x80000000) == 0 ) { nfp <<= 1; shl++; } // use "clz" instead

  uint64 q = 0x100000000ULL; // 2^32
  uint64 e = 0x100000000ULL - (uint64)nfp; // 2^32-NFP
  int i;
  for (i=0;i<4;i++) // iterate
    {
      // Both multiplications are actually
      // 32x32 bits truncated to the 32 high bits
      q += (q*e)>>(uint64)32;
      e = (e*e)>>(uint64)32;
      printf("Q=0x%llx E=0x%llx\n",q,e);
    }
  // Here, (Q/2^32) is the inverse of (NFP/2^32).
  // We have 2^31<=NFP<2^32 and 2^32<Q<=2^33
  return (uint32)(q>>(64-2*BASE-shl));
}

int main()
{
  double x = 1.234567;
  uint32 xx = toFP(x);
  uint32 yy = inverse(xx);
  double y = toDouble(yy);

  printf("X=%f Y=%f X*Y=%f\n",x,y,x*y);
  printf("XX=0x%08x YY=0x%08x XX*YY=0x%016llx\n",xx,yy,(uint64)xx*(uint64)yy);
}

コードに記載されているように、乗算は完全な32x32->64ビットではありません。Eはどんどん小さくなり、最初は32ビットに収まります。Qは常に34ビットになります。製品の上位32ビットのみを使用します。

の導出は64-2*BASE-shl、読者の演習として残されています:-)。0または負になると、結果を表現できなくなります(入力値が小さすぎます)。

編集。私のコメントのフォローアップとして、Qに暗黙の32ビットを含む2番目のバージョンがあります。EとQの両方が32ビットに格納されるようになりました。

uint32 inverse2(uint32 fp)
{
  if (fp == 0) return (uint32)-1; // invalid

  // Shift FP to have the most significant bit set
  int shl = 0; // normalization shift for FP
  uint32 nfp = fp; // normalized FP
  while ( (nfp & 0x80000000) == 0 ) { nfp <<= 1; shl++; } // use "clz" instead
  int shr = 64-2*BASE-shl; // normalization shift for Q
  if (shr <= 0) return (uint32)-1; // overflow

  uint64 e = 1 + (0xFFFFFFFF ^ nfp); // 2^32-NFP, max value is 2^31
  uint64 q = e; // 2^32 implicit bit, and implicit first iteration
  int i;
  for (i=0;i<3;i++) // iterate
    {
      e = (e*e)>>(uint64)32;
      q += e + ((q*e)>>(uint64)32);
    }
  return (uint32)(q>>shr) + (1<<(32-shr)); // insert implicit bit
}
于 2010-04-23T15:38:24.560 に答える
1

いくつかのアイデアがありますが、述べられているように問題を直接解決するものはありません。

  1. なぜ除算にこのアルゴリズムを使用するのですか? 私がARMで見たほとんどの除算は、いくつかの変種を使用しています
    
          adcs hi, den, hi, lsl #1
          subcc hi, hi, den
          adcs lo, lo, lo
    

どこから開始するかを決定するために、clz のバイナリ検索で n ビット回繰り返されます。それはかなり速いです。

  1. 精度が大きな問題である場合、固定小数点表現は 32/64 ビットに制限されません。少し遅くなりますが、 add/adc または sub/sbc を実行してレジスタ間で値を移動できます。mul/mla もこの種の作業用に設計されています。

繰り返しますが、直接的な回答ではありませんが、これを進めるためのいくつかのアイデアがあります。実際の ARM コードを見ることも、おそらく少しは役立つでしょう。

于 2010-04-22T18:37:03.960 に答える
0

マッド、あなたはまったく精度を失っていません。512.00002f を 2^10 で割ると、浮動小数点数の指数が 10 減るだけです。仮数は変わりません。もちろん、指数が最小値に達しない限り、(0.5, 1].

編集:わかりましたので、固定小数点を使用しています。その場合、アルゴリズムで分母の異なる表現を許可する必要があります。D の値は、最初だけでなく、計算全体を通して (0.5, 1] からです (x < 1 に対して x * (2-x) < 1 であることを証明するのは簡単です)。したがって、分母を 10 進数で表す必要があります。 base = 32 をポイントします。このようにして、常に 32 ビットの精度が得られます。

編集:これを実装するには、コードの次の行を変更する必要があります。

  //bitpos = 31 - clz(val) - BASE;
  bitpos = 31 - clz(val) - 31;
...
  //F = (2ULL<<BASE) - D;
  //N = F;
  //D = ((unsigned long long)D*F)>>BASE;
  F = -D;
  N = F >> (31 - BASE);
  D = ((unsigned long long)D*F)>>31;
...
    //F = (2<<(BASE)) - D;
    //D = ((unsigned long long)D*F)>>BASE;
    F = -D;
    D = ((unsigned long long)D*F)>>31;
...
    //N = ((unsigned long long)N*F)>>BASE;
    N = ((unsigned long long)N*F)>>31;

また、最終的には、N を bitpos ではなく別の値にシフトする必要があります。

于 2010-04-22T10:16:51.387 に答える