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

Posted

技术标签:

【中文标题】如何在 Java 中快速计算 2 的大正或负幂?【英文标题】:How do I quickly calculate a large positive, or negative, power of 2 in Java? 【发布时间】:2018-02-10 21:47:06 【问题描述】:

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

【问题讨论】:

使用 BigInteger 或 BitSets 怎么样? 为什么Math#pow 不够用?您是否正确地标定了它是您程序中的瓶颈?即使是这样,您不能在程序开始时使用Math#pow 计算所需的值以进行记忆吗? 【参考方案1】:

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

【讨论】:

【参考方案2】:

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

【讨论】:

以上是关于如何在 Java 中快速计算 2 的大正或负幂?的主要内容,如果未能解决你的问题,请参考以下文章

主定理:当 f(n) 包含对数的负幂时的问题

单电源运放添加偏置电压的原因

HDU1753 (大正小数相加)

使用自动布局创建一个大正方形的网格

计算机快速计算,2^N是如何实现的?

在 unix/linux shell 中进行模式匹配时,如何使用反向或负通配符?