How do I quickly calculate a large positive, or negative, power of 2 in Java?

柔情痞子 提交于 2020-01-03 04:49:13

问题


I want to calculate powers of two larger than 262, so I must store the result in a double and can't use the (1L << exp) trick. I also want to store fractions representing negative powers of two.


回答1:


Java provides java.lang.Math.scalb(float f, int scaleFactor) for this. It multiplies f by 2scaleFactor.




回答2:


Since the IEEE 754 standard specifies a hidden bit, you can simply leave the 52-bit significand portion as 0 and only need to change the exponent portion, which is a biased unsigned integer, for powers in the normal range.

private static double pow2(int x) {
    return Double.longBitsToDouble((x + (long) sun.misc.DoubleConsts.EXP_BIAS) << (sun.misc.DoubleConsts.SIGNIFICAND_WIDTH - 1));
}

In order to also implement gradual underflow for subnormal powers, i.e. when the exponent is less than -1022, you have to specify an exponent of -1023 and shift a 1 bit into the significand.

private static double pow2(int x) {
    if (x < 1 - sun.misc.DoubleConsts.EXP_BIAS)
        return Double.longBitsToDouble(1L << (sun.misc.DoubleConsts.SIGNIFICAND_WIDTH - 1 + x + sun.misc.DoubleConsts.EXP_BIAS - 1));
    return Double.longBitsToDouble((x + (long) sun.misc.DoubleConsts.EXP_BIAS) << (sun.misc.DoubleConsts.SIGNIFICAND_WIDTH - 1));
}

You also should make sure that powers overflow to infinity when the exponent is greater than 1023, and underflow to 0 when the exponent is less than -1074.

private static double pow2(int x) {
    if (x < 2 - sun.misc.DoubleConsts.EXP_BIAS - sun.misc.DoubleConsts.SIGNIFICAND_WIDTH)
        return 0;
    if (x > sun.misc.DoubleConsts.EXP_BIAS)
        return Double.POSITIVE_INFINITY;
    if (x < 1 - sun.misc.DoubleConsts.EXP_BIAS)
        return Double.longBitsToDouble(1L << (sun.misc.DoubleConsts.SIGNIFICAND_WIDTH - 1 + x + sun.misc.DoubleConsts.EXP_BIAS - 1));
    return Double.longBitsToDouble((x + (long) sun.misc.DoubleConsts.EXP_BIAS) << (sun.misc.DoubleConsts.SIGNIFICAND_WIDTH - 1));
}

Finally, you can also remove the dependency on the internal sun.misc package by hardcoding the constants.

private static double pow2(int x) {
    final int EXP_BIAS = 1023;
    final int SIGNIFICAND_WIDTH = 53;
    //boolean isSubnormal = x < 1 - EXP_BIAS;

    if (x < 2 - EXP_BIAS - SIGNIFICAND_WIDTH) return 0;
    //if (x > EXP_BIAS) return Double.POSITIVE_INFINITY;
    x = Math.min(x, EXP_BIAS + 1);
    //long exp = isSubnormal ? 1 : (x + EXP_BIAS);
    long exp = Math.max(1, x + EXP_BIAS);
    //int shift = SIGNIFICAND_WIDTH - 1 + (isSubnormal ? (x + EXP_BIAS - 1) : 0);
    int shift = SIGNIFICAND_WIDTH - 1 + Math.min(0, x + EXP_BIAS - 1);
    return Double.longBitsToDouble(exp << shift);
}

To verify the correctness of this implementation, you can add a unit test that checks the underflow and overflow behavior and compares every representable power of 2 to Math.pow(2, x).

for (int i = -1075; i <= 1024; i++)
    Assert.assertTrue(pow2(i) == Math.pow(2, i));

On my machine, the pow2(i) microbenchmark takes between 50-100ms while the pow(2, i) microbenchmark takes between 2000-2500ms.

long start, end;

start = System.currentTimeMillis();
for (int iter = 0; iter < 10000; iter++)
    for (int i = -1075; i <= 1024; i++)
        pow2(i);
end = System.currentTimeMillis();
System.out.println(end - start);

start = System.currentTimeMillis();
for (int iter = 0; iter < 10000; iter++)
    for (int i = -1075; i <= 1024; i++)
        Math.pow(2, i);
end = System.currentTimeMillis();
System.out.println(end - start);


来源:https://stackoverflow.com/questions/48725903/how-do-i-quickly-calculate-a-large-positive-or-negative-power-of-2-in-java

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!