我想知道进行比例计算的最快方法,即y = x * a / b
,其中所有值都是32位,无符号,a
和b
是固定的(初始化一次) ,然后不要改变)但在编译时不知道。保证结果不会溢出(即使中间乘法可能需要64位)。编程语言并不重要,但Java最适合我的情况。它需要尽可能快(纳秒重要)。我目前使用:
int result = (int) ((long) x * a / b);
但分裂很慢。我知道Perform integer division using multiplication,所以最好是类型的公式:
int result = (int) (((long) x * factor) >>> shift);
其中factor
和shift
可以从a
和b
计算得出(计算速度很慢)。
我试图简单地替换原始公式的除法部分,但它不起作用,因为两次乘法的结果不适合64位:
// init
int shift = 63 - Integer.numberOfLeadingZeros(b);
int factor = ((1L << shift) / b) + 1;
...
// actual calculation
int result = (int) ((long) x * a * factor) >>> shift);
在我的情况下,结果实际上不必完全准确(一个人就可以了)。
答案 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
。没有比特浪费。 dFactor
向long
的转换向下舍入,这可能不是最理想的。
准备工作肯定会加快,特别是首先通过查看前导零来消除循环。
答案 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)
由于a
和b
都是固定的,你可以只进行一次除法并重复使用结果(这可能已经在幕后自动发生):
int c = a / b;
int y1 = x1 * c;
int y2 = x2 * c;
...
如果你真的需要优化它,请考虑在GPU
上运行它(例如使用CUDA
的java绑定),这将允许你并行化计算,尽管这很多更难实施。
最后,在测试时添加计时器总是一个好主意,因此您可以运行基准测试以确保优化实际上提高了性能。