繁体   English   中英

以零开头的右移

[英]Right shift with zeros at the beginning

我正在尝试做一种左移,它会在开头添加零而不是一。 例如,如果我左移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 %d\n", 0xff << 3, 0xff << 3);
  printf("%.4X %d\n", 0xff >> 3, 0xff >> 3);

}

输出:

07F8 2040
001F 31

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


来源:ISO 9899:2011 6.5.7。


问题更新后编辑

int number = ~0; 假设为二进制补码,则为您提供一个等效于 -1 的负数。

number = number << 4; 调用未定义的行为,因为您左移了一个负数。 程序正确地实现了未定义的行为,因为它要么做某事,要么什么都不做。 它可能会打印 fffffff0 或者它可能会打印一个粉红色的大象,或者它可能会格式化硬盘驱动器。

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

在 99% 的现实世界案例中,对有符号数使用按位运算符是没有意义的。 因此,始终确保您使用的是无符号数,并且 C/C++ 中没有任何危险的隐式转换规则将它们转换为有符号数(有关危险转换的更多信息,请参阅“整数提升规则”和“通常的算术转换” ”,关于 SO 的大量信息)。

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

6.5.7 按位移位运算符

K&R 中对移位运算符的描述表明,按 long 计数移位应该强制左操作数在移位之前加宽为 long。 C89 委员会认可的一种更直观的做法是,班次计数的类型与结果的类型无关。

C89 中的安静变化

移位 long 计数不再强制移位的操作数为 long。 C89 委员会确认了 K&R 授予的实现自由,不要求签名右移操作进行符号扩展,因为这样的要求可能会减慢快速代码的速度,并且符号扩展移位的用处很小。 (将负二进制补码整数在算术上右移一位与除以 2 不同!)

如果您明确地将 0xff 移动到您预期的那样

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

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


所以,一般情况下:

您需要使用无符号整数

(unsigned type)0xff

右移作为除以 2(如果我理解正确的话,四舍五入)。

因此,当您将 1 作为第一位时,您的值为负值,除法后再次为负值

您所说的两种右移称为Logical ShiftArithmetic Shift C 和 C++ 对无符号整数使用逻辑移位,大多数编译器将对有符号整数使用算术移位,但这不是由标准保证的,这意味着右移负有符号 int 的值是实现定义的。

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

要解释您的真实代码,您只需要 Lundin 在评论中给出的 C 标准中引用的 C++ 版本:

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

未定义的行为。 [expr.shift] 说

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

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

实现定义的结果,在这种情况下,您的实现给了您一个算术移位:

E1 >> E2 的值是 E1 右移的 E2 位位置。 如果 E1 具有无符号类型或如果 E1 具有有符号类型和非负值,则结果的值是 E1/2 E2的商的整数部分。 如果 E1 具有有符号类型和负值,则结果值是实现定义的

您应该使用无符号类型:

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

输出:

0x0fffffff

在这里添加我的 5 美分价值......我面临与 this.lau 完全相同的问题! 我对此做了一些敷衍的研究,这些是我的结果:

typedef unsigned int Uint;
#define U31 0x7FFFFFFF
#define U32 0xFFFFFFFF

printf ("U31 right shifted: 0x%08x\n", (U31 >> 30));
printf ("U32 right shifted: 0x%08x\n", (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%08x\n", (ST00 << 30));
printf ("ST01 left shifted: 0x%08x\n", (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 为零,我知道这不是适当的算术移位,但会完成工作

即使有几个答案,我也会添加一个与整数中的位数及其符号无关的版本。

假设我们有一个n位的整数,并且我们希望将前s位清零。

// this could be any type of integer including long long
// e.g. typedef unsigned int MyInt;
typedef long long MyInt;

size_t n = sizeof(MyInt);
size_t s = 5; // say we want the first five bits set to zero

// get the most significant bit set to 1
#define MSB1 (((MyInt)1) << (n - 1))  // MSB1 = 10000000....00

// flip the bits
#define MSB0 (~MSB1)                  // MSB0 = 01111111....11

// Now shift right. The bits will fill with zero regardless of
// whether MyInt is signed or unsigned, because we have a leading 0,
// and the integer is not negative regardless of the signed-ness.
// but we will have to shift right 1 time less.
#define MASK (MSB0 >> (s - 1))        // MASK = 00000111....11

或者,

#define MASK ((~(((MyInt)1) << (n - 1))) >> (s - 1))
// Still, MASK = 00000111....11

如果你想先用零右移任意整数,那么你可以,

// Shift right once
MyInt x = 0xff..ff0;
MyInt result = x >> 1; // 0xff..ff8;

// Bitwise and with `MSB0`, that is `0111...11`, thereby flipping the MSB to 0.
result &= MSB0; // 0x7f..ff8

Shift right the rest of times.
result >>= (s - 1) // 0x0f..fff

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM