用Ruby解包签名的little endian

用Ruby解包签名的little endian,ruby,integer,bit-manipulation,endianness,signedness,Ruby,Integer,Bit Manipulation,Endianness,Signedness,所以我正在做一些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”格式被记录为专门返回无符号整数: pos

所以我正在做一些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
没有用于有符号小尾端字节顺序的格式化程序。我确信我可以玩位移位的游戏,或者编写我自己的不使用数组打包的字节损坏方法,但我想知道是否有其他人遇到过这种情况并找到了一个简单的解决方案。非常感谢。

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

BinData::Int16le.read("\000\f") # 3072
[编辑以删除不太正确的s unpack指令]

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

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

[编辑以删除不太正确的s unpack指令]

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

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

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

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

使用
“V”
解包后,可以应用以下转换

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

如果要处理其他大小的整数,则需要更改魔法常数
0x1\u 0000\u 0000
(即
2**32
)和
0x8000\u 0000
2**31
)。

使用
“V”
解包后,可以应用以下转换

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

如果你处理的是其他大小的整数,你需要更改魔法常数
0x1\u 0000\u 0000
(即
2**32
)和
0x8000\u 0000
2**31
)。

为了子孙后代的利益,在找到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
#从给定的小端二进制字符串返回一个整数。
#@param[String]str
#@return[Fixnum]
def self.bson_至_int(str)
bits=str.reverse.unpack('B*')。首先获取0和1
如果位[0]=“0”#我们是正数;生活很简单
位至_i(2)
否则,得到两个补码
comp,flip=“”,false
bits.reverse.each|char do|位|

comp为了子孙后代的利益,在发现保罗·鲁贝尔(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
#从给定的小端二进制字符串返回一个整数。
#@param[String]str
#@return[Fixnum]
def self.bson_至_int(str)
bits=str.reverse.unpack('B*')。首先获取0和1
如果位[0]=“0”#我们是正数;生活很简单
位至_i(2)
否则,得到两个补码
comp,flip=“”,false
bits.reverse.each|char do|位|

修正了comp格式的奇怪之处——谢谢你的关注。这是我快速打字而不是复制和粘贴得到的结果。>8-/我认为“s”指令不是答案,即使使用了长度修饰符。它以本机字节顺序处理字符串,在某些处理器上是小端,在其他处理器上是大端。我一直都需要小恩迪安,谢谢!另一个问题中的比特掩蔽方法看起来确实很酷——而且肯定比我最终使用的另一种方法更有效。我知道BinData,我也考虑过使用它,但当我将Mongo团队的BSON gem用于几乎所有其他方面时,这似乎有些过头了。(我甚至考虑过在BinData中重写整个BSON规范,但是BSON gem及其C扩展比我认为在纯Ruby中可以做的任何事情都要快。)格式怪异修复了——感谢您的关注。这是我快速打字而不是复制和粘贴得到的结果。>8-/我认为“s”指令不是答案,即使使用了长度修饰符。它以本机字节顺序处理字符串,在某些处理器上是小端,在其他处理器上是大端。我一直都需要小恩迪安,谢谢!另一个问题中的比特掩蔽方法看起来确实很酷——而且肯定比我最终使用的另一种方法更有效。我知道BinData,我也考虑过使用它,但当我将Mongo团队的BSON gem用于几乎所有其他方面时,这似乎有些过头了。(我甚至考虑过在BinData中重写整个BSON规范,但是BSON gem及其C扩展比我认为在纯Ruby中可以做的任何事情都要快得多。)不完全如此。我将接收二进制字符串,其中包含以小端表示的有符号整数。我需要将这些二进制字符串转换成Ruby整数,并且我需要一致地进行转换。我认为仍然存在一些混淆。我有一个来自MongoDB的字节字符串,表示有符号的小端数字。我想要一个方法,它将接受一个字符串并返回正确的数字。例如,如果通过
“\xE8\x03\x00\x00”
,则返回1000;如果通过
“\x18\xFC\xFF\xFF”
,则返回-1000。哟