我一直在思考如何编写一个计算幂次方的函数(例如 23)。大多数语言都包含在标准库中,通常为 pow(double x, double y)
,但我该如何自己编写它呢?
我想到了使用 for 循环
,但是当我尝试进行非整数指数的幂运算时(例如 54.5 或 负数 2-21),我的大脑似乎进入了死循环,感觉有些疯狂 ;)
那么,我该如何编写一个计算实数幂次方的函数呢?谢谢!
哦,也许需要注意的是:我不能使用使用幂的函数(例如 exp
),这让这个问题变得无用。
我一直在思考如何编写一个计算幂次方的函数(例如 23)。大多数语言都包含在标准库中,通常为 pow(double x, double y)
,但我该如何自己编写它呢?
我想到了使用 for 循环
,但是当我尝试进行非整数指数的幂运算时(例如 54.5 或 负数 2-21),我的大脑似乎进入了死循环,感觉有些疯狂 ;)
那么,我该如何编写一个计算实数幂次方的函数呢?谢谢!
哦,也许需要注意的是:我不能使用使用幂的函数(例如 exp
),这让这个问题变得无用。
负数幂不是问题,它们只是正数幂的倒数 (1/x
)。
浮点数幂稍微有些复杂;如你所知,分数幂等价于根式 (例如,x^(1/2) == sqrt(x)
),而且你也知道,将具有相同底数的幂乘起来等价于将它们的指数相加。
有了以上所有这些,你可以:
示例:
2^(-3.5) = (2^3 * 2^(1/2)))^-1 = 1 / (2*2*2 * sqrt(2))
AB = Log-1(Log(A)*B)
编辑:是的,这个定义确实有用。例如,在 x86 上,它几乎直接转换为 FYL2X
(Y * Log2(X))和 F2XM1
(2x-1):
fyl2x
fld st(0)
frndint
fsubr st(1),st
fxch st(1)
fchs
f2xmi
fld1
faddp st(1),st
fscale
fstp st(1)
代码看起来比你想象的略长,主要是因为 F2XM1
只能处理范围在 -1.0 到 1.0 之间的数字。 fld st(0)/frndint/fsubr st(1),st
这一步骤减去整数部分,只剩下小数部分。我们对其应用 F2XM1
,加回 1,然后使用 FSCALE
来处理指数的整数部分。
log^-1(x) = exp(x) = e^x
。解释:这个等式的意思是“对数的反函数等于自然指数的指数等于e的指数”。 - user142019通常数学库中pow(double, double)
函数的实现是基于以下公式:
pow(x,y) = pow(a, y * log_a(x))
使用这个身份,你只需要知道如何将一个数a提高到任意指数,以及如何取对数基于a。你已经将一个复杂的多变量函数转化为了两个单变量函数和一个乘法,这非常容易实现。最常选择的值是e或2——e因为e^x和log_e(1+x)具有一些非常好的数学性质,2则因为它在浮点运算中具有一些好的性质。有两种不同的情况需要处理:整数指数和分数指数。
对于整数指数,可以使用平方求幂算法。
def pow(base, exponent):
if exponent == 0:
return 1
elif exponent < 0:
return 1 / pow(base, -exponent)
elif exponent % 2 == 0:
half_pow = pow(base, exponent // 2)
return half_pow * half_pow
else:
return base * pow(base, exponent - 1)
难点在于找到2^x和log2(x)的良好近似值。一个简单的方法是使用泰勒级数。
Σ(从i = 0到n){(1 / (2 * i + 1)) * ((x - 1) / (x + 1)) ^ (2 * n + 1)}
。符号“^”表示第一个函数中实现的幂函数Pow(x, n),它使用简单的迭代。Σ(从i = 0到n){x^i / i!}
。在这里,“^”表示幂函数,但它不是通过调用第一个Pow(x, n)函数来计算的;相反,在第三个函数中,它与阶乘同时实现,使用d *= x / i
。我觉得我必须使用这个技巧,因为在这个函数中,相对于其他函数,迭代需要更多的步骤,而阶乘(i!)大多数情况下会溢出。为了确保迭代不会溢出,在此部分中,幂函数与阶乘并行迭代。通过这种方式,我克服了溢出问题。fPow(x, a) = Exp(a * Ln(x))
。现在,我已经拥有所有带有迭代的函数iPow、Ln和Exp。1.0E-15
,这对于双精度浮点数似乎是合理的。因此,如果(delta < MAX_DELTA_DOUBLE)
,则迭代停止。如果需要更高的精度,可以使用并减小MAX_DELTA_DOUBLE
的常量值,例如减小到1.0E-18
(1.0E-18
将是最小值)。
这是我用过的代码,可以正常运行。
#define MAX_DELTA_DOUBLE 1.0E-15
#define EULERS_NUMBER 2.718281828459045
double MathAbs_Double (double x) {
return ((x >= 0) ? x : -x);
}
int MathAbs_Int (int x) {
return ((x >= 0) ? x : -x);
}
double MathPow_Double_Int(double x, int n) {
double ret;
if ((x == 1.0) || (n == 1)) {
ret = x;
} else if (n < 0) {
ret = 1.0 / MathPow_Double_Int(x, -n);
} else {
ret = 1.0;
while (n--) {
ret *= x;
}
}
return (ret);
}
double MathLn_Double(double x) {
double ret = 0.0, d;
if (x > 0) {
int n = 0;
do {
int a = 2 * n + 1;
d = (1.0 / a) * MathPow_Double_Int((x - 1) / (x + 1), a);
ret += d;
n++;
} while (MathAbs_Double(d) > MAX_DELTA_DOUBLE);
} else {
printf("\nerror: x < 0 in ln(x)\n");
exit(-1);
}
return (ret * 2);
}
double MathExp_Double(double x) {
double ret;
if (x == 1.0) {
ret = EULERS_NUMBER;
} else if (x < 0) {
ret = 1.0 / MathExp_Double(-x);
} else {
int n = 2;
double d;
ret = 1.0 + x;
do {
d = x;
for (int i = 2; i <= n; i++) {
d *= x / i;
}
ret += d;
n++;
} while (d > MAX_DELTA_DOUBLE);
}
return (ret);
}
double MathPow_Double_Double(double x, double a) {
double ret;
if ((x == 1.0) || (a == 1.0)) {
ret = x;
} else if (a < 0) {
ret = 1.0 / MathPow_Double_Double(x, -a);
} else {
ret = MathExp_Double(a * MathLn_Double(x));
}
return (ret);
}
delta < 1.0E-15
)。我没有进行彻底测试,但由于在C语言中double
类型可以处理的有效数字(小数点后)最多为15
,我有点相信上面的代码会按照预期完成工作。 - ssd
static double pows (double p_nombre, double p_puissance)
{
double nombre = p_nombre;
double i=0;
for(i=0; i < (p_puissance-1);i++){
nombre = nombre * p_nombre;
}
return (nombre);
}
static double floors(double p_nomber)
{
double x = p_nomber;
long partent = (long) x;
if (x<0)
{
return (partent-1);
}
else
{
return (partent);
}
}
最好的祝福
这是一个有趣的练习。以下是一些建议,你可以按照以下顺序尝试: