如果已经有一些类似的问题,请通过评论告诉我。
当我们通常尝试区分奇数和偶数时,我们可以尝试以下代码, 在C++。
int main() {
int n=10;
for(n; n>0; n--){
if(n%2==0) std::cout<< "even" << 'n';
if(n%2==1) std::cout<< "odd" << 'n';
}
}
我敢肯定,超过 99% 的本科生,甚至是专业人士,都会使用条件作为"如果(n%2==0)......否则"来区分奇数和偶数。
但是,当数字范围变得足够大时,在我看来"如果(n%2==0)......否则"的方法可能效率很低。让我来解释一下原因。
int main() {
int n=100000;
for(n; n>0; n--){
if(n%2==0) std::cout<< "even" << 'n';
if(n%2==1) std::cout<< "odd" << 'n';
}
}
当整数"n"很小时,将每个正整数除以小于它并不是什么大问题。
但是,当它变大时,难道没有比将它们分开更有效的方法吗?
我们人类通常不会计算模 2 来知道"10^1000 + 1"是奇数还是偶数。 它与"10^1000 + 2"、"10^1000 + 3"等相同。我们可以通过查看整数的最后一位数字来知道答案。
我不是CS方面的专家,所以即使我不确定这些信息,我听说机器对二进制数比人类更友好。如果是这样,计算机难道不能通过查看输入的最后一位数字(无论它们是0还是1)来更快地区分奇数和偶数吗?
如果对此有一些直接的答案,我相信许多中级数值算法可以从答案中受益。期待有人的帮助。
谢谢。
50000000%2 和 5%2 的性能时间真的一样吗?
这是假设编译器"查看数字"并"知道"其值有多大。分裂的情况并非如此。编译器看到一个int
并对该int
执行一些操作。不同的值只是在该int
中设置的不同位,这些位始终具有执行除法时需要考虑的相同字节数。
另一方面,%2
是一个如此常见的操作,编译器确实"知道在哪里查找":最后一点。
请考虑以下两个函数:
bool is_odd1(int n) { return (n%2);}
bool is_odd2(int n) { return (n&1);}
当n
是奇数时,两者都返回true
。
is_odd1
使用定义为除法后的余数的%
。但是,仅仅因为它是这样定义的并不意味着它必须像这样实现。编译器将发出根据定义生成结果的代码,并且该代码可以预期非常有效。
is_odd2
只考虑最低的n
位来检查它是否被设置。
启用优化后,gcc 会为两者生成完全相同的输出:
is_odd1(int):
mov eax, edi
and eax, 1
ret
is_odd2(int):
mov eax, edi
and eax, 1
ret
这两个函数都不做任何事情,只是检查是否设置了最低位n
。
现场演示。
结论:不要担心这种微优化。如果可能,它们在编译器中实现。而是编写代码以提高清晰度和可读性。
但是,不要大规模引入低效率。例如,如果您像这样编写代码,则无需依赖编译器优化:
for(; n>0; n-=2){
std::cout<< "even" << 'n';
std::cout<< "odd" << 'n';
}
不过,无论如何,这都不是一个很好的例子,因为将某些内容打印到控制台比检查是否设置了单个位要昂贵得多。
如果你看一个用十进制写的数字,你可以很快知道它是奇数还是偶数:
- 如果它以 0、2、4、6 或 8 结尾,则它是偶数;
- 如果它以 1、3、5、7 或 9 结尾,那么它是奇数。
计算机上的数字以二进制形式存储。二进制几乎与十进制相同,只是它使用以 2 为基数而不是以 10 为基数,因此仅使用位 0 或 1 而不是数字 0 到 9。
要检查用二进制写的数字是奇数还是偶数,请查看其最后一位:
- 如果它以 0 结尾,则它是偶数;
- 如果它以 1 结尾,那么它是奇数。
当您编写n % 2 == 0
C++时,编译器会优化代码,并且根本不执行除法。相反,它只是查看最后一位n
并检查它是 0 还是 1。
永远不要通过测试余数来测试奇偶校验(余数和除法是一个非常缓慢的操作)。通过使用位掩码(n & 1 == 0
)测试最低有效位就足够了。
[顺便说一下,智能优化器将自动通过掩码代替a % 2
测试。另一种选择是使用移位:a == (a >> 1) << 1
检测到偶数。
如果你的数字是 BigNum,那么检查表示中低阶词的奇偶校验就足够了。