高效的10的x次幂算法

3

有人能帮助我找到一个有效的代码,用于计算10的x次方吗?

 private int power(int base, int exp)
{
    int result = 1;
    while (exp != 0)
    {
        if ((exp & 1) == 1)
            result *= base;
        exp >>= 1;
        base *= base;
    }

    return result;
}

代码来源这里,但我正在寻找一种可以输入3.14(double)的方法。我也不能使用任何库函数,幂可以是实数。因此,它不只是一个简单的整数算法,我们无法通过平方指数获得。


8
这段代码已经从https://dev59.com/hnVD5IYBdhLWcg3wE3No复制。你应该给原作者致谢。 - Jabir
3
那么,为什么你不能使用现有的库函数呢?它们存在的目的是让人们能够使用它们......它们通常也是编写良好且高效的。 - Krease
2
没有库函数的话,这个问题并不容易解决。你可以使用 pow 或者 explog 函数。 - Henry
为什么不想使用现有的库函数? - flup
6个回答

7

我想要一个算法,它不使用任何现有的库函数来解决问题。我会更新我的问题。 - Sayan
谢谢!我是Java的新手。我在谷歌上搜索了,但找不到math.class代码。你能否提供链接?这里没有代码:http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/StrictMath.java#StrictMath.pow%28double%2Cdouble%29 - Sayan
慢。它使用FPU和对数,即使是本地的,你也需要看上百个时钟周期。 - Agoston Horvath

1
这是Java StrictMath 实现中的pow方法。从技术上讲,StrictMath 更偏向于精度而非性能,但实际上它非常快,因为它在Java中被广泛使用。我已经为您复制和格式化了pow函数所需的源代码 :) :
public class YourMathClass {
    public static double pow(double x, double y) {
        // Special cases first.
        if (y == 0)
            return 1;
        if (y == 1)
            return x;
        if (y == -1)
            return 1 / x;
        if (x != x || y != y)
            return Double.NaN;
        // When x < 0, yisint tells if y is not an integer (0), even(1),
        // or odd (2).
        int yisint = 0;
        if (x < 0 && floor(y) == y)
            yisint = (y % 2 == 0) ? 2 : 1;
        double ax = abs(x);
        double ay = abs(y);
        // More special cases, of y.
        if (ay == Double.POSITIVE_INFINITY) {
            if (ax == 1)
                return Double.NaN;
            if (ax > 1)
                return y > 0 ? y : 0;
            return y < 0 ? -y : 0;
        }
        if (y == 2)
            return x * x;
        if (y == 0.5)
            return sqrt(x);
        // More special cases, of x.
        if (x == 0 || ax == Double.POSITIVE_INFINITY || ax == 1) {
            if (y < 0)
                ax = 1 / ax;
            if (x < 0) {
                if (x == -1 && yisint == 0)
                    ax = Double.NaN;
                else if (yisint == 1)
                    ax = -ax;
            }
            return ax;
        }
        if (x < 0 && yisint == 0)
            return Double.NaN;
        // Now we can start!
        double t;
        double t1;
        double t2;
        double u;
        double v;
        double w;
        if (ay > TWO_31) {
            if (ay > TWO_64) // Automatic over/underflow.
                return ((ax < 1) ? y < 0 : y > 0) ? Double.POSITIVE_INFINITY : 0;
            // Over/underflow if x is not close to one.
            if (ax < 0.9999995231628418)
                return y < 0 ? Double.POSITIVE_INFINITY : 0;
            if (ax >= 1.0000009536743164)
                return y > 0 ? Double.POSITIVE_INFINITY : 0;
            // Now |1-x| is <= 2**-20, sufficient to compute
            // log(x) by x-x^2/2+x^3/3-x^4/4.
            t = x - 1;
            w = t * t * (0.5 - t * (1 / 3.0 - t * 0.25));
            u = INV_LN2_H * t;
            v = t * INV_LN2_L - w * INV_LN2;
            t1 = (float) (u + v);
            t2 = v - (t1 - u);
        } else {
            long bits = Double.doubleToLongBits(ax);
            int exp = (int) (bits >> 52);
            if (exp == 0) // Subnormal x.
            {
                ax *= TWO_54;
                bits = Double.doubleToLongBits(ax);
                exp = (int) (bits >> 52) - 54;
            }
            exp -= 1023; // Unbias exponent.
            ax = Double.longBitsToDouble((bits & 0x000fffffffffffffL) | 0x3ff0000000000000L);
            boolean k;
            if (ax < SQRT_1_5)  // |x|<sqrt(3/2).
                k = false;
            else if (ax < SQRT_3) // |x|<sqrt(3).
                k = true;
            else {
                k = false;
                ax *= 0.5;
                exp++;
            }
            // Compute s = s_h+s_l = (x-1)/(x+1) or (x-1.5)/(x+1.5).
            u = ax - (k ? 1.5 : 1);
            v = 1 / (ax + (k ? 1.5 : 1));
            double s = u * v;
            double s_h = (float) s;
            double t_h = (float) (ax + (k ? 1.5 : 1));
            double t_l = ax - (t_h - (k ? 1.5 : 1));
            double s_l = v * ((u - s_h * t_h) - s_h * t_l);
            // Compute log(ax).
            double s2 = s * s;
            double r = s_l * (s_h + s) + s2 * s2 * (L1 + s2 * (L2 + s2 * (L3 + s2 * (L4 + s2 * (L5 + s2 * L6)))));
            s2 = s_h * s_h;
            t_h = (float) (3.0 + s2 + r);
            t_l = r - (t_h - 3.0 - s2);
            // u+v = s*(1+...).
            u = s_h * t_h;
            v = s_l * t_h + t_l * s;
            // 2/(3log2)*(s+...).
            double p_h = (float) (u + v);
            double p_l = v - (p_h - u);
            double z_h = CP_H * p_h;
            double z_l = CP_L * p_h + p_l * CP + (k ? DP_L : 0);
            // log2(ax) = (s+..)*2/(3*log2) = exp + dp_h + z_h + z_l.
            t = exp;
            t1 = (float) (z_h + z_l + (k ? DP_H : 0) + t);
            t2 = z_l - (t1 - t - (k ? DP_H : 0) - z_h);
        }
        // Split up y into y1+y2 and compute (y1+y2)*(t1+t2).
        boolean negative = x < 0 && yisint == 1;
        double y1 = (float) y;
        double p_l = (y - y1) * t1 + y * t2;
        double p_h = y1 * t1;
        double z = p_l + p_h;
        if (z >= 1024) // Detect overflow.
        {
            if (z > 1024 || p_l + OVT > z - p_h)
                return negative ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
        } else if (z <= -1075) // Detect underflow.
        {
            if (z < -1075 || p_l <= z - p_h)
                return negative ? -0.0 : 0;
        }
        // Compute 2**(p_h+p_l).
        int n = round((float) z);
        p_h -= n;
        t = (float) (p_l + p_h);
        u = t * LN2_H;
        v = (p_l - (t - p_h)) * LN2 + t * LN2_L;
        z = u + v;
        w = v - (z - u);
        t = z * z;
        t1 = z - t * (P1 + t * (P2 + t * (P3 + t * (P4 + t * P5))));
        double r = (z * t1) / (t1 - 2) - (w + z * w);
        z = scale(1 - (r - z), n);
        return negative ? -z : z;
    }

    public static int round(float f) {
        return (int) floor(f + 0.5f);
    }

    public static double floor(double a) {
        double x = abs(a);
        if (!(x < TWO_52) || (long) a == a)
            return a; // No fraction bits; includes NaN and infinity.
        if (x < 1)
            return a >= 0 ? 0 * a : -1; // Worry about signed zero.
        return a < 0 ? (long) a - 1.0 : (long) a; // Cast to long truncates.
    }

    public static double abs(double d) {
        return (d <= 0) ? 0 - d : d;
    }

    public static double sqrt(double x) {
        if (x < 0)
            return Double.NaN;
        if (x == 0 || !(x < Double.POSITIVE_INFINITY))
            return x;
        // Normalize x.
        long bits = Double.doubleToLongBits(x);
        int exp = (int) (bits >> 52);
        if (exp == 0) // Subnormal x.
        {
            x *= TWO_54;
            bits = Double.doubleToLongBits(x);
            exp = (int) (bits >> 52) - 54;
        }
        exp -= 1023; // Unbias exponent.
        bits = (bits & 0x000fffffffffffffL) | 0x0010000000000000L;
        if ((exp & 1) == 1) // Odd exp, double x to make it even.
            bits <<= 1;
        exp >>= 1;
        // Generate sqrt(x) bit by bit.
        bits <<= 1;
        long q = 0;
        long s = 0;
        long r = 0x0020000000000000L; // Move r right to left.
        while (r != 0) {
            long t = s + r;
            if (t <= bits) {
                s = t + r;
                bits -= t;
                q += r;
            }
            bits <<= 1;
            r >>= 1;
        }
        // Use floating add to round correctly.
        if (bits != 0)
            q += q & 1;
        return Double.longBitsToDouble((q >> 1) + ((exp + 1022L) << 52));
    }

    private static double scale(double x, int n) {
        //       if (Configuration.DEBUG && abs(n) >= 2048)
        //         throw new InternalError("Assertion failure");
        if (x == 0 || x == Double.NEGATIVE_INFINITY || !(x < Double.POSITIVE_INFINITY) || n == 0)
            return x;
        long bits = Double.doubleToLongBits(x);
        int exp = (int) (bits >> 52) & 0x7ff;
        if (exp == 0) // Subnormal x.
        {
            x *= TWO_54;
            exp = ((int) (Double.doubleToLongBits(x) >> 52) & 0x7ff) - 54;
        }
        exp += n;
        if (exp > 0x7fe) // Overflow.
            return Double.POSITIVE_INFINITY * x;
        if (exp > 0) // Normal.
            return Double.longBitsToDouble((bits & 0x800fffffffffffffL) | ((long) exp << 52));
        if (exp <= -54)
            return 0 * x; // Underflow.
        exp += 54; // Subnormal result.
        x = Double.longBitsToDouble((bits & 0x800fffffffffffffL) | ((long) exp << 52));
        return x * (1 / TWO_54);
    }

    private static final double TWO_31 = 0x80000000L, // Long bits 0x41e0000000000000L.
            TWO_52 = 0x10000000000000L, // Long bits 0x4330000000000000L.
            TWO_54 = 0x40000000000000L, // Long bits 0x4350000000000000L.
            TWO_64 = 1.8446744073709552e19; // Long bits 0x7fe0000000000000L.
    private static final double L1 = 0.5999999999999946, // Long bits 0x3fe3333333333303L.
            L2 = 0.4285714285785502, // Long bits 0x3fdb6db6db6fabffL.
            L3 = 0.33333332981837743, // Long bits 0x3fd55555518f264dL.
            L4 = 0.272728123808534, // Long bits 0x3fd17460a91d4101L.
            L5 = 0.23066074577556175, // Long bits 0x3fcd864a93c9db65L.
            L6 = 0.20697501780033842, // Long bits 0x3fca7e284a454eefL.
            P1 = 0.16666666666666602, // Long bits 0x3fc555555555553eL.
            P2 = -2.7777777777015593e-3, // Long bits 0xbf66c16c16bebd93L.
            P3 = 6.613756321437934e-5, // Long bits 0x3f11566aaf25de2cL.
            P4 = -1.6533902205465252e-6, // Long bits 0xbebbbd41c5d26bf1L.
            P5 = 4.1381367970572385e-8, // Long bits 0x3e66376972bea4d0L.
            DP_H = 0.5849624872207642, // Long bits 0x3fe2b80340000000L.
            DP_L = 1.350039202129749e-8, // Long bits 0x3e4cfdeb43cfd006L.
            OVT = 8.008566259537294e-17; // Long bits 0x3c971547652b82feL.
    private static final double SQRT_1_5 = 1.224744871391589, // Long bits 0x3ff3988e1409212eL.
            SQRT_3 = 1.7320508075688772, // Long bits 0x3ffbb67ae8584caaL.
            CP = 0.9617966939259756, // Long bits 0x3feec709dc3a03fdL.
            CP_H = 0.9617967009544373, // Long bits 0x3feec709e0000000L.
            CP_L = -7.028461650952758e-9, // Long bits 0xbe3e2fe0145b01f5L.
            LN2 = 0.6931471805599453, // Long bits 0x3fe62e42fefa39efL.
            LN2_H = 0.6931471803691238, // Long bits 0x3fe62e42fee00000L.
            LN2_L = 1.9082149292705877e-10, // Long bits 0x3dea39ef35793c76L.
            INV_LN2 = 1.4426950408889634, // Long bits 0x3ff71547652b82feL.
            INV_LN2_H = 1.4426950216293335, // Long bits 0x3ff7154760000000L.
            INV_LN2_L = 1.9259629911266175e-8; // Long bits 0x3e54ae0bf85ddf44L.
}

现在,如果你真的想让大规模计算变得非常快(我指的是数十万次pow方法调用),我建议你使用pow函数,它来自Apache Commons Math FastMath类,这个函数非常快。

根据你的问题,你不想要另一个库,所以我把FastMathFastMathLiteralArrays的必要部分复制到了一个文件中。这个文件太大了,无法放在StackOverflow上,所以我把它放在了PasteAll上:

http://www.pasteall.org/53238/java

警告:这个文件的代码非常庞大。除非你确定要使用pow进行极长时间的计算,否则不应该使用此文件,因为1)它将使用大量内存,2)如果你只是使用它一两次,它可能会变得更慢

无论如何,希望这回答了你所有关于pow效率的需求 :) (我也希望我能得到奖励;))


1

如果不使用库代码,为双精度输入提供服务是毫无意义的 - 你必须使用库或抄袭库代码源 - 这是同样的事情。

然而,对于整数算术,不使用库是可行的。

回答您所述和命名的问题:

private static int tenToPower(int power) {
    int result = 1;
    for (int p = 0; p < power; p++)
        result *= 10;
    return result;
}

由于您的示例代码中返回类型为int,因此我假设负幂(会产生分数结果)不在范围内。


-1

如果你想要非常快速和高效的10次幂计算,并且只需要整数,你可以使用一个表格(这是90年代的一个古老技巧),就像这样:

static final long powerOfTen[] = {1,10,100,1000,10000, ....} // you get the idea

然后

static long powerOfTen(long input, int power) {
    return input * powerOfTen[power];
}

如果您需要更高的精度,您也可以预先计算,但请记住,随着表格变得越来越大,您获得的收益越来越少,因为它使用了更多的内存/L1缓存。上面的要点是,由于其非常小的大小,静态方法将被内联,并且静态数组如此小,以至于适合64字节的L1缓存页面,这意味着从性能上讲,这将归结为寄存器上的整数乘法。

1
这个问题的答案是什么 高效的10的x次方算法,其中[x]可以是3.14(双精度)。 - greybeard

-1

不必计算 10^x,你可以计算 e^(x*ln(10))。所以你的问题归结为 this one。我可以通过计算泰勒级数来提供一个简单的实现:

double tenPow(double x) {
    // ln(10)
    double logTen = 2.3025850929940456840179914546843642076011014886287729;
    double sum = 0.;
    x *= logTen; // x * ln(10)
    double tmp = 1.;
    for (double i = 1.; tmp > 0.; i += 1.) {
        sum += tmp;
        tmp *= x;
        tmp /= i;
    }
    return sum;
}

编辑1 如评论中所述,此方法对于大的x不起作用。但是我们可以将x分解为整数部分和小数部分,即x = xi + xfe^(xi + xf) = e^xf * e^xie^xi可以使用递归指数计算有效地计算。而e^xf可以使用泰勒级数计算。

编辑2 这是我实现此算法的代码:

public static double fastTenPow(double x) {
    if (x < 0.) {
        return 1. / fastTenPow(-x);
    }
    int intPart = (int) x;
    double fractPart = x - intPart;
    return fractTenPow(fractPart) * intTenPow(intPart);
}

private static double intTenPow(int x) { // copied it
    double res = 1.;
    double base = 10.;
    while (x != 0) {
        if ((x & 1) == 1) {
            res *= base;
        }
        x >>= 1;
        base *= base;
    }
    return res;
}

private static final double LOG_TEN = 2.3025850929940456840179914546843642076011014886287729;
private static final double EPS = 0.00000000000000001;

private static double fractTenPow(double x) {
    double sum = 0.;
    x *= LOG_TEN;
    double tmp = 1.;
    for (double i = 1.; tmp > EPS; i += 1.) {
        sum += tmp;
        tmp *= x;
        tmp /= i;
    }
    return sum;
}

3
如果x变得更大,这样做就不太有效率(也不准确)。当x < 0时,准确度也非常糟糕。 - Henry
1
exp和log都可以通过对定点变量进行二分搜索算法来计算...并且它们直接存在于FPU x87协处理器上,因此在PC上不需要使用数学库,只需使用asm即可。 - Spektre

-3

你不能使用简单的递归吗?

 public static void main(String[] args) {
    System.out.println(power10x(4,1)); //replace ur X value with 4

}


public static int power10x(int a,int val){
     if(a==0)
        return val;
     else
         return power10x(a-1,val*10);

}

这基本上是 这个答案 的复制,而且 OP 指定 a 应该是一个 double - Jason C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接