4

我们有一种情况,我们想要对两个值w1w2进行加权平均,基于其他两个值v1v2离零的距离......例如:

  • 如果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 表示两个不同的旋钮转动了多远,它们各自合成效果的权重仅取决于它们转动了多少,而不是朝哪个方向转动。

显然,这在 时会出现问题v1==v2==0,因为我们最终return (0/0)*w1 + (0/0)*w2会遇到而您不能这样做 0/0。进行特殊测试在v1==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 旋钮置于零,并将 Y 旋钮从 R1 平滑地向下调整到 0)。的值F(x, y)将保持不变,W2直到您达到不连续性为止。

现在考虑跟随F(r, 0)(将 Y 旋钮保持为零并将 X 旋钮平滑地调整到零) - 输出将保持不变,W1直到您到达不连续点。

现在考虑跟随F(r, r)(将两个旋钮保持在相同的值,并将它们同时调整为零)。此处的输出将保持不变,W1 + W2 / 2直到您进入不连续点。

这意味着和 之间的任何W1W2处的输出同样有效x = 0, y = 0。在它们之间没有明智的选择。(此外,总是选择 0 作为输出是完全错误的 - 否则输出必然在区间上W1..W2(即,对于您接近不连续性的任何路径,极限F()总是在该区间内),而 0 可能不会甚至在这个区间!)


您可以通过稍微调整函数来“解决”1.0问题v1-v2fabs(). 这将使每个旋钮的最小贡献不能为零 - 只是“接近零”(常数定义了接近程度)。

将这个常数定义为“一个非常小的数字”可能很诱人,但这只会导致输出发生剧烈变化,因为旋钮被操纵接近其零点,这可能是不可取的。

于 2010-09-18T01:50:54.863 回答
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 回答
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 回答
3

就我个人而言,我认为明确检查除以零没有任何问题。我们都这样做,所以可以说没有它更丑陋。

但是,可以关闭 IEEE 除零异常。你如何做到这一点取决于你的平台。我知道在 Windows 上它必须在整个进程范围内完成,所以如果你不小心的话,你可能会无意中弄乱其他线程(以及它们与你)。

但是,如果您这样做,您的结果值将是NaN,而不是 0。我非常怀疑这就是你想要的。如果您在获得 NaN 时无论如何都必须使用不同的逻辑在其中进行特殊检查,那么您最好只在前面检查分母中的 0。

于 2010-09-17T22:44:47.823 回答
1

你应该测试fabs(v1)+fabs(v2)==0(这似乎是最快的,因为你已经计算了它们),并返回在这种情况下有意义的任何值(w1+w2/2?)。否则,保持代码不变。

但是,如果可能的话,我怀疑算法本身被破坏了v1==v2==0。当旋钮“接近 0”​​时,这种数值不稳定性似乎是不可取的。

如果行为实际上是正确的并且您想避免特殊情况,您可以将给定类型的最小正浮点值添加到它们v1v2绝对值之后。(请注意,DBL_MIN和朋友不是正确的值,因为它们是最小归一化值;您需要所有正值中的最小值,包括次正规值。)除非它们已经非常小,否则这将无效;在通常情况下,添加只会产生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 上显着高于 1e-7,它将与“真实”加权平均值无法区分。

于 2010-09-18T01:59:59.553 回答
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);
}

注意静态内联的函数定义和使用应该真正让编译器知道它可以内联。

于 2010-09-17T22:27:29.830 回答
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 来获得准确的结果(我猜你会返回一个浮点值)。

于 2010-09-17T23:06:05.387 回答
-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 回答