Search code examples
rubyunpack

Ruby: Why does unpack('Q') give a different result than manual conversion?


I'm trying to write a function that will .unpack('Q') (unpack to uint64_t) without access to the unpack method.

When I manually convert from string to binary to uint64, I get a different result than .unpack('Q'):

Integer('abcdefgh'.unpack('B*').first, 2) # => 7017280452245743464

'abcdefgh'.unpack('Q').first # => 7523094288207667809

I don't understand what's happening here.

I also don't understand why the output of .unpack('Q') is fixed regardless of the size of the input. If I add a thousand characters after 'abcdefgh' and then unpack('Q') it, I still just get [7523094288207667809]?


Solution

  • Byte order matters:

     Integer('abcdefgh'.
               each_char.
               flat_map { |c| c.unpack('B*') }.
               reverse.
               join, 2)
     #⇒ 7523094288207667809
     'abcdefgh'.unpack('Q*').first
     #⇒ 7523094288207667809
    

    Your code produces the wrong result because after converting to binary, bytes should be reversed.


    For the last part of your question, the reason the output of .unpack('Q') doesn't change with a longer input string is because the format is specifying a single 64-bit value so any characters after the first 8 are ignored. If you specified a format of Q2 and a 16 character string you'd decode 2 values:

    > 'abcdefghihjklmno'.unpack('Q2')
    => [7523094288207667809, 8029475498074204265]
    

    and again you'd find adding additional characters wouldn't change the result:

    > 'abcdefghihjklmnofoofoo'.unpack('Q2')
    => [7523094288207667809, 8029475498074204265]
    

    A format of Q* would return as many values as multiples of 64-bits were in the input:

    > 'abcdefghihjklmnopqrstuvw'.unpack('Q*')
    => [7523094288207667809, 8029475498074204265, 8608196880778817904]
    > 'abcdefghihjklmnopqrstuvwxyz'.unpack('Q*')
    => [7523094288207667809, 8029475498074204265, 8608196880778817904]