2011-03-08 5 views
1

그래서 일부 MongoDB 프로토콜 작업을하고 있습니다. 모든 정수는 리틀 엔디안에 서명되어 있습니다.Ruby에 서명 된 리틀 엔디안 압축 풀기

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

그러나, 다른 방법을 가고는 String#unpack 방법은 특별히 반환 설명 된 'V'형식이 : 루비의 표준 Array#pack 방법을 사용하여, 나는 이진 문자열 정수 내가 잘 할에서 을 변환 할 수 있습니다 부호 정수 : 서명 little endian의 바이트 순서에 대한 포맷터 없습니다

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

. 나는 비트 - 시프 팅으로 게임을하거나 배열 패킹을 사용하지 않는 자신의 바이트 - mangling 방법을 쓸 수있을 것이라고 확신하지만, 다른 누군가가 이것을 실행하고 간단한 해결책을 찾았는지 궁금하다. 매우 감사합니다.

답변

2

"V"으로 개봉 후에는 다음 (2**32입니다) 당신은 마법을 변경해야합니다

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

0x1_0000_0000 상수 변환 및 0x8000_0000 (2**31)를 적용 할 수 있습니다 다른 크기의 정수와 함께.

+0

그게 _ 거의 _ 작동합니다. 'if self> = 0x8000_0000' -'&'연산자는 부울이 아닌 정수를 반환해야합니다. 그렇지 않으면, 고마워! 그게 가장 간단한 해결책 인 것 같고, 내 방식을 모든 단위 테스트에 합격 한 것입니다. – SFEley

+0

@SFEley : 조건부를 다시 작성하는 것이 바람직한 방식으로 편집 했으므로 특별히 서명 비트를 테스트하는 것이 확실합니다. 또한 'else'조건을 포함하지 않은 버그를 수정했기를 바랍니다. –

1

This question에는 도움이 될 수있는 부호가있는 부호로 변환하는 방법이 있습니다. 그것도 당신이 원하는대로 할 것 같아 보이는 bindata 보석에 대한 포인터를 가지고 있습니다.

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

은 (주석에 따라) 내가 원래 변환 된 방향을 잘못 이해

+0

포경에 대한 고마워요. 그것은 내가 복사하고 붙여 넣기 대신에 빨리 타이핑하는 것입니다. > 8-/ – SFEley

+0

길이 수정자를 사용하더라도 's'지시어가 답이라고 생각하지 않습니다. _native 바이트 order_로 문자열을 처리합니다. 일부 프로세서에서는 리틀 엔디안이고 다른 프로세서에서는 빅 엔디 언 (big endian)이됩니다. 항상 작은 엔디안이 필요해. – SFEley

+0

고마워요! 그 다른 질문의 bitmasking 방법은 시원하게 보입니다. 그리고 마침내 나는 다른 방법보다 효율적으로 작동합니다.나는 BinData에 대해 알고 있었고, 사용하도록 고려했지만, 몽고 팀의 BSON 보석을 거의 모든 용도로 사용하고있을 때 과도한 것처럼 보였다. (BinData에서 전체 BSON 사양을 다시 작성하는 것을 고려해 보았지만 C 확장을 사용하는 BSON 젬은 순수 루비에서 할 수있는 것보다 훨씬 빠릅니다.) – SFEley

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 
+0

아닙니다. 리틀 엔디안 표현으로 이진 문자열 _containing_ signed integer를받을 것입니다. 이 바이너리 문자열을 루비 정수로 변환해야하고, 일관되게해야한다. – SFEley

+0

나는 아직도 혼란이 있다고 생각한다. 서명 된 리틀 엔디안 번호를 나타내는 MongoDB에서 오는 바이트 문자열이 있습니다. 문자열을 취하여 올바른 숫자를 반환하는 메서드가 필요합니다. 예를 들어, "\ xE8 \ x03 \ x00 \ x00" '이 (가) 전달되면 1000을 반환하고 "\ x18 \ xFC \ xFF \ xFF"를 전달하면 -1000을 반환해야합니다. 당신의 방법은 다른 것을합니다. 나는 다른 방식으로 (바이너리 문자열에 숫자) 잘 진행할 수있다. – SFEley

+0

@SFEley : 입력란에 문자열이 포함되지 않았다는 사실이 OP에게 알려지지 않았습니다. 나는 그 변환을위한 방법을 추가했다. –

1

후손을 위해서, 나는 Paul Rubel의 "classical method" 링크를 알아 내기 전에 결국 생각해 낸 방법이 있습니다. 그것은 미봉책과 문자열 조작에 따라, 그래서 아마 스크랩 하겠지만, 누군가가 언젠가는 다른 이유가 흥미있을 거라고 그래서이 작업을 수행

# 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 : 여기 사용하여 간단한 리팩토링이다 켄 블룸의 일반화 된 임의 길이 형태의 답 :

# 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 
관련 문제