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