我正在寻找代码审查,最佳实践和优化。

public final class DivThreeEfficiently {

    private DivThreeEfficiently() {}

    /**
     * Returns true if the input number is divisible by three. 
     * Else returns false.
     * 
     * @param n     the input number
     * @return      true if input number is divisible by three
     */
    public static boolean isMultipleOfThree(int n) {
        if(n < 0) n = -n;

        int evenCtr = 0;
        int oddCtr = 0;

        while (n != 0) {
            if ((n & 1) == 1) { 
                oddCtr++;
            } 
            n = n>>1;
            if ((n & 1) == 1) {
                evenCtr++;
            } 
            n = n>>1;
        }

        return evenCtr == oddCtr;
    }   
}

public class DivThreeEfficientlyTest {

    @Test
    public void testEvenNegative() {
        assertTrue(DivThreeEfficiently.isMultipleOfThree(-3));
        assertTrue(DivThreeEfficiently.isMultipleOfThree(-6));
        assertTrue(DivThreeEfficiently.isMultipleOfThree(-12));
    }

    @Test
    public void testEvenPositive() {
        assertTrue(DivThreeEfficiently.isMultipleOfThree(0));
        assertTrue(DivThreeEfficiently.isMultipleOfThree(3));
        assertTrue(DivThreeEfficiently.isMultipleOfThree(6));
    }


    @Test
    public void testOddNegative() {
        assertFalse(DivThreeEfficiently.isMultipleOfThree(-1));
        assertFalse(DivThreeEfficiently.isMultipleOfThree(-4));
        assertFalse(DivThreeEfficiently.isMultipleOfThree(-11));
    }

    @Test
    public void testOddPositive() {
        assertFalse(DivThreeEfficiently.isMultipleOfThree(1));
        assertFalse(DivThreeEfficiently.isMultipleOfThree(4));
        assertFalse(DivThreeEfficiently.isMultipleOfThree(11));
    }
}


评论

我想n%3 == 0在作弊吗?

如下所述,需要更多测试。 isMultipleOfThree(21)返回false。

下一个测试将是确认您的方法实际上是一种性能提升,这远不是显而易见的...

@ user3580294这完美地说明了早期CPU /内存资源优化的危险:在这个问题上花了多少小时的工程师时间(但是很有趣),这些问题多年来一直被其他人多次考虑? “恭喜!在接下来的十年中,您每个月为我们节省了多达47美分,而建造这一切的成本都远低于$ 18,271!” :)

是的,就像有人说的那样,除法确实是一条缓慢的指令。但是我强烈怀疑(n%3)== 0仍然会比包含循环和多个变量的更复杂的函数执行得快得多。在实践中,只要遇到这样的问题,我都会执行“%”。如果我的环境必须在非常有限的CPU上每秒执行数千次这样的操作,那么我将尝试其他选项并进行时序测试。但是,如果有比n%3更快的方法,我会感到惊讶。

#1 楼

电脑不是人。人们可能会发现应用除数规则最容易。但是,计算机不在乎。

最简单的代码是检查n % 3 == 0。模运算符使用JVM操作码irem,其效率与您获得的效率差不多。

在Intel CPU上,irem可能是使用IDIV(有符号除法)指令实现的。在大多数现代x86兼容处理器上,具有32位操作数的IDIV使用10至30个微操作,并且具有20至61个时钟周期的延迟。更重要的是,更新更强大的硬件的趋势是使IDIV占用更少的时钟周期。换句话说,将程序当作RISC处理器来编写是适得其反的“优化”,最好是让硬件充分发挥除法功能。

进行比较……

javap -c DivThreeEfficiently

 public final class DivThreeEfficiently {
  public static boolean isMultipleOfThree(int);
    Code:
       0: iload_0       
       1: ifge          7
       4: iload_0       
       5: ineg          
       6: istore_0      
       7: iconst_0      
       8: istore_1      
       9: iconst_0      
      10: istore_2      
      11: iload_0       
      12: ifeq          46
      15: iload_0       
      16: iconst_1      
      17: iand          
      18: iconst_1      
      19: if_icmpne     25
      22: iinc          2, 1
      25: iload_0       
      26: iconst_1      
      27: ishr          
      28: istore_0      
      29: iload_0       
      30: iconst_1      
      31: iand          
      32: iconst_1      
      33: if_icmpne     39
      36: iinc          1, 1
      39: iload_0       
      40: iconst_1      
      41: ishr          
      42: istore_0      
      43: goto          11
      46: iload_1       
      47: iload_2       
      48: if_icmpne     55
      51: iconst_1      
      52: goto          56
      55: iconst_0      
      56: ireturn       
}
 


Div3MoreEfficiently.java

public class Div3MoreEfficiently {
    private Div3MoreEfficiently() {}

    public static boolean isMultipleOfThree(int n) {
        return n % 3 == 0;
    }
}


javap -c Div3MoreEfficiently

 public class Div3MoreEfficiently {
  public static boolean isMultipleOfThree(int);
    Code:
       0: iload_0       
       1: iconst_3      
       2: irem          
       3: ifne          10
       6: iconst_1      
       7: goto          11
      10: iconst_0      
      11: ireturn       
}
 


评论


\ $ \ begingroup \ $
在大多数处理器上,获得余数都使用除法运算。如果这种方法能正常工作(我不确定是可以做到的),则最终结果可能比使用除法快得多。那可能取决于底层处理器。
\ $ \ endgroup \ $
–杰里·科芬(Jerry Coffin)
2014年6月2日22:26

\ $ \ begingroup \ $
@JerryCoffin 1)如果n%3 == 0不是最明显的正确实现,那么我不知道会说服您什么。 2)我添加了更多关于时钟效率的理由。另请参阅基准。
\ $ \ endgroup \ $
– 200_success
14年3月3日在18:54

\ $ \ begingroup \ $
“这种方法”是指OP的方法,而不是i%3 == 0(我同意这显然是正确的-这就是为什么我用它在答案中创建“黄金”值的原因)。关于时钟效率:并不是说他的方法更高效,只是我可以想象至少可以使用一个处理器。
\ $ \ endgroup \ $
–杰里·科芬(Jerry Coffin)
2014年6月3日18:59



\ $ \ begingroup \ $
的确,用Java编写此代码的最佳方法是仅使用i%3 == 0,但肯定不是,因为idiv指令特别有效。事实上,它的效率极低(我们可以用乘法和移位来完成),我们可以做得更好,但是好的编译器会为您做到这一点。 gcc当然可以,但是我不确定HotSpot是否也可以。
\ $ \ endgroup \ $
– Voo
2014年6月5日18:48

#2 楼

除法指令通常很慢,因为它们必须对商的每一位进行一次条件减法。可能会编写出比这更好的代码,但是需要对商的每一位进行迭代的循环不适合这样做。

我建议如果数字为正,您可以先将高11位加到中间10位,再将低10位开始。这将产生12位值,当且仅当原始值是(称为值Q)时,该值才是3的倍数。将Q乘以0x5556,然后右移16,乘以3,然后从Q中减去。当且仅当原始数字为3的倍数时,该结果才为零。对于具有单周期乘法但具有32周期除法的处理器,这种方法最终可能比使用除法指令快得多。

int Q = (n >> 20) + ((n >> 10) & 0x3FF) + (n & 0x3FF);
Q -= ((Q*0x5556) >> 16)*3; // Divide Q by three and then multiply by 3 to get remainder
// Q will be zero if and only if the original number was a multiple of three


在某些处理器上可能更快的替代方法是:

if (n<0) n=-n;
int q = (int)((n*0x55555556L) >> 32);
n=n-q-q-q;    
return n==0;


是否是否表现良好取决于JITter是否认识到long乘法的两个参数实际上都是int

评论


\ $ \ begingroup \ $
0x5555或0x5556?
\ $ \ endgroup \ $
– ErikE
2014年6月3日,0:49

\ $ \ begingroup \ $
@ErikE:0x5556。对于整数1-65535,(n * 0x5555 >> 16)将计算(n-1)/ 3。使用0x5556避免使用-1项。
\ $ \ endgroup \ $
–超级猫
2014年6月3日4:44

\ $ \ begingroup \ $
第二个代码段类似于gcc在C ++中对i%3的处理。我不知道算术方面的JIT通常有多好,但是如果我的JIT不知道这个常数除数的技巧,并在适当的地方使用它,我会有些失望,所以我不必:-)
\ $ \ endgroup \ $
–史蒂夫·杰索普(Steve Jessop)
2014年6月5日9:18



\ $ \ begingroup \ $
@SteveJessop JIT知道该怎么做。巧合的是,我在stackoverflow.com/a/23451322/3182664中对此进行了剖析
\ $ \ endgroup \ $
– Marco13
2014年11月17日9:15

\ $ \ begingroup \ $
@ Marco13:太酷了。生成的代码肯定很像我上面的第二个建议。
\ $ \ endgroup \ $
–超级猫
2014年11月17日下午16:31

#3 楼

我猜想,现代CPU上一个非常快速的解决方案如下所示:

int mask = 0x2AAAAAAA; // special handling for the sign bit
int diff = 2 * Integer.bitCount(x & mask) + Integer.bitCount(x & ~mask);


不要被背后的复杂代码所迷惑,它是一种内在的特性,可以转换为

现在我们有一个0到48之间的数字,如果x可被3整除,那么它可以被3整除。最快的方法可能是将一个表包装到单个long中。 />
 return (TABLE << diff) < 0;


编辑

我略微更改了上面的代码以使说明更简单。

余数取模3对于1和10来说是相同的,因为10%3 = 1,这意味着对于十进制,十进制数字的位置无关紧要。这导致了众所周知的数字求和规则。

在二进制中,它不能像2%3!= 1那样工作。但是4%3 = 1,所以我们可以重写( 8 * a + 4 * b + 2 * c + d)%3 =(2 *(a + c)+(b + d))%3。由于每个数字都是零或一,所以我们需要计数那些在适当的位置。由于最高位的权重为-2 ** 31,因此需要特殊处理(请参见掩码)。

EDIT

我错过了一个微不足道的优化。这是之后的完整代码:

private static final long TABLE = 0x9249249249240000L;

int diff = Integer.bitCount(x & 0x2AAAAAAA) + Integer.bitCount(x);
return (TABLE << diff) < 0;


编辑

固定(需要long表)并针对所有int进行了详尽的测试。

BENCHMARK

我的基准测试产生了截然不同的结果。由于对Java进行基准测试非常困难,因此我更相信使用caliper可以得到更好的结果。很遗憾,但是获胜者是超级猫,而不是我。

我想,我必须稍微调整基准,因为很难找到可以在我的两行中进行调整的任何东西。

可能的获胜者

我想,James_pic的这个较晚的答案可能是最快的。

评论


\ $ \ begingroup \ $
一定要对为什么这样做有效进行一些解释。 (即为什么对二进制数的3进行除数的数字总和测试与十进制的11相同)。还有为什么两个补码中的偏移量为1意味着将符号位视为偶数索引位会产生正确的结果。最后,如果任何差异高于当前除数3,则您的return语句似乎返回true-我认为您只需捕获最少的一点即可。
\ $ \ endgroup \ $
–塔米尔
2014年6月3日,7:22

\ $ \ begingroup \ $
@Taemyr我没听清楚你的最后一句话。左移将相关位移到第31位,以便我可以测试符号。
\ $ \ endgroup \ $
– maaartinus
2014年6月3日15:36

\ $ \ begingroup \ $
TABLE看起来是八进制表示法实际上有用的少数几个地方之一。我认为八进制表示法应该使用0q之类的东西,而不是一个裸露的前导0,但是01111111111111111111111L是一个可爱的常数,不是吗?
\ $ \ endgroup \ $
–超级猫
14年3月3日在18:15

\ $ \ begingroup \ $
@supercat我猜是这样。但是我讨厌八进制,当前的表示法完全是对大脑的损害(我建议使用0o前缀)。我必须承认,我生成了常量(尝试3的倍数),并且从未看过该值。但是从逻辑上讲,每三位被置位一次。
\ $ \ endgroup \ $
– maaartinus
2014年6月3日19:26

\ $ \ begingroup \ $
@maaartinus我记得当我第一次编程时,尝试输入数字019时出错,这使我发疯了几个小时,然后才意识到前导0意味着什么。我想这比在代码中的某处使用012要好:-)
\ $ \ endgroup \ $
– k_g
15年3月30日在5:37

#4 楼

鉴于应该以多快的速度进行暴力测试,例如:

for (int i=-100000; i<100000; i++)
    assertEquals(DivThreeEfficiently.isMultipleOfThree(i), i%3==0);


这使意图更加明显,减少了代码量,与您使用的测试代码相比,仍然涵盖了更多的案例。覆盖一些明显的转折/极限情况(例如,最小/最大值)可能仍然会很好,但是这似乎简化了“晴天”测试,同时大大扩展了覆盖范围。

评论


\ $ \ begingroup \ $
“晴天”测试?
\ $ \ endgroup \ $
–西蒙·福斯伯格
2014年6月2日在22:17

\ $ \ begingroup \ $
@SimonAndréForsberg:“晴天”只是在测试基本功能,而不花任何精力去寻找边界情况,而这种情况特别有可能破坏被测单元。
\ $ \ endgroup \ $
–杰里·科芬(Jerry Coffin)
2014年6月2日22:22

#5 楼

由于您的原始实现无法为21、42、69、81、84、87和93(从0到100!)产生正确的结果,因此我想提供一种更“常见”的方式来处理问题除以三。这是许多人面对大量数字且想要确定该数字是否可被三除的行为:


如果一个数字可被三除其所有数字的总和可被三整除


这种方法的主要好处是对于人的大脑更容易理解。可以将其转换为一种不错的递归方法:

public static boolean isDivisibleByThree(int n) {
    int sum = 0;
    int abs = Math.abs(n);
    if (abs < 10) {
        return abs == 3 || abs == 6 || abs == 9 || abs == 0;
    }
    while (n != 0) {
        sum += n % 10;
        n = n / 10;
    }
    return isDivisibleByThree(sum);
}


诚然,这不如您当前的方法快(根据我的基准测试,它慢了大约两倍) ),但确实会产生正确的结果。 (即使对于Integer.MIN_VALUE的极值也是如此)。

尽管最后,对于程序员来说,没有什么比i % 3 == 0
可读

评论


\ $ \ begingroup \ $
与其查看以10为底的数字,不如查看以16为底的数字会更有效率。
\ $ \ endgroup \ $
–超级猫
2014年6月2日23:57

\ $ \ begingroup \ $
@SimonAndréForsberg:也许-或者也许按位,并且-例如,如果有人写if(x&1 == 1),他们可能打算if((x&1)== 1),其中原始含义是等同于:if(x&(1 == 1))。
\ $ \ endgroup \ $
–杰里·科芬(Jerry Coffin)
14年6月3日,0:09

\ $ \ begingroup \ $
反复除以10很慢。在以4为底数和以16为底数的语句中,也应用了“如果一个数字的所有位数之和可以被3整除,则数字可以被3整除”这样的语句,并且将这些底数中的数字求和要容易得多。请看我的答案
\ $ \ endgroup \ $
–phuclv
2014年6月5日14:14

\ $ \ begingroup \ $
我不知道当这个问题的数量级比琐碎的n%3 == 0一个慢几个数量级时,没有人能赞成这个问题。我的意思是,为log_10(n)idiv更改单个idiv会更有效吗?
\ $ \ endgroup \ $
– Voo
2014年6月5日18:50

\ $ \ begingroup \ $
我几乎发现了一个错误。您不处理Integer.MIN_VALUE(绝对值为负),但是很幸运。不到十,答案是正确的。您可能想添加评论,因为这种情况经常被漏掉。
\ $ \ endgroup \ $
– maaartinus
17年8月6日在11:00

#6 楼

我运行了一些基准测试。我提供了@JavaDeveloper的原始代码进行比较,即使它会产生错误的结果。

import java.util.Arrays;

public class DivisibilityBenchmark {
    static abstract class DivPredicate {
        private final String name;

        public DivPredicate(String name) {
            this.name = name;
        }

        public abstract boolean isMultiple(int n);
    }

    private static final long MAAARTINUS_TABLE = 0x9249249249240000L;
    // = 0b1001001001001001001001001001001001001001001001000000000000000000L;

    private static final DivPredicate[] SOLUTIONS = new DivPredicate[] {
        new DivPredicate("canonical") {
            public boolean isMultiple(int n) {
                return n % 3 == 0;
            }
        },

        new DivPredicate("200_success") {
            public boolean isMultiple(int n) {
                return n % 3 == 0;
            }
        },

        new DivPredicate("JavaDeveloper") {
            public boolean isMultiple(int n) {
                if(n < 0) n = -n;

                int evenCtr = 0;
                int oddCtr = 0;

                while (n != 0) {
                    if ((n & 1) == 1) { 
                        oddCtr++;
                    } 
                    n = n>>1;
                    if ((n & 1) == 1) {
                        evenCtr++;
                    } 
                    n = n>>1;
                }

                return evenCtr == oddCtr;
            }
        },

        new DivPredicate("Simon André Forsberg") {
            public boolean isMultiple(int n) {
                int sum = 0;
                int abs = Math.abs(n);
                if (abs < 10) {
                    return abs == 3 || abs == 6 || abs == 9 || abs == 0;
                }
                while (n != 0) {
                    sum += n % 10;
                    n = n / 10;
                }
                return isMultiple(sum);
            }
        },

        new DivPredicate("supercat Rev 2") {
            public boolean isMultiple(int n) {
                int q = (n >> 20) + ((n >> 10) & 0x3ff) + (n & 0x3ff);
                return q == ((q * 0x5556) >> 16) * 3;
            }
        },

        new DivPredicate("supercat Rev 4") {
            public boolean isMultiple(int n) {
                if (n < 0) n = -n;
                int q = (int)((n * 0x55555556L) >> 32);
                n = n - q - q - q;
                return n == 0;
            }
        },

        new DivPredicate("maartinus Rev 4") {
            public boolean isMultiple(int x) {
                int diff = Integer.bitCount(x & 0x2AAAAAAA) + Integer.bitCount(x);
                return (MAAARTINUS_TABLE << diff) < 0;
            }
        },

        new DivPredicate("200_success (bis)") {
            public boolean isMultiple(int n) {
                return n % 3 == 0;
            }
        }
    };

    public static boolean[] run(DivPredicate pred, int lower, int upper) {
        boolean[] results = new boolean[upper - lower];
        for (int i = lower; i < upper; i++) {
            results[i - lower] = pred.isMultiple(i);
        }
        return results;
    }

    public static int perf(DivPredicate pred, int lower, int upper) {
        int count = 0;
        for (int i = lower; i < upper; i++) {
            if (pred.isMultiple(i)) {   // Count results to ensure that the
                count++;                // work is not optimized away
            }
        }
        return count;
    }

    public static void main(String[] args) {
        // Warm-up and correctness tests
        boolean[] expected = null;
        for (DivPredicate pred : SOLUTIONS) {
            boolean[] actual = run(pred, -5000, 5000);
            if (expected == null) {
                expected = actual;
            } else if (!Arrays.equals(expected, actual)) {
                System.out.println("Mismatch " + pred.name);
            }
        }

        // Performance measurement
        final int LOWER = -0x00FFFFFF, UPPER = 0x00FFFFFF;
        for (DivPredicate pred : SOLUTIONS) {
            long start = System.currentTimeMillis();
            perf(pred, LOWER, UPPER);
            long finish = System.currentTimeMillis();
            System.out.printf("Time %s: %d ms\n", pred.name, finish - start);
        }
        for (int i = SOLUTIONS.length - 1; i >= 0; i--) {
            DivPredicate pred = SOLUTIONS[i];
            long start = System.currentTimeMillis();
            perf(pred, LOWER, UPPER);
            long finish = System.currentTimeMillis();
            System.out.printf("Time %s: %d ms\n", pred.name, finish - start);
        }
    }
}


结果

Java(TM)SE运行时环境(在OS X 10.9,Intel Core i7-2600 Sandy Bridge 3.4 GHz上构建1.7.0_45-b18):


JavaDeveloper不匹配
时间规范:62毫秒(←非常不公平的优势!)
时间200_成功:79毫秒(仍然不可靠!)
时间JavaDeveloper:995毫秒
时间西蒙·安德烈·福斯伯格(SimonAndréForsberg):1146毫秒
时间超级猫Rev 2:151毫秒
时间超级猫Rev 4:146 ms
时间maartinus Rev 4:142 ms
时间200_成功(bis):146 ms(←可能是一个相当的比较)
时间200_成功(bis):146 ms
时间maartinus修订版4:140毫秒
时间超级猫修订版4:146毫秒
时间超级猫修订版2:153毫秒
时间西蒙·安德烈·福斯伯格:1168毫秒
时间JavaDeveloper :1029毫秒
时间200_成功:143毫秒
时间规范:144毫秒

讨论

正如@maaartinus指出的,前三个解决方案由于单态内联缓存或其他JIT优化,在此基准测试中获得不公平的优势。为了说明基准测试是如何存在缺陷的,我已经将相同的解决方案包含了三遍(分别为“ canonical”,“ 200_success”和“ 200_success(bis)”),然后以相反的顺序执行测试以取得良好的效果。我将以使用适当的基准测试工具(例如Caliper)作为课程。

基于修改后的度量,我得出的结论是@ 200_success,@ supercat和@maaartinus的解决方案是性能几乎相同。

评论


\ $ \ begingroup \ $
您基本上是在测量内存和虚拟调度开销。查看我的更新。
\ $ \ endgroup \ $
– maaartinus
2014年6月3日19:53

\ $ \ begingroup \ $
@maaartinus在第3版中,我从时序中删除了阵列分配,在不显着改变解决方案的相对排名的情况下,缩短了约30 ms的时间。我认为您和supercat的解决方案具有相同的性能。 (我无法让Caliper运行-他们的API不稳定,网站上的示例与他们发布的caliper-1.0-beta-1-all.jar不兼容,您的DivisibilityBenchmark也没有。不是您的错,我看不出有什么理由不信任我的结果,尽管我可以理解它们是否因CPU类型而异。
\ $ \ endgroup \ $
– 200_success
2014年6月3日在21:12

\ $ \ begingroup \ $
@ 200_success根据您的基准,第一个解决方案可以节省很多时间,因为在运行时,呼叫站点是单态的。您需要启动一个新的JVM,Caliper会这样做。自一年多以来,他们的API相当稳定,问题在于他们的文档更加稳定(已过时)或类似的东西。我正在使用git中的版本。如果您喜欢Maven,请尝试JMH。
\ $ \ endgroup \ $
– maaartinus
2014年6月4日,0:26

\ $ \ begingroup \ $
除非没有关系,否则您不应该信任任何基准。请注意,您报告的“ supercat Rev 4”比您的报告慢了两倍多,而Caliper报告说它要快得多。我并不是说,您必须做任何事情,仅出于记录:微基准测试很难,尤其是在Java中。
\ $ \ endgroup \ $
– maaartinus
14年6月4日,0:35

\ $ \ begingroup \ $
@maaartinus Rev 4证实了您的观察结果,即基准测试为前几个测试用例带来了不公平的优势。谢谢您的启发。我学到了一些有关JIT行为和基准测试的新知识。我想奖励您一个赏金,但首先,我必须等到对这个问题的密切投票期满。
\ $ \ endgroup \ $
– 200_success
2014年6月4日在5:22

#7 楼

本着过早优化的精神,这是一种基于模块化算术的快速方法:

import java.math.BigInteger;

private static final int MULTIPLIER =
  BigInteger.valueOf(3).modInverse(BigInteger.valueOf(2).pow(32)).intValue();
private static final int LOWER_LIMIT = Integer.MIN_VALUE / 3;
private static final int UPPER_LIMIT = Integer.MAX_VALUE / 3;

public static boolean divideFast(int i) {
  int j = i * MULTIPLIER;
  return LOWER_LIMIT <= j && j <= UPPER_LIMIT;
}


这个想法是,在Java中,乘法是232模(因为它在溢出时自动换行) ),并且在模算术中,您可以乘以除法-通常比除法快。每个奇数都有一个“倒数”-您可以将其乘以1的数字-因为奇数是232的互质数。3的倒数是MULTIPLIER。如果i被3整除在常规整数算术中,则i * MULTIPLIER将为i / 3。如果i不能被3整除,那么3 * (i * MULTIPLIER) === i模232仍然是正确的,只是因为乘法在溢出时自动换行。

因此,被3整除的数字恰好是我们可以相乘的数字i * MULTIPLIER减3,无溢出。也就是说,i * MULTIPLIERInteger.MIN_VALUE / 3INTEGER.MAX_VALUE / 3之间的数字。在我的基准中,此方法的计算结果为每张支票0.72ns,而下一个最接近的@maaartinus则为每操作数0.93ns。

评论


\ $ \ begingroup \ $
难以置信,但正确(已详尽验证)!
\ $ \ endgroup \ $
– maaartinus
2015年4月19日,1:13

#8 楼

您确定分裂是您的瓶颈吗?在大多数情况下,智能编译器/ JITer会将其优化为一个乘法,并且不会花费很多程序周期。

如果确实使您的程序变慢,则可以在此处使用解决方案。它使用以下事实:如果以N为底的数字的所有数字的总和除以N-1的除数M,则该数字可被M整除。原始版本处理无符号数字,因此我们需要做一些改动

private static int reduce(int i) {
    if (i >= 6)
        return reduce((i >> 2) + (i & 0x03));
    else
        return i; // Done.
}

public static boolean isMultipleOfThree(int i) {
    if (i < 0) i = -i;
    // Sum of digits in base 4
    i = (i >> 16) + (i & 0xFFFF);
    i = (i >> 8) + (i & 0xFF);
    i = (i >> 4) + (i & 0xF);
    i = reduce(i);
    return i == 0 || i == 3;
}


您也可以在base 16中执行此操作,但是它将需要更多的比较,并且可能不会比此版本快。