Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you explain the bits I'm getting from unpack?

I'm relatively inexperienced with Perl, but my question concerns the unpack function when getting the bits for a numeric value. For example:

my $bits = unpack("b*", 1);
print $bits;

This results in 10001100 being printed, which is 140 in decimal. In the reverse order it's 49 in decimal. Any other values I've tried seem to give the incorrect bits.

However, when I run $bits through pack, it produces 1 again. Is there something I'm missing here?

It seems that I jumped to conclusions when I thought my problem was solved. Maybe I should briefly explain what it is I'm trying do.

I need to convert an integer value that could be as big as 24 bits long (the point being that it could be bigger than one byte) into a bit string. This much can be accomplished using unpack and pack as suggested by @ikegami, but I also need to find a way to convert that bit string back into it's original integer (not a string representation of it).

As I mentioned, I'm relatively inexperienced with Perl, and I've been trying with no success.


I found what seems to be an optimal solution:

my $bits = sprintf("%032b", $num);
print "$bits\n";
my $orig = unpack("N", pack("B32", substr("0" x 32 . $bits, -32)));
print "$orig\n";
like image 571
Rob Avatar asked Apr 21 '11 20:04

Rob


3 Answers

This might be obvious, but the other answers haven't pointed it out explicitly: The second argument in unpack("b*", 1) is being typecast to the string "1", which has an ASCII value of 31 in hex (with the most significant nibble first).

The corresponding binary would be 00110001, which is reversed to 10001100 in your output because you used "b*" instead of "B*". These correspond to the opposite "endian" forms of the binary representation. "Endian-ness" is just whether the most-significant bits go at the start or the end of the binary representation.

like image 105
goodside Avatar answered Oct 17 '22 22:10

goodside


Yes, you're missing that different machines support different "endianness". And Perl is treating 1 like '1' so ( 0x31 ). So, you're seeing 1 -> 1000 (in ascending order) and 3 -> 1100.

"Wrong" depends on perspective and whether or not you gave Perl enough information to know what encoding and endianness you wanted.

From pack:

b A bit string (ascending bit order inside each byte, like vec()).
B A bit string (descending bit order inside each byte).

I think this is what you want:

unpack( 'B*', chr(1))
like image 3
Axeman Avatar answered Oct 17 '22 23:10

Axeman


You're trying to convert an integer to binary and then back. While you can do that with pack and then unpack, the better way is to use sprintf or printf with the %b format:

my $int = 5;
my $bits = sprintf "%024b\n", $int;
print "$bits\n";

To go the other way (converting a string of 0s & 1s to an integer), the best way is to use the oct function with a 0b prefix:

my $orig = oct("0b$bits");
print "$orig\n";

As the others explained, unpack expects a string to unpack, so if you have an integer, you first have to pack it into a string. The %b format expects an integer to begin with.

If you need to do a lot of this on bytes, and speed is crucial, you could build a lookup table:

my @binary = map { sprintf '%08b', $_ } 0 .. 255;

print $binary[$int];  # Assuming $int is between 0 and 255
like image 2
cjm Avatar answered Oct 17 '22 23:10

cjm