Java 二分式幂的最近倍数

Java 二分式幂的最近倍数,java,double,rounding,Java,Double,Rounding,是否有一种优化的、性能良好的方法将双精度四舍五入到最接近给定分数幂倍数的精确值 换言之,将.44四舍五入到最接近的1/16(换言之,四舍五入到可以表示为n/16的值,其中n是整数)将是.4375。注:这是相关的,因为两个分数的幂可以在没有舍入误差的情况下存储,例如 public class PowerOfTwo { public static void main(String... args) { double inexact = .44; double exact = .4

是否有一种优化的、性能良好的方法将双精度四舍五入到最接近给定分数幂倍数的精确值

换言之,将
.44
四舍五入到最接近的1/16(换言之,四舍五入到可以表示为
n/16
的值,其中
n
是整数)将是
.4375
。注:这是相关的,因为两个分数的幂可以在没有舍入误差的情况下存储,例如

public class PowerOfTwo {
  public static void main(String... args) {
    double inexact = .44;
    double exact = .4375;

    System.out.println(inexact + ":   " + Long.toBinaryString(Double.doubleToLongBits(inexact)));
    System.out.println(exact + ": " + Long.toBinaryString(Double.doubleToLongBits(exact)));
  }
}
输出:

0.44:   11111111011100001010001111010111000010100011110101110000101001
0.4375: 11111111011100000000000000000000000000000000000000000000000000
0.5
0.5
0.4375
0.4375
0.4375
0.4375
0.44140625

如果问题是将任何数字四舍五入到预先确定的二进制精度,则需要执行以下操作:

  • 使用将值转换为
    long
  • 检查指数:如果指数太大(
    exponent+required precision>51
    ,有效位中的位数),您将无法进行任何舍入,但您不必这样做:该数字已经满足您的标准

  • 另一方面,如果
    exponent+required precision这里是我第一次尝试一个解决方案,那么@biziclop的答案中并不能处理所有的情况,可能会使用“floor”而不是“round”

    公共静态双循环(双d,整数精度){
    双长部件=数学打印(d);
    双精度=d-长部件;
    长位=双。双到长位(十进制);
    
    长面具=-1l如果你想要四舍五入到2的任意幂,你需要一些魔法

    您需要检查指数:

    int exponent = Math.getExponent(inexact);
    
    然后知道尾数中有53位,就可以找到需要舍入的位


    或者干脆做:

    Math.round(inexact* (1l<<exponent))/(1l<<exponent)
    

    Math.round(不精确*(1l如果要选择2的幂,最简单的方法是乘以例如16,四舍五入到最接近的整数,然后除以16。请注意,如果结果是正常数,则除以2的幂是准确的。这可能会导致次正常数的舍入错误

    以下是使用此技术的示例程序:

    public class Test {
      public static void main(String[] args) {
        System.out.println(roundToPowerOfTwo(0.44, 2));
        System.out.println(roundToPowerOfTwo(0.44, 3));
        System.out.println(roundToPowerOfTwo(0.44, 4));
        System.out.println(roundToPowerOfTwo(0.44, 5));
        System.out.println(roundToPowerOfTwo(0.44, 6));
        System.out.println(roundToPowerOfTwo(0.44, 7));
        System.out.println(roundToPowerOfTwo(0.44, 8));
      }
    
      public static double roundToPowerOfTwo(double in, int power) {
        double multiplier = 1 << power;
        return Math.rint(in * multiplier) / multiplier;
      }
    }
    

    在所有情况下都要做到这一点是有点棘手的。如果我必须解决这样的任务,我通常会从一个幼稚的实现开始,我可以非常肯定它是正确的,然后才开始实现一个优化的版本。在这样做的时候,我总是可以与幼稚的方法进行比较,以验证我的结果

    最简单的方法是从1开始,用2乘/除,直到我们将输入的绝对值括起来。然后,我们将输出更接近边界的值。实际上有点复杂:如果值是NaN或无穷大,则需要特殊处理

    代码如下:

    public static double getClosestPowerOf2Loop(final double x) {
        final double absx = Math.abs(x);
        double prev = 1.0;
        double next = 1.0;
        if (Double.isInfinite(x) || Double.isNaN(x)) {
            return x;
        } else if (absx < 1.0) {
            do {
                prev = next;
                next /= 2.0;
            } while (next > absx);
        } else if (absx > 1.0) {
            do {
                prev = next;
                next *= 2.0;
            } while (next < absx);
        }
        if (x < 0.0) {
            prev = -prev;
            next = -next;
        }
        return (Math.abs(next - x) < Math.abs(prev - x)) ? next : prev;
    }
    
    我完全相信我已经涵盖了所有的角落案例,但以下测试确实在运行:

    public static void main(final String[] args) {
        final double[] values = {
            5.0, 4.1, 3.9, 1.0, 0.0, -0.1, -8.0, -8.1, -7.9,
            0.9 * Double.MIN_NORMAL, -0.9 * Double.MIN_NORMAL,
            Double.NaN, Double.MAX_VALUE, Double.MIN_VALUE,
            Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
        };
        for (final double value : values) {
            final double powerL = getClosestPowerOf2Loop(value);
            final double powerB = getClosestPowerOf2Bits(value);
            System.out.printf("%17.10g  -->  %17.10g  %17.10g%n",
                              value, powerL, powerB);
            assert Double.doubleToLongBits(powerL) == Double.doubleToLongBits(powerB);
        }
    }
    
    输出:

    0.44:   11111111011100001010001111010111000010100011110101110000101001
    0.4375: 11111111011100000000000000000000000000000000000000000000000000
    
    0.5
    0.5
    0.4375
    0.4375
    0.4375
    0.4375
    0.44140625
    
    5.000000000-->4.000000000 4.000000000
    4.100000000  -->        4.000000000        4.000000000
    3.900000000  -->        4.000000000        4.000000000
    1.000000000  -->        1.000000000        1.000000000
    0.000000000  -->        0.000000000        0.000000000
    -0.1000000000  -->      -0.1250000000      -0.1250000000
    -8.000000000  -->       -8.000000000       -8.000000000
    -8.100000000  -->       -8.000000000       -8.000000000
    -7.900000000  -->       -8.000000000       -8.000000000
    2.0025664733E-308-->2.225073859e-308 2.225073859e-308
    -2.0025664733E-308-->-2.225073859e-308-2.225073859e-308
    楠-->楠楠楠
    1.797693135e+308-->8.988465674e+307 8.988465674e+307
    4.90000000E-324-->4.90000000E-324 4.90000000E-324
    -无限-->-无限-无限
    无限-->无限无限
    
    表演怎么样

    我运行了以下基准测试

    public static void main(final String[] args) {
        final Random rand = new Random();
        for (int i = 0; i < 1000000; ++i) {
            final double value = Double.longBitsToDouble(rand.nextLong());
            final double power = getClosestPowerOf2(value);
        }
    }
    
    publicstaticvoidmain(最终字符串[]args){
    最终随机数=新随机数();
    对于(int i=0;i<1000000;++i){
    最终双精度值=double.longBitsToDouble(rand.nextLong());
    最终双功率=GetClosesPowerROF2(值);
    }
    }
    
    其中
    getClosestPowerOf2
    将被
    getClosestPowerOf2Loop
    getClosestPowerOf2Bits
    替换。在我的笔记本电脑上,我得到以下结果:

    • getClosestPowerOf2Loop
      :2.35s
    • getClosestPowerOf2Bits
      :1.80s

    这真的值得付出努力吗?

    您希望存储的结果与正常的双精度值有何不同?因为双精度的指数部分,所有的双精度值都是2^x的倍数。您希望得到什么?您想让结果精确地表示为2^x的值吗?这里的最佳选择是经过长时间的屏蔽,然后折回双倍again@fge我同意,但我不确定该怎么做。特别是如果分数的整数部分不是零,例如
    12345.4375
    这是怎么回事?你只改变分数,而不是整数exponent@fge因为当指数较大时,必须屏蔽较少的位,所以我尝试实现这个答案此回复的第一段是对我原始问题中措辞不明确的直接回复。我已修复了问题以澄清。@durron597我已相应地编辑了我的答案,并添加了一个示例程序。我已向您投了赞成票。我想知道这个答案是否比我的位掩码答案快,以及它是否适用于大整数部分。@durron597 Goo关于大值的d点-如果乘法的结果溢出
    long
    ,它将失败。我已经纠正了@durron597提出的溢出问题,使用
    Math.rint
    而不是
    Math.round
    。现在,只有当乘法的结果大于
    Double.MAX\u值时,它才会溢出。它将向零进位,表示正的下限,表示负的上限。我建议使用Math.rint将intPart存储为double,而不是将int存储为除相对较小的double之外的所有整数。在我的实际应用程序中,我知道输入永远不会溢出