【问题标题】:Why if (n & -n) == n then n is a power of 2?为什么如果 (n & -n) == n 那么 n 是 2 的幂?
【发布时间】:2011-11-16 08:29:02
【问题描述】:

Line 294 of java.util.Random source

if ((n & -n) == n) // i.e., n is a power of 2
    // rest of the code

这是为什么?

【问题讨论】:

  • 新标签应该是一个提示。 :)
  • 跟随位。顺便说一句,它也将零视为 2 的幂。公式(n & (n - 1)) == 0 也有效(它会删除最低位,如果没有剩余位,则首先设置最多 1 个位)。
  • 是的,我对使用这样的代码表示认罪。你可以玩很多这样的技巧,只要你知道你正在处理 2 的补码算术并保持对各种转换和溢出陷阱的认识。为了获得额外的功劳,请弄清楚如何四舍五入到下一个更高的 2 次方,或者可能是 2 的次方 - 1 - 在某些方面需要以惊人的频率完成的事情。
  • 等等,现在 everyone 是从 java.util.Random 源读取的吗? (几个月前我读到了,从那时起我记得关于它的一些问题。)

标签: java logic bit-manipulation


【解决方案1】:

因为在 2 的补码中,-n~n+1

如果n 是 2 的幂,那么它只设置了一个位。所以~n 设置了除那个之外的所有位。加 1,然后再次设置特殊位,确保 n & (that thing) 等于 n

反之亦然,因为该 Java 源代码中的前一行已排除了 0 和负数。如果n 设置了多个位,则其中一个是最高位。 +1不会设置此位,因为有一个较低的清除位可以“吸收”它:

 n: 00001001000
~n: 11110110111
-n: 11110111000  // the first 0 bit "absorbed" the +1
        ^
        |
        (n & -n) fails to equal n at this bit.

【讨论】:

    【解决方案2】:

    描述并不完全准确,因为(0 & -0) == 0 但 0 不是 2 的幂。更好的说法是

    ((n & -n) == n) 当 n 是 2 的幂,或 2 的幂的负数,或零时。

    如果 n 是 2 的幂,则二进制中的 n 是单个 1 后跟零。 二进制补码中的 -n 是倒数 + 1,因此位排列整齐

     n      0000100...000
    -n      1111100...000
     n & -n 0000100...000
    

    要了解为什么会这样,请将二进制补码视为逆 + 1,-n == ~n + 1

    n          0000100...000
    inverse n  1111011...111
                         + 1
    two's comp 1111100...000
    

    因为在加一得到二的补码时,你一直携带一。

    如果 n 不是 2 的幂†,则结果会丢失一点,因为由于进位,二进制补码不会设置最高位。

    † - 或 0 或 2 的幂的负数...如顶部所述。

    【讨论】:

    • 还有一个技巧可以隔离最低有效的 1 位。
    • (0 & -0) == 0the immediately preceeding statementif (n <= 0) throw ...。这意味着在那个时候被测试的数字永远不会是 0(或负数)。
    • @Michael,非常正确。我正在回答标题中的问题,而不是批评我没有读过的Random.java
    • @Mike,我意识到了;但是,我不认为代码中注释中的语句(包含在问题中并且是标题中问题的基础)在没有在之前建立的先决条件的上下文中看到时完全独立在代码中给它。如果您查看此处发布的问题,我们甚至不知道n 是什么类型;我没有检查过这个假设,但不知何故怀疑 double 的行为方式是否相同。
    • @Michael,我们可以很好地限制n 的类型,因为这个问题有“java”标签。 & 没有在 Java 中的 doublefloat 上定义。它仅在整数类型和布尔值上定义。由于- 没有为布尔值定义,我们可以安全地推断n 是整数。
    【解决方案3】:

    您需要以位图的形式查看这些值,以了解为什么会这样:

    1 & 1 = 1
    1 & 0 = 0
    0 & 1 = 0
    0 & 0 = 0
    

    所以只有两个字段都为 1 时才会出现 1。

    现在 -n 进行 2 的补码。它将所有 0 更改为 1 并添加 1。

    7 = 00000111
    -1 = NEG(7) + 1 = 11111000 + 1 = 11111001
    

    然而

    8 = 00001000
    -8 = 11110111 + 1 = 11111000 
    
    00001000  (8)
    11111000  (-8)
    --------- &
    00001000 = 8.
    

    只有 2 的幂,(n & -n) 才会是 n。
    这是因为 2 的幂表示为一长串零中的单个设置位。 否定将产生完全相反的结果,一个单一的零 (在 1 曾经所在的位置) 在 1 的海洋中。添加 1 会将较低的移动到零所在的空间。
    而按位和 (&) 将再次过滤掉 1。

    【讨论】:

      【解决方案4】:

      在二进制补码表示中,2 的幂的独特之处在于它们由所有 0 位组成,除了第 k 位,其中 n = 2^k:

      base 2    base 10
      000001 =  1 
      000010 =  2
      000100 =  4
           ...
      

      要在二进制补码中获得负值,您可以翻转所有位并加一。对于 2 的幂,这意味着您会在左侧得到一堆 1,包括正值中的 1 位,然后在右侧得到一堆 0:

      n   base 2  ~n      ~n+1 (-n)   n&-n  
      1   000001  111110  111111      000001
      2   000010  111101  111110      000010
      4   000100  111011  111100      000100
      8   001000  110111  111000      001000
      

      您可以很容易地看到第 2 列和第 4 列的结果将与第 2 列相同。

      如果您查看此图表中缺少的其他值,您就会明白为什么这仅适用于 2 的幂:

      n   base 2  ~n      ~n+1 (-n)   n&-n  
      1   000001  111110  111111      000001
      2   000010  111101  111110      000010
      3   000011  111100  111101      000001
      4   000100  111011  111100      000100
      5   000101  111010  111011      000001
      6   000110  111001  111010      000010
      7   000111  111000  111001      000001
      8   001000  110111  111000      001000
      

      n&-n 将(对于 n > 0)仅设置 1 位,并且该位将是 n 中的最低有效设置位。对于所有为 2 的幂的数字,最低有效设置位是唯一设置位。对于所有其他数字,有多个位集,其中只有最低有效位将在结果中设置。

      【讨论】:

        【解决方案5】:

        它是 2 的幂和它们的 two's complement 的属性。

        例如取8:

        8  = 0b00001000
        
        -8 = 0b11111000
        

        计算二进制补码:

        Starting:  0b00001000
        Flip bits: 0b11110111  (one's complement)
        Add one:   0b11111000  
        
        AND 8    : 0b00001000
        

        对于 2 的幂,只会设置 一位,因此添加将导致设置 2n 的第 nth 位(那个一直携带到第 nth 位)。然后当你AND这两个号码时,你会得到原来的。

        对于不是 2 的幂的数字,其他位不会被翻转,因此 AND 不会产生原始数字。

        【讨论】:

          【解决方案6】:

          简单地说,如果 n 是 2 的幂,则意味着只有一位设置为 1,其他位设置为 0:

          00000...00001 = 2 ^ 0
          00000...00010 = 2 ^ 1
          00000...00100 = 2 ^ 2
          00000...01000 = 2 ^ 3
          00000...10000 = 2 ^ 4
          
          and so on ...
          

          并且因为-nn 的2 的补码(这意味着唯一为1 的位保持原样,并且该位左侧的位为1,这实际上无关紧要,因为如果两位中的一位为零,则 AND 运算符& 的结果将为 0):

          000000...000010000...00000 <<< n
          &
          111111...111110000...00000 <<< -n
          --------------------------
          000000...000010000...00000 <<< n
          

          【讨论】:

            【解决方案7】:

            通过示例显示:

            8 进制 = 0x000008

            -8 十六进制 = 0xFFFFF8

            8 & -8 = 0x000008

            【讨论】:

              猜你喜欢
              • 2012-04-13
              • 1970-01-01
              • 1970-01-01
              • 2011-01-29
              • 1970-01-01
              • 1970-01-01
              • 2020-03-22
              • 1970-01-01
              • 2015-05-04
              相关资源
              最近更新 更多