Распаковать подписанный little-endian в Ruby - PullRequest
1 голос
/ 08 марта 2011

Итак, я работаю над некоторыми протоколами MongoDB. Все целые числа подписаны как little-endian. Используя стандартный метод 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

Нет формата для подписанного байтового порядка с прямым порядком байтов. Я уверен, что мог бы играть в игры со сдвигом битов или написать свой собственный метод манипулирования байтами, который не использует упаковку массивов, но мне интересно, сталкивался ли кто-нибудь с этим и нашел простое решение. Большое спасибо.

Ответы [ 4 ]

2 голосов
/ 09 марта 2011

Редактировать Я неправильно понял направление, в котором вы первоначально конвертировали (согласно комментарию).Но, подумав об этом, я думаю, что решение остается прежним.Вот обновленный метод.Он делает то же самое, но комментарии должны объяснить результат:

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
2 голосов
/ 09 марта 2011

После распаковки с "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), если вы имеете дело с целыми числами других размеров.

1 голос
/ 09 марта 2011

Ради потомков, вот метод, который я в конце концов придумал, прежде чем заметил ссылку Пола Рубеля на «классический метод» . Это грязно и основано на манипуляции со строками, так что я, вероятно, откажусь от этого, но это работает, так что кто-то может найти это интересным по какой-то другой причине:

# 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

ОБНОВЛЕНИЕ: Вот более простой рефакторинг с использованием обобщенной формы произвольной длины ответа Кена Блума:

# 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
1 голос
/ 08 марта 2011

В этом вопросе есть метод для преобразования подписанного в неподписанное, что может быть полезным.Он также имеет указатель на гем bindata , который выглядит так, как будто он будет делать то, что вы хотите.

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

[отредактирован для удаления не совсем правильной директивы s распаковки]

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...