【问题标题】:bitwise division rounding to nearest zero按位除法舍入到最接近的零
【发布时间】:2013-04-01 18:21:15
【问题描述】:

我想执行一个有符号整数按位除以 2 的幂。但是,我遇到了几个问题。我只是想知道是否有人可以帮助我。

首先,我尝试单独使用位移:

int result = number >> n;

但是,当我尝试除以负数时遇到问题。 (它总是用更大的数字四舍五入。例如:-9/4=-3 而不是 -2。所以,我在互联网上查看了这个问题,最终我得到了这个解决方案:

int result = (number + (1<<n)-1) >> n;

但是,当我尝试 11/4 = 3 而不是 2 时

有什么建议吗?我只能用! ~ & ^ | + >(不允许循环或 if/switch)

【问题讨论】:

  • 我不清楚。 1)您是使用/ 来获得结果还是 2)使用&gt;&gt; 来执行等价除法?
  • @Koushik,他说他只能使用这些运算符:! ~ & ^ | + >。此外,没有循环或 if 条件/开关。
  • @Koushik 我正在调用函数 div(11,2) 来执行 11/4
  • @AnishRam 我只是想确保他不会遗漏/。现在我清楚了。

标签: c math bit-manipulation


【解决方案1】:

也许这对你有帮助。

1) 如果您使用 / 运算符,那么标准规定 (ISO/IEC TR3 in 6.5.5 Multiplicative operators) / 运算符的结果 是第一个操作数除以第二个操作数的商.

2) 如果您使用 &gt;&gt; 标准说明 (ISO/IEC TR3 in 6.5.7) &gt;&gt; 的结果,其中 LHS 操作数是有符号类型且为负,则结果值为 implementation defined.

所以/ 会给你想要的结果。

&gt;&gt; 上的带符号 && 负数取决于您的编译器。

【讨论】:

    【解决方案2】:

    以下方法不好,因为它依赖于:

    • 负整数的右移是算术移位(可能不是这种情况)
    • 有符号整数采用 2 的补码表示(极少数情况可能不是这样)
    • 没有任何填充位的整数(现在在现代 CPU 上,您不会找到填充位,尽管标准允许它们存在)

    由于有符号整数溢出,它可能会导致某些红利(例如INT_MIN)出现未定义的行为。

    因此,它不是便携式的,也不能保证始终工作。您已收到警告。

    #include <stdio.h>
    #include <limits.h>
    
    int DivByShifting1(int n, unsigned shift)
    {
      int sgn = n >> ((sizeof(int) * CHAR_BIT) - 1);
      return ((((n + sgn) ^ sgn) >> shift) + sgn) ^ sgn;
    }
    
    int main(void)
    {
      int n, s;
      for (n = -10; n <= 10; n++)
        for (s = 0; s <= 4; s++)
          printf("%d / %d = %d\n", n, 1 << s, DivByShifting1(n, s));
      return 0;
    }
    

    输出(ideone):

    -10 / 1 = -10
    -10 / 2 = -5
    -10 / 4 = -2
    -10 / 8 = -1
    -10 / 16 = 0
    -9 / 1 = -9
    -9 / 2 = -4
    -9 / 4 = -2
    -9 / 8 = -1
    -9 / 16 = 0
    -8 / 1 = -8
    -8 / 2 = -4
    -8 / 4 = -2
    -8 / 8 = -1
    -8 / 16 = 0
    -7 / 1 = -7
    -7 / 2 = -3
    -7 / 4 = -1
    -7 / 8 = 0
    -7 / 16 = 0
    -6 / 1 = -6
    -6 / 2 = -3
    -6 / 4 = -1
    -6 / 8 = 0
    -6 / 16 = 0
    -5 / 1 = -5
    -5 / 2 = -2
    -5 / 4 = -1
    -5 / 8 = 0
    -5 / 16 = 0
    -4 / 1 = -4
    -4 / 2 = -2
    -4 / 4 = -1
    -4 / 8 = 0
    -4 / 16 = 0
    -3 / 1 = -3
    -3 / 2 = -1
    -3 / 4 = 0
    -3 / 8 = 0
    -3 / 16 = 0
    -2 / 1 = -2
    -2 / 2 = -1
    -2 / 4 = 0
    -2 / 8 = 0
    -2 / 16 = 0
    -1 / 1 = -1
    -1 / 2 = 0
    -1 / 4 = 0
    -1 / 8 = 0
    -1 / 16 = 0
    0 / 1 = 0
    0 / 2 = 0
    0 / 4 = 0
    0 / 8 = 0
    0 / 16 = 0
    1 / 1 = 1
    1 / 2 = 0
    1 / 4 = 0
    1 / 8 = 0
    1 / 16 = 0
    2 / 1 = 2
    2 / 2 = 1
    2 / 4 = 0
    2 / 8 = 0
    2 / 16 = 0
    3 / 1 = 3
    3 / 2 = 1
    3 / 4 = 0
    3 / 8 = 0
    3 / 16 = 0
    4 / 1 = 4
    4 / 2 = 2
    4 / 4 = 1
    4 / 8 = 0
    4 / 16 = 0
    5 / 1 = 5
    5 / 2 = 2
    5 / 4 = 1
    5 / 8 = 0
    5 / 16 = 0
    6 / 1 = 6
    6 / 2 = 3
    6 / 4 = 1
    6 / 8 = 0
    6 / 16 = 0
    7 / 1 = 7
    7 / 2 = 3
    7 / 4 = 1
    7 / 8 = 0
    7 / 16 = 0
    8 / 1 = 8
    8 / 2 = 4
    8 / 4 = 2
    8 / 8 = 1
    8 / 16 = 0
    9 / 1 = 9
    9 / 2 = 4
    9 / 4 = 2
    9 / 8 = 1
    9 / 16 = 0
    10 / 1 = 10
    10 / 2 = 5
    10 / 4 = 2
    10 / 8 = 1
    10 / 16 = 0
    

    请注意,((sizeof(int) * CHAR_BIT) - 1) 是编译时常量,因此可以允许使用 *-

    另一个版本,非常相似,但不需要负整数的右移是算术移位,并且没有有符号整数溢出(2 的补码和填充位仍然是限制,但在今天几乎不存在练习):

    #include <stdio.h>
    #include <limits.h>
    #include <string.h>
    
    int DivByShifting2(int n, unsigned shift)
    {
      unsigned un = n;
      unsigned sgn = 1 + ~(un >> ((sizeof(int) * CHAR_BIT) - 1));
      un = ((((un + sgn) ^ sgn) >> shift) + sgn) ^ sgn;
      memcpy(&n, &un, sizeof n);
      return n;
    }
    
    int main(void)
    {
      int n, s;
      for (n = -10; n <= 10; n++)
        for (s = 0; s <= 4; s++)
          printf("%d / %d = %d\n", n, 1 << s, DivByShifting2(n, s));
      return 0;
    }
    

    输出(ideone):

    -10 / 1 = -10
    -10 / 2 = -5
    -10 / 4 = -2
    -10 / 8 = -1
    -10 / 16 = 0
    -9 / 1 = -9
    -9 / 2 = -4
    -9 / 4 = -2
    -9 / 8 = -1
    -9 / 16 = 0
    -8 / 1 = -8
    -8 / 2 = -4
    -8 / 4 = -2
    -8 / 8 = -1
    -8 / 16 = 0
    -7 / 1 = -7
    -7 / 2 = -3
    -7 / 4 = -1
    -7 / 8 = 0
    -7 / 16 = 0
    -6 / 1 = -6
    -6 / 2 = -3
    -6 / 4 = -1
    -6 / 8 = 0
    -6 / 16 = 0
    -5 / 1 = -5
    -5 / 2 = -2
    -5 / 4 = -1
    -5 / 8 = 0
    -5 / 16 = 0
    -4 / 1 = -4
    -4 / 2 = -2
    -4 / 4 = -1
    -4 / 8 = 0
    -4 / 16 = 0
    -3 / 1 = -3
    -3 / 2 = -1
    -3 / 4 = 0
    -3 / 8 = 0
    -3 / 16 = 0
    -2 / 1 = -2
    -2 / 2 = -1
    -2 / 4 = 0
    -2 / 8 = 0
    -2 / 16 = 0
    -1 / 1 = -1
    -1 / 2 = 0
    -1 / 4 = 0
    -1 / 8 = 0
    -1 / 16 = 0
    0 / 1 = 0
    0 / 2 = 0
    0 / 4 = 0
    0 / 8 = 0
    0 / 16 = 0
    1 / 1 = 1
    1 / 2 = 0
    1 / 4 = 0
    1 / 8 = 0
    1 / 16 = 0
    2 / 1 = 2
    2 / 2 = 1
    2 / 4 = 0
    2 / 8 = 0
    2 / 16 = 0
    3 / 1 = 3
    3 / 2 = 1
    3 / 4 = 0
    3 / 8 = 0
    3 / 16 = 0
    4 / 1 = 4
    4 / 2 = 2
    4 / 4 = 1
    4 / 8 = 0
    4 / 16 = 0
    5 / 1 = 5
    5 / 2 = 2
    5 / 4 = 1
    5 / 8 = 0
    5 / 16 = 0
    6 / 1 = 6
    6 / 2 = 3
    6 / 4 = 1
    6 / 8 = 0
    6 / 16 = 0
    7 / 1 = 7
    7 / 2 = 3
    7 / 4 = 1
    7 / 8 = 0
    7 / 16 = 0
    8 / 1 = 8
    8 / 2 = 4
    8 / 4 = 2
    8 / 8 = 1
    8 / 16 = 0
    9 / 1 = 9
    9 / 2 = 4
    9 / 4 = 2
    9 / 8 = 1
    9 / 16 = 0
    10 / 1 = 10
    10 / 2 = 5
    10 / 4 = 2
    10 / 8 = 1
    10 / 16 = 0
    

    @R.. 正确地提醒,从 signed intunsigned int 的转换可以通过添加 0u(无符号 0)来完成。

    而且他还提醒un可以直接返回,而不是memcpy()n。转换应该是实现定义的,但在 C 的 2 的补码实现中,逐位复制实际上总是如此。

    【讨论】:

    • 您能解释一下n &gt;&gt; ((sizeof(int) * CHAR_BIT) - 1) 在您的DivByShifting1 函数中的作用吗?
    • @AnishRam Arithmetic shift。它为 n >= 0 提供 0,为 n
    • 就个人而言,我会使用实现定义的从unsignedint 的转换(发生在return 语句中)而不是memcpy...
    【解决方案3】:

    只需使用/ 运算符:

    int result = number / (1 << n);
    

    任何体面的编译器都会将其编译为最佳位移,并修正负面结果的“四舍五入”。

    【讨论】:

    • 练习的重点是避免在源代码中明确划分。
    • 那么我不认为它可以移植,因为&gt;&gt; 是针对负操作数的实现定义的。你至少需要一种方法来判断操作数是否为负数...
    • 否则会使用idiv;哪个更有效
    • 您可以将有符号整数转换为无符号整数,并使用最高有效位作为符号位值。我认为,它仍然是可行的,虽然要长得多。
    • 是的,刚刚添加了一个不依赖于&gt;&gt;的实现定义行为的版本。
    【解决方案4】:

    int div(int a){return a/4;}的反汇编来看:

    leal 3(%rdi), %eax
    testl %edi, %edi
    cmovns %edi, %eax
    sarl $2, %eax
    

    当且仅当操作数为负时,必须通过(1&lt;&lt;n)-1 调整操作数。

    一个无条件的方法是使用sgn(a)*(abs(a)&gt;&gt;n);两者都可以根据实现定义的行为使用无分支 bitmagic 来实现。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-22
      • 2014-12-14
      • 1970-01-01
      相关资源
      最近更新 更多