Swift和Javascript不同的Bitwise计算结果

Swift and Javascript different Bitwise calculation results

我正在尝试将 Javascript 散列函数移植到 Swift:

在Javascript中: 68207947269 ^ 51 = -511529418

其中与 Swift 5

中的计算相同

68207947269 ^ 51 = 68207947318

它们有何不同?

编辑,添加了 JS 哈希函数

function hash(str) {
  var hash = 5381,
      i    = str.length;

  while(i) {
    hash = (hash * 33) ^ str.charCodeAt(--i);
  }

  /* JavaScript does bitwise operations (like XOR, above) on 32-bit signed
   * integers. Since we want the results to be always positive, convert the
   * signed int to an unsigned by doing an unsigned bitshift. */
  return hash >>> 0;
}

编辑 2,添加电流损坏 Swift:

extension String {
    subscript(i: Int) -> String {
        return String(self[index(startIndex, offsetBy: i)])
    }
}

extension Character {
    func unicodeScalarCodePoint() -> UInt32 {
        let characterString = String(self)
        let scalars = characterString.unicodeScalars
        return scalars[scalars.startIndex].value
    }
}

func hashString(_ string: String) -> UInt32 {
    var hash: Int32 = 5381
    var i = string.count - 1
    while(i >= 0) {
        let char = Int32(Character(string[i]).unicodeScalarCodePoint())
        // Crash here with error: `Swift runtime failure: arithmetic overflow`
        let hashMultiply = Int32(truncating: NSNumber(value: hash * 33))
        hash = hashMultiply ^ char
        i -= 1
    }
    return UInt32(bitPattern: hash)
}

hashString("0x8f1083db77b5F556E46Ac46A29DE86e01031Bb14")

注意 in JavaScript:

The operands are converted to 32-bit integers and expressed by a series of bits (zeroes and ones). Numbers with more than 32 bits get their most significant bits discarded.

Swift 中的整数文字默认为 Int 类型,这是您的体系结构的字宽。如果您在任何最新的 Apple 设备上尝试过此操作,它将是 64 位的。

因此,要模仿 JavaScript 行为,您需要转换为 Int32:

Int32(truncatingIfNeeded: 68207947269) ^ Int32(51)

这给出了想要的结果 -511529418

请注意,您的 JavaScript 代码丢弃了整数 68207947269 的位,因为它不能表示为 32 位整数。

根据here,JavaScript中的位运算符使用32位操作数。 68207947269太大,无法用32位表示,先自动截断,再进行按位运算

Swift 整数字面值默认为 Int 类型,Int 的大小取决于平台。它很可能是 64 位的,这就是产生不同结果的原因。

要生成与 JavaScript 代码相同的结果,首先通过截断将其转换为 Int32

Int32(truncatingIfNeeded: 68207947269) ^ 51

请注意,结果是 Int32。稍后您可能需要进行更多类型转换。


关于您的 Swift 翻译,我发现有两个主要问题。

首先,hash * 33会溢出导致崩溃。 JavaScript 版本不需要担心这一点,因为结果会简单地在 JavaScript 中“回绕”。幸运的是,如果 Swift 中的结果溢出(而不是崩溃),还有一个 operator 也会“回绕”。所以你可以这样做:

hash &* 33

其次,您处理字符串的方式与 JavaScript 版本不同。在 JavaScript、charCodeAt returns 中是一个 UTF-16 代码单元,但是您的 Swift 代码获取的是 unicode 标量。

要获得相同的行为,您应该这样做:

extension String.UTF16View {
    subscript(i: Int) -> Element {
        return self[index(startIndex, offsetBy: i)]
    }
}

...

Int32(string.utf16[i])