右移,开始为0

Right shift with zeros at the beginning

本文关键字:开始 右移      更新时间:2023-10-16

我正在尝试做一种左移,在开始时添加0而不是1。例如,如果我左移0xff,我得到这个:

0xff << 3 = 11111000

然而,如果我右移它,我得到这个:

0xff >> 3 = 11111111

是否有任何操作我可以使用,以获得等效的左移?例如,我想要得到这个:

00011111

任何建议吗?

编辑

回答这些评论,下面是我使用的代码:

int number = ~0;
number = number << 4;   
std::cout << std::hex << number << std::endl;
number = ~0;
number = number >> 4;
std::cout << std::hex << number << std::endl;
输出:

fffffff0
ffffffff

因为它似乎在一般情况下应该工作,我感兴趣的是为什么这个特定的代码没有。任何想法?

C和二进制算法是这样工作的:

左移0xff << 3,得到二进制:00000000 11111111 << 3 = 00000111 11111000

右移0xff >> 3,得到二进制:00000000 11111111 >> 3 = 00000000 00011111

0xff是一个(有符号的)整型,其正数为255。由于它是正的,因此在C和c++中,转换它的结果是定义良好的行为。它不会做任何算术移位,也不会做任何类型或定义不清的行为。

#include <stdio.h>
int main()
{
  printf("%.4X %dn", 0xff << 3, 0xff << 3);
  printf("%.4X %dn", 0xff >> 3, 0xff >> 3);
}
输出:

07F8 2040
001F 31

所以你在你的程序中做了一些奇怪的事情,因为它没有像预期的那样工作。也许您正在使用字符变量或c++字符字面量。


来源:ISO 9899:2011 6.5.7.


问题更新后编辑

int number = ~0;给出一个等于-1的负数,假设2是补数。

number = number << 4;调用未定义的行为,因为你左移了一个负数。程序正确地实现了未定义行为,因为它要么做点什么,要么什么都不做。它可以打印fffffff0,也可以打印粉红色的大象,也可以格式化硬盘。

number = number >> 4;调用实现定义的行为。在这种情况下,编译器保留符号位。这被称为算术移位,算术右移的工作方式是用移位前的任何位值填充MSB。因此,如果你有一个负数,你将体验到程序"以1为单位移位"。

在99%的实际情况中,对有符号数使用位运算符是没有意义的。因此,始终确保您使用的是无符号数,并且C/c++中没有任何危险的隐式转换规则将它们转换为有符号数(有关危险转换的更多信息,请参阅"整数提升规则"answers"常用算术转换",其中有很多关于SO的好信息)。

EDIT 2,来自C99标准的基本原理文档V5.10的一些信息:

6.5.7位移位运算符

移位算子在K&R中的描述表明移位a长计数应该强制左操作数拓宽到Long之前被转移。这是一个更直观的做法,得到了C89的认可委员会的意见是,轮班计数的类型与结果的类型。

C89的安静变化

按长计数移位不再强制移位的操作数为长。89国委员会肯定了所给予的执行自由通过K&R不需要带符号的右移操作来签名扩展,因为这样的需求可能会减慢快速代码的速度符号扩展移位的用处不大。(转移负2的补数整数算术上右1位是和除以2不一样!)

如果你显式地移动0xff,它会像你期望的那样工作

cout << (0xff >> 3) << endl; // 31

只有当0xff的类型为带符号宽度8 (charsigned char在流行的平台上)时才可能。


一般情况下:

你需要使用unsigned int

(unsigned type)0xff

右移相当于除以2(如果我理解正确的话,是四舍五入)。

所以当你把1作为第一位时,你得到负的值,除法后它又变成负的

您所讨论的两种右移称为逻辑移位和算术移位。C和c++对无符号整数使用逻辑移位,大多数编译器将对有符号整数使用算术移位,但这并不能由标准保证,即负符号整型右移的值是实现定义的。

由于需要逻辑移位,因此需要切换到使用无符号整数。您可以通过将常量替换为0xffU来实现这一点。

要解释你的实际代码,你只需要Lundin在注释中给出的C标准引号的c++版本:

int number = ~0;
number = number << 4;

未定义的行为。[expr。转变]说

E1 <<E2为E1左移E2位的位置;空出的位是零填充的。如果E1是unsigned类型,则结果为E1 × 2E2,比最大值减模1可在结果类型中表示。否则,如果E1是有符号类型,则为和非负值,并且E1×2E2在结果中是可表示的类型,那么这就是结果值;否则,行为为未定义的 .

number = ~0;
number = number >> 4;

实现定义的结果,在这个例子中你的实现给了你一个算术移位:

E1>> E2的值是E1右移E2位。如果E1有无符号类型,或者E1具有有符号类型和非负值,结果的值是商的整部分E1/2 <一口> E2 吃饭。如果E1是有符号类型且为负值,则结果为value是实现定义的

应该使用unsigned类型:

unsigned int number = -1;
number = number >> 4;
std::cout << std::hex << number << std::endl;
输出:

0x0fffffff

在这里加上我的5美分价值…我正面临着和这个法律完全一样的问题!我对此做了一些粗略的研究,以下是我的结果:

typedef unsigned int Uint;
#define U31 0x7FFFFFFF
#define U32 0xFFFFFFFF
printf ("U31 right shifted: 0x%08xn", (U31 >> 30));
printf ("U32 right shifted: 0x%08xn", (U32 >> 30));
Output:
U31 right shifted: 0x00000001 (expected)
U32 right shifted: 0xffffffff (not expected)

看起来(在没有详细知识的情况下),Mac OS X v5.0.1的XCode中的C编译器将MSB保留为每次移位时被拉出的进位。

有些恼人的是,反过来是不正确的:-

#define ST00 0x00000001
#define ST01 0x00000002
printf ("ST00 left shifted: 0x%08xn", (ST00 << 30));
printf ("ST01 left shifted: 0x%08xn", (ST01 << 30));
Output:
ST00 left shifted: 0x40000000
ST01 left shifted: 0x80000000

我完全同意上面的人的说法,即操作数的符号与移位操作符的行为无关。

有谁能对C的Posix4实现的规范有任何启发吗?我觉得一个明确的答案可能就在那里。

在此期间,似乎唯一的解决方法是沿着以下行构造;-
#define CARD2UNIVERSE(c) (((c) == 32) ? 0xFFFFFFFF : (U31 >> (31 - (c))))

这是有效的-令人恼火但必要的。

如果你想让负数的第一个位在右移后为0我们可以做的是用INT_MIN对该负数进行异或,使其msb为零,我知道这不是合适的算术移位,但可以完成