右移32位整数

5

在Clojure中,位移操作似乎总是返回64位的long结果,即使使用32位的int参数。这对于bit-shift-left来说并不是一个重大问题:

user=> (format "%08x" (unchecked-int (bit-shift-left (unchecked-int 0x12345678) 4)))
"23456780"
user=> (format "%08x" (unchecked-int (bit-shift-left (unchecked-int 0xf2345678) 4)))
"23456780"

然而,对于负数的无符号右移来说,这将成为一个问题:

user=> (format "%08x" (unchecked-int (unsigned-bit-shift-right (unchecked-int 0xf2345678) 4)))
"ff234567"

当然,正确的答案是0f234567

在Clojure中实现32位无符号右移的最有效方法是什么?


(format "%08x" (-> 0xf2345678 (unsigned-bit-shift-right 4)))通过整个过程使用长整型,可以给你想要的答案。你不能只关注低32位,用长整型做你正在做的事情吗? - gfredericks
一种可能性是:在移位之前明确地屏蔽您的 int,以便它不会变成负的 64 位值:(unsigned-bit-shift-right (bit-and (unchecked-int 0xf2345678) 0xffffffff) 4)。(实际上,在这里您可能不需要 unchecked-int。) - matt
1个回答

5

要实现这个目标,可以调用 int clojure.lang.Numbers.unsignedShiftRightInt(int, int) 方法,它使用 >>> 操作符对 int 类型的参数进行位移运算,并返回一个 int 类型的值。该方法目前没有在任何地方公开作为一个函数,但它具有内在的实现(相当于 Java 中的 >>> 操作符),您可以直接调用它,也可以将其包装在自己的可内联的函数中:

(defn unsigned-bit-shift-right-int
  {:inline (fn [x n] `(clojure.lang.Numbers/unsignedShiftRightInt ~x ~n))}
  [x n]
  (clojure.lang.Numbers/unsignedShiftRightInt x n))

无论是否内联,此函数将返回正确的值,但通常情况下您会希望进行内联。另外,最好确保参数实际上是原始的int,以便可以启用内部函数。
以下是在Clojure 1.8中,当它被内联的两种可能情况下它编译成的代码(未被内联的情况下是常规函数调用,没有什么可看的):

具有原始参数的内联:

滥用count只是为了阐明这一点,请注意iushr指令。
  1. Clojure deftype:

    (deftype Foo [^int x ^int y]
      clojure.lang.Counted
      (count [this]
        (unsigned-bit-shift-right-int x y)))
    
  2. Bytecode:

    // Method descriptor #61 ()I
    // Stack: 2, Locals: 1
    public int count();
       0  aload_0 [this]
       1  getfield user.Foo.x : int [19]
       4  aload_0 [this]
       5  getfield user.Foo.y : int [21]
       8  iushr
       9  ireturn
        Line numbers:
          [pc: 0, line: 1]
          [pc: 8, line: 4]
        Local variable table:
          [pc: 0, pc: 9] local: this index: 0 type: user.Foo
    

使用非基本类型参数进行内联:

请注意invokestatic clojure.lang.Numbers.unsignedShiftRight… 指令。

  1. Clojure expression:

    #(format "%08x"
       (clojure.lang.Numbers/unsignedShiftRightInt (unchecked-int 0xf2345678) 4))
    
  2. Bytecode:

    // Method descriptor #11 ()Ljava/lang/Object;
    // Stack: 5, Locals: 1
    public java.lang.Object invoke();
       0  getstatic user$eval16141$fn__16142.const__0 : clojure.lang.Var [15]
       3  invokevirtual clojure.lang.Var.getRawRoot() : java.lang.Object [20]
       6  checkcast clojure.lang.IFn [22]
       9  ldc <String "%08x"> [24]
      11  ldc2_w <Long 4063516280> [25]
      14  l2i
      15  ldc2_w <Long 4> [27]
      18  invokestatic clojure.lang.RT.intCast(long) : int [34]
      21  invokestatic clojure.lang.Numbers.unsignedShiftRightInt(int, int) : int [40]
      24  invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [46]
      27  invokeinterface clojure.lang.IFn.invoke(java.lang.Object, java.lang.Object) : java.lang.Object [49] [nargs: 3]
      32  areturn
        Line numbers:
          [pc: 0, line: 1]
          [pc: 6, line: 1]
          [pc: 14, line: 1]
          [pc: 21, line: 1]
          [pc: 27, line: 1]
        Local variable table:
          [pc: 0, pc: 32] local: this index: 0 type: java.lang.Object
    

看起来不错,但是#(unsigned-bit-shift-right-int % 32)似乎出于某种原因是一个恒等函数,而我本来期望它是常数0函数。 - Cactus
1
JVM的无符号右移指令只使用右操作数的5个最低位。因此,x >>> 32等同于x >>> 0,而System.out.println(1 >>> 32)在Java中打印1。这在《Java®语言规范:Java SE 8版》第15.19节移位运算符的第563页中有说明。 - Michał Marczyk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接