1

所以我正在研究一些 MongoDB 协议的东西。所有整数都是有符号的小端序。使用 Ruby 的标准Array#pack方法,我可以将整数转换我想要的二进制字符串:

positive_one = Array(1).pack('V')   #=> '\x01\x00\x00\x00'
negative_one = Array(-1).pack('V')  #=> '\xFF\xFF\xFF\xFF'

然而,另一方面,该String#unpack方法具有记录为专门返回无符号整数的“V”格式:

positive_one.unpack('V').first #=> 1
negative_one.unpack('V').first #=> 4294967295

没有用于签名 little-endian 字节顺序的格式化程序。我确信我可以玩位移游戏,或者编写我自己的不使用数组打包的字节处理方法,但我想知道是否有其他人遇到过这个问题并找到了一个简单的解决方案。非常感谢。

4

4 回答 4

2

编辑我误解了您最初转换的方向(根据评论)。但经过一番思考,我相信解决方案还是一样的。这是更新的方法。它做同样的事情,但评论应该解释结果:

def convertLEToNative( num )
    # Convert a given 4 byte integer from little-endian to the running
    # machine's native endianess.  The pack('V') operation takes the
    # given number and converts it to little-endian (which means that
    # if the machine is little endian, no conversion occurs).  On a
    # big-endian machine, the pack('V') will swap the bytes because
    # that's what it has to do to convert from big to little endian.  
    # Since the number is already little endian, the swap has the
    # opposite effect (converting from little-endian to big-endian), 
    # which is what we want. In both cases, the unpack('l') just 
    # produces a signed integer from those bytes, in the machine's 
    # native endianess.
    Array(num).pack('V').unpack('l')
end

可能不是最干净的,但这会转换字节数组。

def convertLEBytesToNative( bytes )
    if ( [1].pack('V').unpack('l').first == 1 )
        # machine is already little endian
        bytes.unpack('l')
    else
        # machine is big endian
        convertLEToNative( Array(bytes.unpack('l')))
    end
end
于 2011-03-08T21:25:41.807 回答
2

用 解包后"V",您可以应用以下转换

class Integer
  def to_signed_32bit
    if self & 0x8000_0000 == 0x8000_0000
      self - 0x1_0000_0000  
    else
      self
    end
  end
end

如果您要处理其他大小的整数,则需要更改魔术常量0x1_0000_0000(即2**32)和0x8000_0000( )。2**31

于 2011-03-09T19:33:36.363 回答
1

这个问题有一种将有符号转换为无符号的方法,这可能会有所帮助。它还有一个指向bindata gem 的指针,看起来它会做你想做的事。

BinData::Int16le.read("\000\f") # 3072

[编辑删除不完全正确的解包指令]

于 2011-03-08T17:53:36.583 回答
1

为了后代,这是我在发现 Paul Rubel 与“经典方法”的链接之前最终想出的方法。它很笨拙并且基于字符串操作,所以我可能会放弃它,但它确实有效,所以有一天有人可能会因为其他原因发现它很有趣:

# Returns an integer from the given little-endian binary string.
# @param [String] str
# @return [Fixnum]
def self.bson_to_int(str)
  bits = str.reverse.unpack('B*').first   # Get the 0s and 1s
  if bits[0] == '0'   # We're a positive number; life is easy
    bits.to_i(2)
  else                # Get the twos complement
    comp, flip = "", false
    bits.reverse.each_char do |bit|
      comp << (flip ? bit.tr('10','01') : bit)
      flip = true if !flip && bit == '1'
    end
    ("-" + comp.reverse).to_i(2)
  end
end

更新:这是更简单的重构,使用 Ken Bloom 答案的广义任意长度形式:

# Returns an integer from the given arbitrary length little-endian binary string.
# @param [String] str
# @return [Fixnum]
def self.bson_to_int(str)
  arr, bits, num = str.unpack('V*'), 0, 0
  arr.each do |int|
    num += int << bits
    bits += 32
  end
  num >= 2**(bits-1) ? num - 2**bits : num  # Convert from unsigned to signed
end
于 2011-03-09T19:59:52.757 回答