按位除法舍入到最接近的零 [英] bitwise division rounding to nearest zero

查看:54
本文介绍了按位除法舍入到最接近的零的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I want to perform an signed integer bitwise division by a power of 2. However, I encounter several problem. I just wonder if anyone can help me.

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

First, I try to use bit shifting alone:

int result = number >> n;

但是,当我尝试对负数进行除法时遇到问题.(它总是以更大的数字取整.例如:-9/4 = -3而不是-2.因此,我在互联网上看到了这个问题,最终得到了以下解决方案:

However, I got a problem when I try to divide a negative number. (it always round with a number with bigger magnitude. example: -9/4=-3 instead of -2. So, I look this problem in the internet, that end me up with this solution:

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

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

However, when I try 11/4 = 3 instead of 2

有什么建议吗?我只能用!〜&^ |+<<>>(无循环或是否允许/切换)

Any suggestions? I can only use ! ~ & ^ | + << >> (no loop or if/switch allowed)

推荐答案

以下方法是错误的,因为它依赖于:

The following method is bad because it relies on:

  • 负整数的右移是算术移位(可能不是这种情况)
  • 带符号的整数以2的补码表示(极少数情况可能并非如此)
  • 没有任何填充位的整数(尽管标准允许存在填充位,但在现代CPU上,如今这些日子都找不到)

由于有符号整数溢出,它可能导致某些除数(例如 INT_MIN )上的不确定行为.

And it may cause undefined behavior on some dividends (e.g. INT_MIN) due to signed integer overflow.

因此,它不是便携式的并且不能保证始终工作.您已被警告.

Therefore it isn't portable and isn't guaranteed to work always. You have been warned.

#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)是一个编译时常量,因此 * -可以被允许.

Note that ((sizeof(int) * CHAR_BIT) - 1) is a compile-time constant and therefore * and - can be allowed.

另一种版本,非常相似,但是不需要将负整数右移就可以算术移位,并且没有带符号整数溢出(2的补码和填充位仍然是局限性,但是在今天的版本中实际上已不存在练习):

Another version, which is very similar, but does not require right shifts of negative integers to be arithmetic shifts and is free of signed integer overflow (2's complement-ness and padding bits are still limitations, but virtually in-existent in today's practice):

#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 ..正确地提醒您,可以通过添加0u(无符号0)来完成从 signed int unsigned int 的转换.

@R.. rightfully reminds that the conversion from a signed int to an unsigned int can be done by adding 0u (unsigned 0).

他还提醒说,可以直接返回 un 而不是对 n 执行 memcpy().转换应该是实现定义的,但是在C的2的补码实现中,按位复制实际上总是这种情况.

And he also reminds that un can be returned directly instead of doing memcpy() to n. The conversion should be implementation-defined, but in 2's complement implementations of C, bit-for-bit copy is practically always the case.

这篇关于按位除法舍入到最接近的零的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆