所以我正在研究一些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'格式记录为专门返回 unsigned 整数:
positive_one.unpack('V').first #=> 1
negative_one.unpack('V').first #=> 4294967295
没有用于签名的little-endian字节顺序的格式化程序。我确信我可以使用位移来玩游戏,或者编写我自己的不使用数组打包的字节错误方法,但我想知道是否有其他人遇到过这个并找到了一个简单的解决方案。非常感谢。
答案 0 :(得分: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
答案 1 :(得分: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
)。
答案 2 :(得分:1)
This question有一种将签名转换为无符号的方法,可能会有所帮助。它还有一个指向bindata宝石的指针,看起来它会做你想要的。
BinData::Int16le.read("\000\f") # 3072
[编辑删除不太正确的解包指令]
答案 3 :(得分:1)
为了后人,这里是我最终提出的方法,然后发现Paul Rubel与"classical method"的链接。它是kludgy并且基于字符串操作,所以我可能会废弃它,但它确实有效,所以有人可能会因某些其他原因觉得它有趣:
# 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
UPDATE:这是更简单的重构,使用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