4

他の2つの値v1v2がゼロからどれだけ離れているかに基づいて、 2つの値w1w2のある種の加重平均を実行したい状況があります...例:

  • v1がゼロの場合、重み付けはまったく行われないため、w2を返します。
  • v2がゼロの場合、重み付けはまったく行われないため、w1を返します。
  • 両方の値がゼロから等しく遠い場合、平均を実行して(w1 + w2)/2を返します。

私は次のようなコードを継承しました:

float calcWeightedAverage(v1,v2,w1,w2)
{
  v1=fabs(v1);
  v2=fabs(v2);
  return (v1/(v1+v2))*w1 + (v2/(v1+v2)*w2);
}

少し背景として、v1とv2は、2つの異なるノブをどれだけ回したかを表します。個々の結果のエフェクトの重みは、どちらの方向ではなく、どれだけ回したかにのみ依存します。

明らかに、これには問題があります。これは、v1==v2==0最終的にはreturn (0/0)*w1 + (0/0)*w2実行できなくなるためです 0/0v1==v2==0浮動小数点数を使用するのは悪い習慣ではなかったとしても、数学的に恐ろしい音に対して特別なテストを行うこと。

だから私は疑問に思いました

  • これを処理するための標準ライブラリ関数がありました
  • きちんとした数学的表現があります
4

10 に答える 10

14

この数学関数を実装しようとしています:

F(x, y) = (W1 * |x| + W2 * |y|) / (|x| + |y|)

この関数は点 で不連続x = 0, y = 0です。残念ながら、R. がコメントで述べたように、不連続性は削除できません。この時点で使用する賢明な値はありません

これは、 にたどり着くまでの道のりによって「感覚的な価値」が変わるからx = 0, y = 0です。たとえば、パスF(0, r)を からr = R1までたどるとしますr = 0(これは、X ノブを 0 にして、Y ノブを R1 から 0 にスムーズに調整することと同じです)。の値は、不連続点に到達するまでF(x, y)一定です。W2

次に、次のことを検討してくださいF(r, 0)(Y ノブをゼロに保ち、X ノブをスムーズにゼロまで調整します)。出力はW1、不連続点に到達するまで一定になります。

次に、次のことを検討してくださいF(r, r)(両方のノブを同じ値に保ち、同時にゼロに調整します)。ここでの出力はW1 + W2 / 2、不連続点に到達するまで一定です。

これは、 との間の任意の値がでの出力として等しく有効であることを意味します。それらの中から選択する賢明な方法はありません。(さらに、出力として常に 0 を選択することは完全に間違っています。それ以外の場合、出力は間隔内に制限されます(つまり、不連続点に近づく経路では、限界は常にその間隔内にあります)。0 はそうではない可能性があります。この間隔で横になっても!)W1W2x = 0, y = 0W1..W2F()


関数を少し調整することで問題を「修正1.0v1できv2ますfabs()。これにより、各ノブの最小寄与がゼロになることはなく、「ゼロに近い」だけになります (定数はどれだけ近いかを定義します)。

この定数を「非常に小さい数値」として定義したくなるかもしれませんが、ノブをゼロ点近くで操作すると出力が大きく変化するだけであり、これはおそらく望ましくありません。

于 2010-09-18T01:50:54.863 に答える
4

これを行うだけで何が悪いのかわかりません:

float calcWeightedAverage( float v1, float v2, float w1, float w2 ) {
    static const float eps = FLT_MIN; //Or some other suitably small value.
    v1 = fabs( v1 );
    v2 = fabs( v2 );

    if( v1 + v2 < eps )
        return (w1+w2)/2.0f;
    else
        return (v1/(v1+v2))*w1 + (v2/(v1+v2)*w2);
}

確かに、あなたの部門を理解するための「派手な」ものはありませんが、なぜそれを必要以上に難しくするのですか?

于 2010-09-17T22:36:33.490 に答える
4

これは私がすぐに思いつくことができる最高のものです

float calcWeightedAverage(float v1,float v2,float w1,float w2)
{
    float a1 = 0.0;
    float a2 = 0.0;

    if (v1 != 0)
    { 
        a1 = v1/(v1+v2) * w1;
    }

    if (v2 != 0)
    { 
        a2 = v2/(v1+v2) * w2;
    }

    return a1 + a2;
}
于 2010-09-17T22:29:01.067 に答える
3

個人的には、ゼロ除算の明示的なチェックに問題はないと思います。私たちは皆それらをしているので、それを持っていない方が醜いと主張することができます.

ただし、ゼロ例外による IEEE 除算をオフにすることは可能です。これを行う方法は、プラットフォームによって異なります。Windowsではプロセス全体で行う必要があることを知っているので、注意しないと、他のスレッド(およびそれらがあなたと一緒に)をうっかり混乱させる可能性があります。

ただし、そうすると、結果の値はNaN0 ではなくになります。私はそれがあなたが望むものであることを非常に疑っています. NaN を取得したときに別のロジックで特別なチェックを入れる必要がある場合は、前もって分母の 0 をチェックすることもできます。

于 2010-09-17T22:44:47.823 に答える
1

テストしてfabs(v1)+fabs(v2)==0(すでに計算していることを考えると、これが最速のようです)、この場合は意味のある値を返します(w1+w2/2?)。それ以外の場合は、コードをそのままにします。

ただし、可能であればアルゴリズム自体が壊れているのではないかと思いv1==v2==0ます。ノブが「0に近い」ときのこの種の数値的不安定性は、ほとんど望ましくないようです。

動作が実際に正しく、特殊なケースを避けたい場合は、指定された型の最小の正の浮動小数点値を絶対値に追加したり、絶対値を取得した後に追加したりできv1ますv2。(DBL_MINおよびfriendsは正規化された最小値であるため、正しい値ではないことに注意してください。非正規化数を含むすべての正の値の最小値が必要です。)これは、すでに極端に小さい場合を除いて効果がありません。通常の場合、追加はただ降伏v1します。v2

于 2010-09-18T00:05:49.507 に答える
1

したがって、加重平均では、両方がゼロである特殊なケースを調べる必要があります。その場合は 0.5 * w1 + 0.5 * w2 として扱いますよね?これはどう?

float calcWeightedAverage(float v1,float v2,float w1,float w2)
{
  v1=fabs(v1);
  v2=fabs(v2);
  if (v1 == v2) {
    v1 = 0.5;
  } else {
    v1 = v1 / (v1 + v2); // v1 is between 0 and 1
  }
  v2 = 1 - v1; // avoid addition and division because they should add to 1      

  return v1 * w1 + v2 * w2;
}
于 2010-09-17T23:13:44.527 に答える
1

ゼロの明示的なチェックを使用する際の問題は、cafs 応答で概説されているように注意しない限り、動作が不連続になる可能性があることです (アルゴリズムのコアにある場合、if は高価になる可能性がありますが、それまでは気にしないでください)。あなたが測定します...)

代わりに、ウェイトをゼロに近づけるだけのものを使用する傾向があります。

float calcWeightedAverage(v1,v2,w1,w2)
{
  eps = 1e-7; // Or whatever you like...
  v1=fabs(v1)+eps;
  v2=fabs(v2)+eps;
  return (v1/(v1+v2))*w1 + (v2/(v1+v2)*w2);
}

関数は滑らかになり、漸近線やゼロ除算がなくなり、v1 または v2 のいずれかが 1e-7 を大幅に上回っている限り、「実際の」加重平均と区別できなくなります。

于 2010-09-18T01:59:59.553 に答える
0

これはうまくいくはずです

#include <float.h>

float calcWeightedAverage(v1,v2,w1,w2)
{
  v1=fabs(v1);
  v2=fabs(v2);
  return (v1/(v1+v2+FLT_EPSILON))*w1 + (v2/(v1+v2+FLT_EPSILON)*w2);
}

編集: 正確な結果を得るには、FLT_EPSILON を使用する代わりに DBL_EPSILON を使用します (float 値を返すと思います)。

于 2010-09-17T23:06:05.387 に答える
0

分母がゼロの場合、どのようにデフォルトにしますか? 次のようなことができます。

static inline float divide_default(float numerator, float denominator, float default) {
    return (denominator == 0) ? default : (numerator / denominator);
}

float calcWeightedAverage(v1, v2, w1, w2)
{
  v1 = fabs(v1);
  v2 = fabs(v2);
  return w1 * divide_default(v1, v1 + v2, 0.0) + w2 * divide_default(v2, v1 + v2, 0.0);
}

関数の定義と static inline の使用により、インライン化できることをコンパイラに実際に知らせる必要があることに注意してください。

于 2010-09-17T22:27:29.830 に答える
-2

私はこのようにします:

float calcWeightedAverage(double v1, double v2, double w1, double w2)
{
  v1 = fabs(v1);
  v2 = fabs(v2);
  /* if both values are equally far from 0 */
  if (fabs(v1 - v2) < 0.000000001) return (w1 + w2) / 2;
  return (v1*w1 + v2*w2) / (v1 + v2);
}
于 2010-09-17T22:36:50.730 に答える