【问题标题】:Is it possible to test if a number is either even or '1', using only bitwise operators?是否可以仅使用按位运算符来测试数字是偶数还是“1”?
【发布时间】:2020-02-20 14:44:08
【问题描述】:

是否可以只使用位运算符,即不使用逻辑/比较/关系运算符?

这基本上等同于以下内容。

(a & 1 == 0) || (a == 1)

我接受在许多语言中可能需要进行最终的相等比较以将结果转换为真/假值,但我没有在问题中考虑这一点。 一个接受输入位数组并为偶数返回与“1”相同的输出位数组的答案基本上就是我所追求的。要么是那个,要么是不可能的原因。

【问题讨论】:

  • 不是答案,但我猜第一个测试 (a & 1) 返回数字是否为奇数。
  • 我的错误,更新例子是正确的
  • 请指定语言
  • 与语言无关的问题,目的是更好地了解按位运算的潜力
  • 对于特定数值表现不同的任何事情都不可能仅使用按位运算符来实现,因为与该特定值相等是所有位加在一起的属性,但按位运算符处理每个位的位置隔离。您可以在汇编语言中执行此操作,您可以根据整个结果设置标志位,而无需进行任何显式的非按位测试(XOR 值与 1,结果是零标志或值的低位)。

标签: bit-manipulation bitwise-operators


【解决方案1】:

((a & 1) == 0) | (a == 1) 有效。无需做任何复杂的技巧

【讨论】:

  • (a & 1 == 0) 对我来说似乎是一个逻辑运算符
  • @RobertKock 谁说==logical operatorJavascriptC/C++Java 和其他语言都不同意你的观点。在 Java 和 C# 中,如果没有关系运算符,您甚至无法获得逻辑表达式。 if (a & 1)if (a && 1) 在这些语言中无法编译
  • == 的优先级高于&,你需要更多的括号。
  • @SergeyGrinev 谢谢。我直接从 OP 的代码中复制了
  • @phuclv 嗯 C 不同意? en.wikipedia.org/wiki/…。好的。 OP 显然在这里需要位运算符,所以我们为什么要假装他不是?
【解决方案2】:

虽然我根本不建议这样做,但我能够在 C 中实现它,但只需将a 设为unsigned char。它也可以与其他人一起使用,但这会延长条件。

#include <stdio.h>

int main() {
    for (unsigned char a = 0; a < 255; a++) {
        if ((~a & 1) | (1 >> (a & 0x0E | a >> 4)))
            printf ("%d is even or 1\n", a);
        else
            printf("%d is odd\n", a);
    }
    return 0;
}

如果a 是偶数,(~a &amp; 1) 将产生1。当a10 时,(a &amp; 0x0E | a &gt;&gt; 4) 将产生0。因此在这些情况下,(1 &gt;&gt; (a &amp; 0x0E | a &gt;&gt; 4))) 将是(1 &gt;&gt; 0),即1,否则移位值在115 之间,因此表达式为0

我最初的方法是(1 &gt;&gt; (a &gt;&gt; 1)),但很快意识到这会导致移位溢出,因为移位量超过了可能的最大值。不仅是发出警告,而且实际上错误地将每个 n * 64 + 1 值识别为偶数。因此,更复杂的方法是通过 OR -ing a 高 4 位与其低 4 位来限制移位量小于 64,但将 LSB 设置为 0。这个结果数字是 &lt;= 15 并且只有 0a10 时才需要。

【讨论】:

    【解决方案3】:

    我不完全确定我理解您的要求,但在 Intel64/AMD64/x86_64 指令集中,很容易执行一系列按位运算,这些运算将返回一个值和一个允许建议比较。

    假设输入值在 %rax 寄存器中。

    MOV %rax, %r8     # replicate input value into %r8
    MOV %rax, %r9     # replicate input value into %r9
    AND $0x1, %r8     # AND 0x1 with the contents of %r8 and put result in %r8
    XOR $0x1, %r9     # XOR 0x1 with the contents of %r9 and put result in %r9
    OR  %r8, %r9      # OR %r8 with %r9 and put result in %r9
    

    如果输入为偶数,AND运算返回0,否则返回1。 如果输入为 0x1,则 XOR 运算将返回 0,否则返回非零。 如果 %r8 和 %r9 都为零,OR 操作将返回 0,否则返回非零。

    每个位运算都会设置不同的条件标志。这里相关的称为“ZF”,如果运算结果为0,则设置为1。

    最终操作设置的 ZF 标志可用于控制条件分支。如果 ZF=1(即条件为真)则使用“JZ”(零时跳转)进行分支,如果条件不成立,则使用“JNZ”(非零时跳转)进行分支。

    您可以将 %r9 中的结果保存到内存中,稍后在与零 (true) 或非零 (false) 进行比较时使用它。

    您也可以在不使用条件分支指令的情况下对结果进行操作——相同的条件代码可用于控制条件移动指令。如果 ZF=1,CMOVE 指令会将内存或寄存器中的输入操作数复制到输出寄存器,或者当 ZF=0 时,CMOVNE 将执行复制。例如,如果条件为真,您可以将值“1”从源寄存器复制到目标寄存器,然后将目标寄存器值累加到累加器中。然后,累加器将包含比较返回“True”的案例计数。

    【讨论】:

    • 也可以使用输出值创建 SSE/AVX/AVX2 样式的掩码(仅 SIMD 寄存器的每个字段中的最高有效位用作掩码控制位)或 AVX512 样式(掩码保存在一组单独的掩码寄存器中)。需要大量位移、合并和取反,但不需要明确的比较指令。
    【解决方案4】:

    一种适用于任何固定(二次幂)位宽的方法是使用以下函数,当所有位都设置时返回 1,否则返回 0:

    uint32_t allOnes(uint32_t a) {
        a &= a >> 1;
        a &= a >> 2;
        a &= a >> 4;
        a &= a >> 8;
        a &= a >> 16;
        return a;
    }
    

    然后我们可以进行如下检查:

    uint32_t f1(uint32_t a) {
        return (~a & 1) | allOnes(a ^ ~uint32_t(1));
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-18
      • 1970-01-01
      • 1970-01-01
      • 2010-12-29
      • 2012-07-20
      • 2011-11-05
      相关资源
      最近更新 更多