【问题标题】:Implementing a logical shift right实现逻辑右移
【发布时间】:2019-06-25 23:12:33
【问题描述】:

所以我正在从事 nand2tetris 项目,我想在软件级别实现右移逻辑,因为硬件不支持它。

我知道右移逻辑是除以二。所以我第一次实现它时会计算在值变为 0 或负数之前我能够从初始值中减去 2 的次数。如果数字为负数,则类似。

但是,我发现它不起作用的情况。我想右移-27139。那么移位后的二进制值是19199。应该是19198。所以我正在寻找一种新的方式来实现移位。

我可以使用and 值、or 值、addsubtract,这就是我可以使用的全部内容。

OSFTW

这是我在 Hack 实现的汇编语言中的代码:

//============================================================
// SLR: Shift Logical Right (by 1)
//============================================================

(SLR)

@SLR.1              // Load value
D=M
@SLR.2
M=0                 // Clear variables
@SLR_POSITIVE_LOOP      // If value is positive, go to the positive loop
D;JGT


(SLR_NEGATIVE_LOOP)
@SLR.1              // Add 2 to value, since it's negative
M=M+1
M=M+1
@SLR.1              // If initial value was negative and current value is positive or zero, jump out of loop
D=M
@SLR_NEG
D; JGE
@SLR.2              // If value is negative, add 1 to SLR.2 (we're dividing)
M=M+1
@SLR.1              // If value is less than 0, restart the loop
D=M
@SLR_NEGATIVE_LOOP
D; JLT

(SLR_NEG)
@SLR.2
D=M
D=!D                // Invert the result
D=D+1               // Add 1 to finish converting
@32767              // And it with 0111111111111111 to clear the sign bit
D=D&A
@SLR.2
M=D                 // Set value
@SLR_END                        // Jump to end of loop
0;JMP

(SLR_POSITIVE_LOOP)
@SLR.1              // Subtract 2 from value
M=M-1
M=M-1
@SLR.1              // If initial value was positive and current value is negative or zero, jump out of loop
D=M
@SLR_END
D; JLE
@SLR.2              // If value is still positive, add 1 to SLR.2 (we're dividing)
M=M+1
@SLR.1              // If value is greater than 0, restart the loop
D=M
@SLR_POSITIVE_LOOP
D; JGT


(SLR_END)               // Loop is over. Set value of SLR.1 to that of SLR.2, for return purposes

@SLR.2                                  // Place result in correct place
D=M
@SLR.1
M=D

@SLR.0             // Return to calling function
A = M
0; JMP

【问题讨论】:

  • 如果你把一个奇数减去 2 那么它总是奇数,所以结果。另外,反复减去也不是一个好的解决方案,因为它会导致程序循环数千次​​pan>
  • 重复地将有符号数减去 2 就像算术移位一样,因为符号位总是复制到结果 (-27139 - 2 = -27137),结果向 -Inf 舍入。这就是你看到结果的原因。要将零移入,请执行无符号减法 ((unsigned)-27139 - 2 =38397 - 2 = 38395)
  • 右移然后修复 msbit 以匹配下一个 msbit。少得多的工作快得多。如果这是一个无符号数,那么您根本不必这样做,只需向右移动,即除以 2。
  • 类似问题here
  • 如果是正数,就是除以二。

标签: assembly bit-manipulation bit-shift nand2tetris


【解决方案1】:

一个数字向左或向右的逻辑移位等于将 N-n 位从 N 位的一个字复制到另一个字。因此:

unsigned int a = 0x1321;
unsigned int b = 0;
unsigned int mask1 = 1;
unsigned int mask2 = 1 << n;  // use repeated addition for left shift...
int i;
for (i = 0; i < N-n; i++) {
    if (a & mask2)
        b|= mask1;
    mask1 += mask1;
    mask2 += mask2;
}

交换 mask1 和 mask2 将实现左移(仅限按位运算)。

【讨论】:

  • 我在这里假设 N 是值中的位数?这听起来也很愚蠢,但是我如何将一个被解释为有符号整数的数字视为无符号整数?就像未签名一样对其进行操作?
  • N 通常为 32,即机器字中的位数。逻辑运算符(甚至 2 的补码中的加/减)在有符号整数和无符号整数之间没有任何区别。所以是的:去做吧!
【解决方案2】:

根据 Nand2Tetris 课程的性质,我试图在这个答案中走一条线,给出了 Hack 汇编编码技术和通用算法的示例,但将最终代码留作练习。

Hack ALU 没有任何将位 N 与位 N-1 连接的数据路径。这意味着必须使用左旋转来实现右移和旋转。 (注:左 = 最高有效位,右 = 最低有效位)

左移很容易,因为它只是乘以 2,它本身就是自加法。例如:

// left-shift variable someVar 1 bit

@someVar     // A = address of someVar
D = M        // D = Memory[A]
M = M + D    // Memory[A] = Memory[A] * 2

左旋转有点困难。您需要保留最左侧位的副本,并在执行乘法后将其移至最右侧位。但是请注意,您在 D 寄存器中拥有“someVar”原始值的副本,您可以根据其值进行测试和跳转——如果 D 的最左边位为 1,则 D 将小于零。此外,请注意,将“someVar”乘以 2 后,最右边的位将始终为 0,这样可以轻松设置而无需更改任何其他位。

一旦你向左旋转,向右旋转就很简单了;如果你想左移 N 位,你可以右移 16-N 位。请注意,这假定 N 在 0-15 范围内。

右移是最复杂的操作。在这种情况下,您需要先进行右旋转,然后生成一个掩码,将高 N 位设置为零。你用面具右旋的结果。

生成掩码的基本方法是从-1开始(所有位都设置)并将其添加到自身N次;这使得掩码的最右边的 N 位为 0。然后将这 16-N 次左循环以将所有 0 位移动到最左边的 N 位。

但是,这是很多循环,而在使用汇编语言编程时,节省循环就是它的全部意义所在。您可以使用几种技术。

第一个是使用地址算术来实现case语句的等价物。对于 16 个可能的循环值中的每一个,您需要将一个 16 位掩码值加载到 D 寄存器中,然后跳转到案例的末尾。您必须小心,因为您只能使用 @instruction 加载 15 位常量,但您可以在 6 条指令中执行加载和无条件跳转(4 条用于加载完整的 16 位常量,2 条用于跳转)。

因此,如果您有 16 个从位置 (CASE) 开始,您只需将 N 乘以 6,将其添加到 @CASE,然后跳转到该位置。在考虑如何乘以 6 时,请记住 HACK 指令集非常可爱的特性之一;您可以将 ALU 运算的结果同时存储在多个寄存器中。

然而,最有效的解决方案是预先计算掩码表。在程序初始化过程中,生成 16 位掩码并将它们存储在内存中的某个固定位置,然后只需将 N 加到表的开头地址即可读取掩码。

由于HACK CPU除了获取指令外无法访问程序ROM,因此无法将表存储在ROM中,您必须使用每个表条目的几条指令将值加载到D寄存器中然后保存它进入RAM。我最终编写了一个简单的 python 脚本,生成初始化表的代码。

【讨论】:

    【解决方案3】:

    如果您将要移位的值视为无符号,它会变得更容易,因为逻辑右移无论如何都不会保留符号。然后你只需重复减去 2,直到结果小于 2,此时减去的次数就是你的商(即右移值)。

    C 中的示例实现:

    int lsr(int valueToShift)
    {
        int shifted = 0;
        uint16_t u = valueToShift;
    
        while (u >= 2) {
            u -= 2;
            shifted++;
        }
    
        return shifted;
    }
    

    【讨论】:

      【解决方案4】:

      您应该使用二进制或十六进制,因为使用十进制很难想象数字表示。

      如果您有算术移位但没有逻辑移位,最明显的解决方案是清除最高位(如果它是负数)

      int LogicalRightShift(int x, int shift)
      {
          return (x >> shift) & ((1U << (CHAR_BIT*sizeof(x) - shift)) - 1);
          // or
          return (x >> shift) & (~((~0) << (CHAR_BIT*sizeof(x) - shift)));
      }
      

      如果你没有算术右移,你可以逐位复制它

      int LogicalRightShift(int x, int shift)
      {
          // assuming int size is 32
          int bits[] = {  0x1,        0x2,        0x4,        0x8,        0x10,       0x20,       0x40,       0x80,
                          0x100,      0x200,      0x400,      0x800,      0x1000,     0x2000,     0x4000,     0x8000,
                          0x10000,    0x20000,    0x40000,    0x80000,    0x100000,   0x200000,   0x400000,   0x800000,
                          0x1000000,  0x2000000,  0x4000000,  0x8000000,  0x10000000, 0x20000000, 0x40000000, 0x80000000
          }
          int res = 0;
          for (int i = 31; i >= shift; i++)
          {
              if (x & bits[i])
                  res |= bits[i - shift];
          }
          return res;
      }
      

      另一种方法是重复除以 2。或者您可以将 2 的幂存储在查找表中并除以该幂。这样,如果您没有硬件除法器,它可能比上面的位复制方法慢,但仍然比像您的方法那样必须减去数千次要快得多。要将 -27139 (38397) 向右移动 1 位,您需要从数字 9599 中减去 2,如果数字更大或需要移动不同数量的位,则甚至更多

      【讨论】:

      • 据我了解,不支持移位操作(&lt;&lt;&gt;&gt;),这就是为什么 OP 想要使用他可用的指令来实现它。跨度>
      • @Michael 据我所知不支持逻辑移位操作,所以我改用算术移位
      • 不幸的是,我没有右移算术。我也必须实现它。
      【解决方案5】:

      一种更快的方法可能是使用加法。举个粗略的例子:

      uin32_t LSR(uint32_t value, int count) {
          uint32_t result = 0;
          uint32_t temp;
      
          while(count < 32) {
              temp = value + value;
              if(temp < value) {                // Did the addition overflow?
                  result = result + result + 1;
              } else {
                  result = result + result;
              }
              value = temp;
              count++;
          }
          return result;
      }
      

      基本思想是将一个 64 位无符号整数左移“32 - count”次,然后返回最高 32 位。

      在汇编中,上面的大部分代码(分支等)有望变成add value, value 然后add_with_carry result, result

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-02-12
        • 1970-01-01
        • 2017-11-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多