【问题标题】:How to avoid -nan(ind) error when using the pow function. Is it possible to use pow with negative bases with non-integral exponents使用 pow 函数时如何避免 -nan(ind) 错误。是否可以使用具有非整数指数的负基数的 pow
【发布时间】:2021-07-08 19:28:04
【问题描述】:

使用 pow 函数 -nan(ind) 打印到屏幕时出现错误。想知道是否有一种方法可以将 pow 与具有负基数和非整数指数的数字一起使用。

目前 pow 函数是 pow(-12.4112021858, 0.2)。并给我 -nan(ind) 错误。 如果我将基数更改为 12.41,它似乎计算得非常好。

编辑 - 我将指数设置为与 0.2 不同的数字

int main() {



    double a = 1.83;
    double v = 1.25;
    double r = 0;

    double sum = 0;
 
    double exponent = 0.2;

    double result = 1;
    while (true)
    {
    printf("Enter Radius \n");
    scanf_s("%lf", &r);
    sum = 1 - r/ a;
    printf("%lf\n", sum);
    sum = sum * v;
    printf("%lf\n", sum);
    sum=  pow(sum, exponent);
    printf("%lf\n", sum);

}
}

【问题讨论】:

  • 可能是因为0.2没有准确表示,所以pow的结果比较复杂。
  • 你不能。这是一个数学问题,而不是编程问题。请参阅en.wikipedia.org/wiki/Exponentiation#Real_exponents,特别是en.wikipedia.org/wiki/…
  • 您可以在复数上使用 pow ...就像在数学中一样...参见简单的cpow implementation using log,exp
  • @Spektre:cpow(-12.4112021858, .2) 返回大约 1.3388 + .9727 i,我认为这不是 OP 想要的。他们更可能想要大约 -1.65486576。
  • @EugeneSh.: pow(-12.4112021858, .2) 会产生域错误,即使 .2 被精确表示。

标签: c debugging math pow


【解决方案1】:

通过使用复杂的数学是可能的......问题是pow 正在使用ln 操作,它是在复域多值上,这意味着它有无限数量的有效结果(作为结果的虚部与k*2*PI 相加,其中k 是任意整数)。因此,当使用的结果未添加正确的k 时,结果将是复杂的,而不是您想要的。

但是对于求根(1.0/exponent -> integer),k 可以这样直接获得:

int k=int(floor((1.0/exponent)+0.5))/2;

我刚才凭经验发现的。当我重写我的 complex math cpow 来使用它时,我得到了这个 C++ 代码:

//---------------------------------------------------------------------------
vec2 cadd(vec2 a,vec2 b)    // a+b
    {
    return a+b;
    }
vec2 csub(vec2 a,vec2 b)    // a-b
    {
    return a-b;
    }
vec2 cmul(vec2 a,vec2 b)    // a*b
    {
    return vec2((a.x*b.x)-(a.y*b.y),(a.x*b.y)+(a.y*b.x));
    }
vec2 cdiv(vec2 a,vec2 b)    // a/b
    {
    float an=atan2(-a.y,-a.x)-atan2(-b.y,-b.x);
    float  r=length(a)/length(b);
    return r*vec2(cos(an),sin(an));
    }
vec2 csqr(vec2 a)           // a^2
    {
    return cmul(a,a);
    }
vec2 cexp(vec2 a)           // e^a
    {
    //  e^(x+y*i)= e^x * e^(y*i) = e^x * ( cos(y) + i*sin(y) )
    return exp(a.x)*vec2(cos(a.y),sin(a.y));
    }
vec2 cln(vec2 a)            // ln(a) + i*pi2*k where k={ ...-1,0,+1,... }
    {
    return vec2(log(length(a)),atan2(a.y,a.x));
    }
vec2 cpow(vec2 a,vec2 b)    // a^b
    {
    return cexp(cmul(cln(a),b));
    }
vec2 ctet(vec2 a,int b)     // a^^b
    {
    vec2 c=vec2(1.0,0.0);
    for (;b>0;b--) c=cpow(a,c);
    return c;
    }
//-------------------------------------------------------------------------
vec2 cln(vec2 a,int k)          // ln(a) + i*pi2*k where k={ ...-1,0,+1,... }
    {
    return vec2(log(length(a)),atan2(a.y,a.x)+float(k+k)*M_PI);
    }
float mypow(float a,float b)        // a^b
    {
    if (b<0.0) return 1.0/mypow(a,-b);
    int k=0;
    if ((a<0.0)&&(b<1.0))       // rooting with negative base
        {
        k=floor((1.0/b)+0.5);
        k/=2;
        }
    return cexp(cmul(cln(vec2(a,0.0),k),vec2(b,0.0))).x;
    }
//-------------------------------------------------------------------------

我正在使用像数学vec2 这样的 GLSL,您可以轻松地重写为 x,y 组件,例如:

//-------------------------------------------------------------------------
float mypow(float a,float b)        // a^b
    {
    if (b<0.0) return 1.0/mypow(a,-b);
    int k=0;
    if ((a<0.0)&&(b<1.0))           // rooting with negative base
        {
        k=floor((1.0/b)+0.5);
        k/=2;
        }
    float x,y;
    x=log(fabs(a));
    y=atan2(0.0,a)+(float(k+k)*M_PI);
    x*=b; y*=b;
//  if (fabs(exp(x)*sin(y))>1e-6) throw domain error;  // abs imaginary part is not zero
    return exp(x)*cos(y);                              // real part of result
    }
//-------------------------------------------------------------------------

返回复域pow 的实部,并选择正确的多值cln 子结果。我还在代码中添加了域测试作为注释,以防您需要/想要实现它。

您的案例的结果如下:

mypow(-12.411202,0.200000) = -1.654866

已经测试了更多的数字和1/odd number 指数,看起来它正在工作。

[Edit1] 处理混合指数

现在,如果我们有a^(b0+1/b1) 形式的指数,其中b0,b1 是整数,a&lt;0 我们需要将战俘分解为两部分:

a^(b0+1/b1) = a^b0 * a^(1/b1)

所以当添加到上述函数时:

//-------------------------------------------------------------------------
float mypow(float a,float b)            // a^b
    {
    if (b<0.0) return 1.0/mypow(a,-b);  // handle negative exponents
    int k;
    float x0,x,y,e,b0;
    k=0;                                // for normal cases k=0
    b0=floor(b);                        // integer part of exponent
    b-=b0;                              // decimal part of exponent
    // integer exponent (real domain power |a|^b0 )
    x0=pow(fabs(a),b0);
    if ((a<0.0)&&((int(b0)&1))==1) x0=-x0; // just add sign if odd exponent and negative base
    // decimal exponent (complex domain rooting a^b )
    if ((a<0.0)&&(b>0.0))               // rooting with negative base
        {
        k=floor((1.0/b)+0.5);
        k&=0xFFFFFFFE;
        }
    x=b*(log(fabs(a))); e=exp(x);
    y=b*(atan2(0.0,a)+(float(k)*M_PI));
    x=e*cos(y);
//  y=e*sin(y); if (fabs(y)>1e-6) throw domain error;
    return x*x0; // full complex result is x0*(x+i*y)
    }
//-------------------------------------------------------------------------

和样本输出:

mypow(-12.41120243,3.200000) = 3163.76635742
mypow(3163.76635742,0.312500) = 12.41120243

【讨论】:

  • floor((1.0/exponent)+0.5) 在极端情况下有问题。考虑lround(1.0/exponent)
  • 什么是length()?看起来不标准。
  • @chux-ReinstateMonica lround 在 math.h / C++ 中未定义 ...length(vec2 a) = sqrt(a.x*a.x + a.y*a.y) ...如果您查看独立函数,它们不会在 vec2 上中继,也不会在其他任何东西上数学.h
  • lround() 自 C99 开始使用 C 语言,20 多年。而不是sqrt(a.x*a.x + a.y*a.y) 考虑标准hypot()
  • @chux-ReinstateMonica 长度是 GLSL 内部函数...不需要hypot
【解决方案2】:

想知道是否有办法将 pow 与具有负基数和非整数指数的数字一起使用。

首先,检查文档。 C 2018 7.12.7.4 指定powfpowpowl

pow 函数计算 x 的幂 y。如果x 是有限且负的,并且y 是有限且不是整数值,则会发生域错误...

您的x,大约为 -12.4112021858,是有限的和负的,而您的 y,大约是 0.2,是有限的,而不是整数值。所以出现域错误。

这意味着除非您使用的 pow 专门为超出 C 标准要求的其他情况提供支持,否则您不能期望得到结果,即使 .2 在 double 中精确表示也是如此。

(当存在域错误时,返回实现定义的结果。这可能是 NaN,一个有效的数学结果,例如 -2 表示 pow(-32, .2),如果使用基于十进制的浮点,或者别的东西。实现也可能通过errno 或浮点异常报告错误。有关更多信息,请参阅C 2018 7.12.1。)

其次,.2 在大多数 C 实现的double 格式中是不可表示的。 C 实现通常使用 IEEE-754 binary64 格式。在这种格式中,最接近 0.2 的可表示值是 0.200000000000000011102230246251565404236316680908203125。对于源代码pow(-12.4112021858, .2),首先将数字转换为double,然后使用参数-12.41120218580000056363132898695766925811767578125调用pow和 0.2000000000000000011102230246251565404236316680908203125。因此,您请求的不是具有实数结果的操作。

如果您的 C 实现使用基于十进制的浮点,则 .2 可以表示,并且 pow(-12.4112021858, .2) 返回 x 的第五个根是合理的,因为第五个根是负实数. (这将是对pow 标准规范的扩展,如上所述。)

如果你知道y 应该是五分之一或有理数p/q,其中q 是奇数,你如果 p 是偶数,则可以将期望的结果计算为 pow(fabs(x), y),如果 p 是奇数,则可以计算为 copysign(pow(fabs(x), y), x)

其中一个 cmets 建议使用 cpow,但这不会产生您想要的结果。 cpow(-12.4112021858, .2) 将返回大约 1.3388 + .9727 i。 (复幂“函数”是多值的,但定义 cpow 以产生该结果。)

【讨论】:

  • @EricPostpischil 看起来我解决了由多值cln 引起的cpow 问题,如果我没有遗漏/弄乱某些东西,请检查我的答案......那么你在这方面的技能要高得多我。
  • @EricPostpischil 嗯,我认为更安全的方法是将指数分解为整数和小数部分,并仅针对小数部分执行此操作……您知道x^(2+1/3) 之类的情况……将测试如果我发现了一些新的东西,再更新我的答案
猜你喜欢
  • 1970-01-01
  • 2018-09-08
  • 1970-01-01
  • 1970-01-01
  • 2015-01-18
  • 1970-01-01
  • 1970-01-01
  • 2018-02-05
  • 2016-02-14
相关资源
最近更新 更多