无递归的浮点幂函数
Floating point power function without recursion
GLSL 没有针对双精度类型的 pow
函数。此函数的经典实现使用递归。由于 GLSL 中不允许递归,因此我需要一个无递归的实现。
我还需要exp
功能。
这个问题可以简单地把它分成两个子问题来解决。
- 将指数的整数部分和小数部分分开
- 计算整个部分的功率(这很简单)
- 用小数部分计算幂(使用近似值)
- 将结果相乘
- 如果初始指数为负,则反转结果
/// This is a simple non recursive implementation for pow, where the exponent is an integer.
double pow(double base, int power) {
double res = 1.0;// Initialize result
while (power > 0.0) {
// If power is odd, multiply x with result
if (power % 2 == 1) {
res *= base;
}
// n must be even now
power /= 2;
base *= base;// Change x to x^2
}
return res;
}
// The desired precision. More precision results in slower execution.
const double EPSILON = 0.00000001;
double pow(double base, double power) {
// We want to ignore negative exponents for now. We invert our result at the if necessary.
bool negative = power < 0.0LF;
if (negative) {
power *= -1.0LF;
}
// Seperate the whole and fractional parts.
double fraction = power - int(power);
int integer = int(power - fraction);
// The whole part is easily calculated.
double intPow = pow(base, integer);
// The fractional part uses an approximation method.
double low = 0.0LF;
double high = 1.0LF;
double sqr = sqrt(base);
double acc = sqr;
double mid = high / 2.0LF;
while (abs(mid - fraction) > EPSILON) {
sqr = sqrt(sqr);
if (mid <= fraction) {
low = mid;
acc *= sqr;
} else {
high = mid;
acc *= (1.0LF / sqr);
}
mid = (low + high) / 2.0LF;
}
// Exponential rules allow us to simply multiply our results.
double result = intPow * acc;
// If we started with a negative exponent we invert the result.
if (negative) {
return 1.0LF / result;
}
return result;
}
const double E = 2.7182818284590452353602874LF;
/// e^x is as simple as that.
double exp(double power) { return pow(E, power); }
性能
exp(random(0.0, 100.0))
的平均迭代次数:
EPSILON = 0.0001 -> 16.0763
EPSILON = 0.00001 -> 19.4108
EPSILON = 0.000001 -> 22.6639
EPSILON = 0.0000001 -> 26.0477
EPSILON = 0.00000001 -> 29.3929
GLSL 没有针对双精度类型的 pow
函数。此函数的经典实现使用递归。由于 GLSL 中不允许递归,因此我需要一个无递归的实现。
我还需要exp
功能。
这个问题可以简单地把它分成两个子问题来解决。
- 将指数的整数部分和小数部分分开
- 计算整个部分的功率(这很简单)
- 用小数部分计算幂(使用近似值)
- 将结果相乘
- 如果初始指数为负,则反转结果
/// This is a simple non recursive implementation for pow, where the exponent is an integer.
double pow(double base, int power) {
double res = 1.0;// Initialize result
while (power > 0.0) {
// If power is odd, multiply x with result
if (power % 2 == 1) {
res *= base;
}
// n must be even now
power /= 2;
base *= base;// Change x to x^2
}
return res;
}
// The desired precision. More precision results in slower execution.
const double EPSILON = 0.00000001;
double pow(double base, double power) {
// We want to ignore negative exponents for now. We invert our result at the if necessary.
bool negative = power < 0.0LF;
if (negative) {
power *= -1.0LF;
}
// Seperate the whole and fractional parts.
double fraction = power - int(power);
int integer = int(power - fraction);
// The whole part is easily calculated.
double intPow = pow(base, integer);
// The fractional part uses an approximation method.
double low = 0.0LF;
double high = 1.0LF;
double sqr = sqrt(base);
double acc = sqr;
double mid = high / 2.0LF;
while (abs(mid - fraction) > EPSILON) {
sqr = sqrt(sqr);
if (mid <= fraction) {
low = mid;
acc *= sqr;
} else {
high = mid;
acc *= (1.0LF / sqr);
}
mid = (low + high) / 2.0LF;
}
// Exponential rules allow us to simply multiply our results.
double result = intPow * acc;
// If we started with a negative exponent we invert the result.
if (negative) {
return 1.0LF / result;
}
return result;
}
const double E = 2.7182818284590452353602874LF;
/// e^x is as simple as that.
double exp(double power) { return pow(E, power); }
性能
exp(random(0.0, 100.0))
的平均迭代次数:
EPSILON = 0.0001 -> 16.0763
EPSILON = 0.00001 -> 19.4108
EPSILON = 0.000001 -> 22.6639
EPSILON = 0.0000001 -> 26.0477
EPSILON = 0.00000001 -> 29.3929