使用乘法+移位(32位)乘以+除

时间:2016-10-05 11:55:06

标签: java optimization bit-manipulation division multiplication

我想知道进行比例计算的最快方法,即y = x * a / b,其中所有值都是32位,无符号,ab是固定的(初始化一次) ,然后不要改变)但在编译时不知道。保证结果不会溢出(即使中间乘法可能需要64位)。编程语言并不重要,但Java最适合我的情况。它需要尽可能快(纳秒重要)。我目前使用:

int result = (int) ((long) x * a / b);

但分裂很慢。我知道Perform integer division using multiplication,所以最好是类型的公式:

int result = (int) (((long) x * factor) >>> shift);

其中factorshift可以从ab计算得出(计算速度很慢)。

我试图简单地替换原始公式的除法部分,但它不起作用,因为两次乘法的结果不适合64位:

// init
int shift = 63 - Integer.numberOfLeadingZeros(b);
int factor = ((1L << shift) / b) + 1;
...
// actual calculation
int result = (int) ((long) x * a * factor) >>> shift);

在我的情况下,结果实际上不必完全准确(一个人就可以了)。

3 个答案:

答案 0 :(得分:2)

怎么样?
long a2 = a & 0xFFFFFFFFL;
long b2 = b & 0xFFFFFFFFL;
checkArgument(b2 > 0);
double dFactor = (double) a2 / b2;
shift = 0;
while (dFactor < 1L<<32) {
   dFactor *= 2;
   shift++;
}
factor = (long) dFactor;

准备和

int result = (int) (((x & 0xFFFFFFFFL) * factor) >>> shift);

快速部分?现在我们有2**32 <= factor < 2**33,对于任何int x >= 0,产品x * factor < 2**31 * 2**33 = 2**64只适合unsigned long。没有比特浪费。 dFactorlong的转换向下舍入,这可能不是最理想的。

准备工作肯定会加快,特别是首先通过查看前导零来消除循环。

答案 1 :(得分:0)

如果使用公式(x * factor) >>> shift,我认为不可能总是得到确切的结果:对于某些边缘情况,结果是1太低或1太高。为了始终获得正确的结果,公式需要更复杂。我找到了一个不需要浮点的解决方案,这里是一个测试用例:

static final Set<Integer> SOME_VALUES = new TreeSet<Integer>();

static {
    Set<Integer> set = SOME_VALUES;
    for (int i = 0; i < 100; i++) {
        set.add(i);
    }
    set.add(Integer.MAX_VALUE);
    set.add(Integer.MAX_VALUE - 1);
    for (int i = 1; i > 0; i += i) {
        set.add(i - 1);
        set.add(i);
        set.add(i + 1);
    }
    for (int i = 1; i > 0; i *= 3) {
        set.add(i);
    }
    Random r = new Random(1);
    for (int i = 0; i < 100; i++) {
        set.add(r.nextInt(Integer.MAX_VALUE));
    }
}

private static void testMultiplyDelete() {
    for (int a : SOME_VALUES) {
        for (int b : SOME_VALUES) {
            if (b == 0) {
                continue;
            }
            int shift = 32;
            // sometimes 1 too low
            long factor = (1L << shift) * a / b;
            // sometimes 1 too high
            // long factor = ((1L << shift) * a / b) + 1;

            // sometimes 1 too low
            // double dFactor = (double) a / b;
            // int shift = 0;
            // while (dFactor > 0 && dFactor < (1L << 32)) {
            //     dFactor *= 2;
            //     shift++;
            // }
            // long factor = (long) dFactor;

            for (int x : SOME_VALUES) {
                long expectedResult = (long) x * a / b;
                if (expectedResult < 0 ||
                        expectedResult >= Integer.MAX_VALUE) {
                    continue;
                }
                int result = (int) ((x * factor) >>> shift);
                if (Math.abs(result - expectedResult) > 1) {
                    System.out.println(x + "*" + a + "/" + b +
                            "=" + expectedResult + "; " +
                            "(" + x + "*" + factor + ")>>>" + shift + "=" + result);
                }
            }
        }
    }
}

答案 2 :(得分:-2)

由于ab都是固定的,你可以只进行一次除法并重复使用结果(这可能已经在幕后自动发生):

int c = a / b;
int y1 = x1 * c;
int y2 = x2 * c;
...

如果你真的需要优化它,请考虑在GPU上运行它(例如使用CUDA的java绑定),这将允许你并行化计算,尽管这很多更难实施。

最后,在测试时添加计时器总是一个好主意,因此您可以运行基准测试以确保优化实际上提高了性能。