如何在Java中快速计算2的大的正或负2的幂?

时间:2018-02-10 21:47:06

标签: java floating-point precision ieee-754 exponent

我想计算两个大于2 62 的幂,所以我必须将结果存储在double中,并且不能使用(1L << exp)技巧。我还想存储代表2的负幂的分数。

2 个答案:

答案 0 :(得分:1)

由于IEEE 754标准规定了一个隐藏位,你可以简单地将52位有效位数部分保留为0,只需要改变指数部分,这是一个有偏无符号整数,用于正常范围内的幂。

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

为了实现次正规幂的逐渐下溢,即当指数小于-1022时,你必须指定-1023的指数并将1位移入有效数字。

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));
}

当指数大于1023时,你还应确保功率溢出到无穷大,当指数小于-1074时,功率下溢到0。

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));
}

最后,您还可以通过对常量进行硬编码来删除对内部sun.misc包的依赖。

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);
}

要验证此实现的正确性,您可以添加一个单元测试来检查下溢和溢出行为,并将每个可表示的2的幂与Math.pow(2, x)进行比较。

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

在我的机器上,pow2(i)微基准标记需要50-100毫秒,而pow(2, i)微基准标记需要2000-2500毫秒。

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);

答案 1 :(得分:1)

Java为此提供了java.lang.Math.scalb(float f, int scaleFactor)。它将f乘以2 scaleFactor