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));
}
}
#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 * MULTIPLIER
在Integer.MIN_VALUE / 3
和INTEGER.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中执行此操作,但是它将需要更多的比较,并且可能不会比此版本快。
评论
我想n%3 == 0在作弊吗?如下所述,需要更多测试。 isMultipleOfThree(21)返回false。
下一个测试将是确认您的方法实际上是一种性能提升,这远不是显而易见的...
@ user3580294这完美地说明了早期CPU /内存资源优化的危险:在这个问题上花了多少小时的工程师时间(但是很有趣),这些问题多年来一直被其他人多次考虑? “恭喜!在接下来的十年中,您每个月为我们节省了多达47美分,而建造这一切的成本都远低于$ 18,271!” :)
是的,就像有人说的那样,除法确实是一条缓慢的指令。但是我强烈怀疑(n%3)== 0仍然会比包含循环和多个变量的更复杂的函数执行得快得多。在实践中,只要遇到这样的问题,我都会执行“%”。如果我的环境必须在非常有限的CPU上每秒执行数千次这样的操作,那么我将尝试其他选项并进行时序测试。但是,如果有比n%3更快的方法,我会感到惊讶。