0

現在、次のようにログを計算します。

#define MAXLOG 1001
double myloglut[MAXLOG];
void MyLogCreate()
{
    int i;
    double exp, expinc;
    expinc = (2.0 - 0.1) / MAXLOG;
    for (i = 0, exp = 0.1; i <= MAXLOG; ++i, exp += expinc)
            myloglut[i] = log(exp);
    myloglut[478] = 0; // this one need to be precise
}

double MyLog(double v)
{
    int idx = (int)((MAXLOG*(v - 0.1)) / (2.0 - 0.1));
    return myloglut[idx];
}

ご覧のとおり、私は range のみに関心があります0.1 - 2.0。ただし、 のあたりでもっと精度が必要0です。その非線形計算をどのように達成できますか? また、この関数で補間を使用して精度を上げる方法はありますか?

4

3 に答える 3

2

最後のバージョン

#include <stdio.h>                  // for input/output.
#include <math.h>                   // for mathmatic functions (log, pow, etc.)

// Values
#define MAXELM 1000                 // Array size
#define MINVAL 0.1                  // Minimum x value
#define MAXVAL 1.9                  // Maximum x value
#define EXPVAR 1.4                  // Exponent which makes the variation non linear. If set to 1, the variation will be linear.
#define ACRTPT (MINVAL + MAXVAL)/2  // Accurate point. This value is used to know where to compute with maximum accuracy. Can be set to a fixed value.
// Behavior
#define STRICT 0                    // if TRUE: Return -1 instead of the floored (or closest if out of range) offset when (x) hasn't been calculated for this value.
#define PNTALL 0                    // if TRUE: Print all the calculated values.
#define ASKFOR 1                    // if TRUE: Ask for a x value then print the calculated ln value for it.

// Global vars
double results[MAXELM];             // Array to store computed values.

// Func: offset to var conversion
double getvar(int offset)
{
    double x = (double)MINVAL + ((double)MAXVAL - (double)MINVAL) * (double)offset / (double)MAXELM;

    if(x >= (double)ACRTPT)
        x = pow(x - (double)ACRTPT, (double)EXPVAR) + (double)ACRTPT;
    else
        x = -pow((double)ACRTPT - x, (double)EXPVAR) + (double)ACRTPT;
    // This ^ is the equation used when NONLIN = 1; to have a non linear repartition. Feel free to change it. The inverse equation is in `int getoffset(double)`.
    return x;
}

// Func: var to offset conversion
int getoffset(double var)
{
    double x = var;

    if(x >= (double)ACRTPT)
        x = pow(x - (double)ACRTPT, 1.0/(double)EXPVAR) + (double)ACRTPT;
    else
        x = -pow((double)ACRTPT - x, 1.0/(double)EXPVAR) + (double)ACRTPT;
    // This ^ is the equation used when NONLIN = 1; to calculate offset with a non linear repartition. Feel free to change it (but it must be the inverse of the one in
    // `double getvar(int)` for this to work.). These equations are tied, so you cannot modify one without modifying the other. They are here because
    // `pow(negative, non-integer)` always returns `-nan` instead of the correct value. This 'trick' uses the fact that (-x)^(1/3) == -(x^(1/3)) to cicumvent the
    // limitation.

    int offset = (x - (double)MINVAL) * (double)MAXELM / ((double)MAXVAL - (double)MINVAL);
#if STRICT
    if(getvar(offset) != var)
        return -1;
    return (offset < 0)?-1:(offset > (MAXELM - 1))?-1:offset;
#else
    return (offset < 0)?0:(offset > (MAXELM - 1))?MAXELM - 1:offset;
#endif
}

// Func: main.
int main(int argc, char* argv[])
{
    int offset;
    for(offset = 0; offset < MAXELM; offset++)
        results[offset] = log(getvar(offset));

#if PNTALL
    for(offset = 0; offset < MAXELM; offset++)
    {
        printf("[log(%lf) = %lf] ", getvar(offset), results[offset]);
        if(!((offset + 1) % 6))
            printf("\n");
    }
    printf("\n");
#endif

#if ASKFOR
    double x;
    printf("log(x) for x = ");
    scanf("%lf", &x);
    if((offset = getoffset(x)) < 0)
        printf("ERROR: Value for x = %lf hasn't been calculated\n", x);
    else
        printf("results[%d]: log(%lf) = %lf\n", offset, getvar(offset), results[offset]);
#endif

    return 0;
}

最新バージョンの特徴:

  • 固定サイズの配列を使用します。
  • 保存された値のみを計算します(1つの配列セルに対して複数の値を計算しません)。
  • log関数を使用して値からオフセットを取得し、オフセットから値を取得するため、が計算された値を格納する必要はありません。

前のバージョンに対する利点:

  • 使用せず、代わりcbrtに使用しpowます。
  • コンパイル時に微積分変数の増加を指定できます。(したがって、値は正確なポイント()を中心に多かれ少なかれグループ化されますACRTPT

3番目のバージョン

#include <stdio.h>                  // for input/output.
#include <math.h>                   // for mathmatic functions (log, pow, etc.)

// Values
#define MAXELM 1000                 // Array size
#define MINVAL 0.1                  // Minimum x value
#define MAXVAL 1.9                  // Maximum x value
#define ACRTPT (MINVAL + MAXVAL)/2  // Accurate point. This value is used to know where to compute with maximum accuracy. Can be set to a fixed value.
// Behavior
#define NONLIN 1                    // if TRUE: Calculate log values with a quadratic distribution instead of linear distribution.
#define STRICT 1                    // if TRUE: Return -1 instead of the floored (or closest if out of range) offset when (x) hasn't been calculated for this value.
#define PNTALL 0                    // if TRUE: Print all the calculated values.
#define ASKFOR 1                    // if TRUE: Ask for a x value then print the calculated ln value for it.

// Global vars
double results[MAXELM];             // Array to store computed values.

// Func: offset to var conversion
double getvar(int offset)
{
    double x = (double)MINVAL + ((double)MAXVAL - (double)MINVAL) * (double)offset / (double)MAXELM;
#if NONLIN
    x = pow((x - ACRTPT), 3) + ACRTPT;
    // This ^ is the equation used when NONLIN = 1; to have a non linear repartition. Feel free to change it. The inverse equation is in `int getoffset(double)`.
#endif
    return x;
}

// Func: var to offset conversion
int getoffset(double var)
{
#if NONLIN
    int offset = ((
        cbrt(var - ACRTPT) + ACRTPT
    // This ^ is the equation used when NONLIN = 1; to calculate offset with a non linear repartition. Feel free to change it (but it must be the inverse of the one in
    // `double getvar(int)` for this to work.)
                    ) - (double)MINVAL) * (double)MAXELM / ((double)MAXVAL - (double)MINVAL);
#else
    int offset = (var - (double)MINVAL) * (double)MAXELM / ((double)MAXVAL - (double)MINVAL);
#endif
#if STRICT
    if(getvar(offset) != var)
        return -1;
    return (offset < 0)?-1:(offset > (MAXELM - 1))?-1:offset;
#else
    return (offset < 0)?0:(offset > (MAXELM - 1))?MAXELM - 1:offset;
#endif
}

// Func: main.
int main(int argc, char* argv[])
{
    int offset;
    for(offset = 0; offset < MAXELM; offset++)
        results[offset] = log(getvar(offset));

#if PNTALL
    for(offset = 0; offset < MAXELM; offset++)
    {
        printf("[log(%lf) = %lf] ", getvar(offset), results[offset]);
        if(!((offset + 1) % 6))
            printf("\n");
    }
    printf("\n");
#endif

#if ASKFOR
    double x;
    printf("log(x) for x = ");
    scanf("%lf", &x);
    if((offset = getoffset(x)) < 0)
        printf("ERROR: Value for x = %lf hasn't been calculated\n", x);
    else
        printf("results[%d]: log(%lf) = %lf\n", offset, getvar(offset), results[offset]);
#endif

    return 0;
}

このバージョンは、以前のバージョンよりもクリーンで保守が簡単です。他に何か必要な場合は、コメントを残してください。

ファイルの先頭にあるマクロを使用して、その動作を構成できます。

特徴:

  • 固定サイズの配列を使用します。
  • 保存された値のみを計算します(1つの配列セルに対して複数の値を計算しません)。
  • log関数を使用して値からオフセットを取得し、オフセットから値を取得するため、が計算された値を格納する必要はありません。

2番目のバージョン

さて、これが私の2番目の解決策です。元のコメントについては、以下を参照してください。

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

#define MIN_INC 0.001    // This is the minimum increment. If its set to 0, when tmp will be equal to avg, it will never leave this state, since INC_MUL * (tmp - avg)^2 will be 0.
#define INC_MUL 0.2      // This is a number which influences the precision you will get. The smaller it is, the more precise you will be, and the greater will be your result array cardinality.

typedef struct {
    double offset;
    double value;    // value = log(offset). Since the results are not linarly widespread, this is pretty important.
} logCalc;

// Here, we need to use a pointer on a logCalc pointer, since we want to actually SET the address of the logCalc pointer, not the address of one of its copies.
int MyLogCreate(logCalc** arr, double min, double max)
{
    if((*arr) != NULL)
        return 0;
    unsigned int i = 0;
    double tmp, avg = (max + min) / 2.0;
    for( ; min < avg; min += (INC_MUL * ((avg - min) * (avg - min)) + MIN_INC))
    {
        (*arr) = (logCalc*)realloc((*arr), sizeof(logCalc) * (i + 1));
        (*arr)[i].offset  = min;
        (*arr)[i++].value = log(min);
    }
    for(tmp = avg ; tmp < max; tmp += (INC_MUL * ((tmp - avg) * (tmp - avg)) + MIN_INC))
    {
        (*arr) = (logCalc*)realloc((*arr), sizeof(logCalc) * (i + 1));
        (*arr)[i].offset  = tmp;
        (*arr)[i++].value = log(tmp);
    }
    return i;
}

int main(int argc, char** argv)
{
    logCalc *myloglut = NULL;
    unsigned int i,
        t = MyLogCreate(&myloglut, .1, 1.9);
    for(i = 0; i < (t-1); i++)
    {
        printf("[log(%lf) = %lf], ", myloglut[i].offset, myloglut[i].value);
        if(!((i+1)%6))         // Change 6 to what's best for your terminal $COLUMNS
            printf("\n");
    }
    printf("\n");
    free(myloglut);
    return 0;
}

元のコメント

計算の線形性は、線形増分を使用しているという事実に由来します。forループの反復ごとに、をインクリメントexp(2.0 - 0.1) / MAXLOGます。

0付近のより正確な値を取得するには、次のものが必要です。

  1. より大きな範囲(より大きな配列)を定義するには(0付近により多くの値を格納できるようにするため)
  2. 非線形増分を使用します。この増分はおそらく依存しますi(またはexp、実行方法によっては依存します)。したがって、計算しようとしている数値の「オフセット」(および増分する必要のある量)を正確に把握できますexp。もちろん、0付近でより多くの結果を計算します。

これが私の現在の実装です:

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

#define CALCULATE_UNTIL 2.0
#define PRECISE_UNTIL   1.0

typedef struct {
    double offset;
    double value;
} logCalc;

logCalc *myloglut = NULL;

int MyLogCreate()
{
    double exp = 0.1;
    int i;
    for (i = 0; exp <= CALCULATE_UNTIL; exp += (exp < PRECISE_UNTIL)?0.0001898:0.001898)
    {
        myloglut = realloc(myloglut, sizeof(logCalc) * (i + 1));
        myloglut[i].offset = exp;
        myloglut[i++].value = (i == 4780)?0:log(exp);
    }
    return i; // So you know how big the array is. Don't forget to free(myloglut); at the end of your code.
}

int main(int argc, char** argv)
{
    int i,
    t = MyLogCreate();
    for(i = 0; i < t; i++)
    {
        printf("[log(%lf) = %lf], ", myloglut[i].offset, myloglut[i].value);
        if(!(i%6))    // For formatting purposes.
            printf("\n");
    }
    printf("\n");
    free(myloglut);
    return 0;
}

expの値も格納するために新しいタイプを作成しました。これは、結果がログの値であるかどうかを知るのに役立ちます。

更新:あなたが何をしたいのかわかりません。log(x)=0付近またはx= 0付近を正確にしますか?最初のケースでは、コードを希望どおりに機能させるために、コードを書き直さなければならない場合があります。また、0に近づいたときに結果をより正確にしたいですか、それとも(現在のように)特定の範囲で結果をより正確にしたいですか?

于 2012-12-06T09:27:44.007 に答える
0

関数の「原点」をゼロまたは 0.1 から 1.0 にシフトします。

 for (i=-478;i<523;i++) {
     double j = 1.0 + (double)i / 523.0;
     myloglut[i+478] = log(j);
 }

この関数は、1.0 + (523.0 / 523.0) == 2.0 として 1.0 と 2.0 の 2 つの点を正確に選択します。

最初の値は次のとおりです。

myloglut[0] = log(0.0860420650095602);

より「自然な」サイズは 973 で、除数は (正確に) 512 になり、最初のエントリは 51/512 = ~0.099609375 になります。

于 2012-12-06T09:43:02.903 に答える
0

これはどのくらい正確で、どれくらい速くする必要がありますか? 区分的チェビシェフ近似を使用すると、かなり良いことができます。チェビシェフ近似の次数を使用して精度を制御します (次数が高いほど遅くなりますが、より正確になります)。double を仮数 (1 と 2 の間) と指数 (2 の累乗で、その対数は単純に指数回log(2)であり、事前に計算することができます)に分解して、引数の削減を行うこともお勧めします。

ログが必要なときはいつでもより多くの計算を行うか、巨大なテーブルを使用し、それに伴うすべてのキャッシュと予測不可能なメモリアクセスの問題を発生させることなく、[0.1, 2] で非常に正確なものを思い付くことはできないと思います。しかし、十分な時間があれば、区分的なチェビシェフ近似を行うことを検討してください。(チェビシェフ近似を使用するコードを見せてほしい場合は、コメントでお知らせください。この投稿を更新します。)

EDIT : チェビシェフ近似を使用した対数のコード。1e-5 以内の精度。

double mylog(double x) {
  static double logtwo = log(2);
  static double tbls[4][5] = {{0,0,0,0,0},
    {-.9525934480e-2,-.87402539075,-1.135790603,1.1519051721,-1.7063112037},
    {.53892330786e-3,-1.0117355213,-.4085197450,-.6242237228,0},
    {.60393290013e-6,-1.0001523639,-.4940510719,-.4058961978,0}};
  if (x>1) return -mylog(1/x);
  int expo,e2;
  x = 1-frexp(x, &expo);
  double y = frexp(x, &e2);
  if (e2 < -3) e2 = -3;
  double *tbl = tbls[-e2];
  return expo*logtwo + tbl[0]+x*(tbl[1]+x*(tbl[2]+x*(tbl[3]+x*tbl[4])));
}

Maple を使用してチェビシェフ近似を計算し、それらを速度のために従来の多項式として拡張しました。

1に非常に近い精度が必要な場合は、テイラーの近似に対応する行を変更して最後にif (e2 < -3) e2 = -3追加できます。より速くしたい場合は、1/2 と 3/4 の間で log(x) のより適切な 3 次近似を計算し、それを の最初の行に格納します。{0,-1,-.5,-1/3.,-.25}tblstbls

于 2012-12-06T20:02:53.340 に答える