3

在处理 C# 移位运算符时,我遇到了左移运算符的意外行为。
然后我尝试了这个简单的功能:

for (int i = 5; i >= -10; i--) {
    int s = 0x10 << i;
    Debug.WriteLine(i.ToString().PadLeft(3) + "   " + s.ToString("x8"));
}

有了这个结果:

  5   00000200
  4   00000100
  3   00000080
  2   00000040
  1   00000020
  0   00000010
 -1   00000000     -> 00000008 expected
 -2   00000000     -> 00000004 expected
 -3   00000000     -> 00000002 expected
 -4   00000000     -> 00000001 expected
 -5   80000000
 -6   40000000
 -7   20000000
 -8   10000000
 -9   08000000
-10   04000000

直到今天,我都期望<<运算符可以处理第二个操作数的负值。
MSDN没有说明使用第二个操作数的负值时的行为。但 MSDN 表示,运营商只使用低 5 位 (0-31),这应该适合负值。

我也尝试了longvalues: long s = 0x10L << i;,但结果相同。

那么这里会发生什么?

编辑
如您的回答中所述,负值表示不是原因。
对于所有情况,我得到了相同的错误结果:

0x10<<-3                    = 0x00000000    (wrong!)
0x10<<(int)(0xfffffffd)     = 0x00000000    (wrong!)
0x10<<(0x0000001d           = 0x00000000    (wrong!)
                   expected = 0x00000002

编辑#2
这两个之一应该是真的:

1)移位运算符是一个真正的移位运算符,所以结果应该是:
1a)0x10 << -3 = 00000002
1b)0x10 << -6 = 00000000

2) 移位运算符是一个旋转运算符,所以结果应该是:
2a) 0x10 << -3 = 00000002 (same as 1a)
2b)0x10 << -6 = 40000000

但是显示的结果既不适合 1) 也不适合 2) !!!

4

3 回答 3

3

负数是两个补数,所以-1 == 0xFFFFFFFF, 和0xFFFFFFFF & 31 == 31, -2 == 0xFFFFFFFE,0xFFFFFFFE & 31 == 30等等。

-10 == 0xFFFFFFF6, and 0xFFFFFFF6 & 31 == 22, in fact:

(0x10 << 22) == 04000000

要显示的一些代码:

const int num = 0x10;
int maxShift = 31;

for (int i = 5; i >= -10; i--)
{
    int numShifted = num << i;
    uint ui = (uint)i;
    int uiWithMaxShift = (int)(ui & maxShift);
    int numShifted2 = num << uiWithMaxShift;

    Console.WriteLine("{0,3}: {1,8:x} {2,2} {3,8:x} {4,8:x} {5}",
        i,
        ui,
        uiWithMaxShift,
        numShifted,
        numShifted2,
        numShifted == numShifted2);
}

long它是一样的,但现在不是& 31你有& 63. -1 == 63,-2 == 62-10 == 54

一些示例代码:

const long num = 0x10;
int maxShift = 63;

for (int i = 5; i >= -10; i--)
{
    long numShifted = num << i;
    uint ui = (uint)i;
    int uiWithMaxShift = (int)(ui & maxShift);
    long numShifted2 = num << uiWithMaxShift;

    Console.WriteLine("{0,3}: {1,8:x} {2,2} {3,16:x} {4,16:x} {5}", 
        i, 
        ui, 
        uiWithMaxShift, 
        numShifted, 
        numShifted2, 
        numShifted == numShifted2);
}

只是要清楚:

(int x) << y == (int x) << (int)(((uint)y) & 31)
(long x) << y == (long x) << (int)(((uint)y) & 63)

并不是

(int x) << y == (int x) << (Math.Abs(y) & 63)
(long x) << y == (long x) << (Math.Abs(y) & 63)

而你认为“应该”“如果它是”“它会很漂亮”“必须是”ecc 是无关紧要的。虽然 1 和 0 是“近”(它们的二进制表示在不同位数上的“距离”为 1),但 0 和 -1 是“远”(它们的二进制表示在位数上的“距离”为 32 或 64不同的位)

你认为你应该得到这个:

-1   00000000     -> 00000008 expected
-2   00000000     -> 00000004 expected
-3   00000000     -> 00000002 expected
-4   00000000     -> 00000001 expected

但实际上你没有看到的是你得到了这个:

-1   (00000008) 00000000
-2   (00000004) 00000000
-3   (00000002) 00000000
-4   (00000001) 00000000
-5   (00000000) 80000000 <-- To show that "symmetry" and "order" still exist
-6   (00000000) 40000000 <-- To show that "symmetry" and "order" still exist

其中的部分(...)是在“左边”的部分int并且不存在。

于 2013-08-08T12:57:30.340 回答
3

它与负数的表示有关-1对应于全 1,因此它的五个最低有效位总和为 31,左移0x1031 位得到全零(根据文档,已设置的高位被丢弃)。

越来越大的负数对应于移位 30、29 等位。已设置的位0x10位于从零开始的位置 4,因此为了不丢弃它,移位最多必须为 31 - 4 = 27 位,这发生在i == -5.

如果您尝试例如,您可以很容易地看到发生了什么Console.WriteLine((-1).ToString("x8"))

ffffffff

更新:当第一个操作数是 a 时,long您会看到类似的行为,因为现在从第二个操作数开始计算六个最低有效位:0x10L << -1左移 63 位等。

于 2013-08-08T12:58:28.290 回答
3

左移运算符不会将负的第二个操作数视为右移。它只会使用该值的低五位并使用它进行左移。

-1值( )的低五位0xFFFFFFFF将是31( 0x0000001F),因此第一个操作数0x10向左移动 31 步,只在结果的最高有效位中留下最低有效位。

换句话说,0x10 << -10x10 << 31which will be相同0x800000000,但结果仅为 32 位,因此将被截断为0x00000000

当您使用 long 值时,将使用第二个操作数的六个最低有效位。该值-1变为 63,并且这些位仍然移出 long 的范围之外。

于 2013-08-08T13:00:41.537 回答