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

6

6

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";
Bloodstone answered 21/4, 2011 at 20:5 Comment(0)
H
2

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
Hogarth answered 21/4, 2011 at 21:0 Comment(0)
I
8

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.

Itinerary answered 21/4, 2011 at 20:19 Comment(0)
R
3

You didn't specify what you expect. I'm guessing you're expecting 00000001.

That's the correct bits for the byte you provided, at least on non-EBCDIC systems. Remember, the input of unpack is a string (mostly strings of bytes). Perhaps you wanted

unpack('b*', pack('C', 1))

Update: As others have pointed out, the above gives 10000000. For 00000001, you'd use

unpack('B*', pack('C', 1))  # 00000001
Redstone answered 21/4, 2011 at 20:12 Comment(5)
Thanks. It seems that's what I was looking for. Although, is this the only way to accomplish the task, with a nested pack. I mean maybe it is very efficient, but if not, I just wondering if there's another way to go about. I will have to execute this command many times.Bloodstone
@Rob: yes, there's another way. The nested pack is only needed because unpack works on strings, not numbers; if you have a number, you should probably just use sprintf (see tchrist's answer)Halyard
@Rob, "\x01", chr(1) and pack('C', 1) are produce the same string.Redstone
@Rob: Did you really just ask whether something is the only way to do it — in Perl? :)Tswana
@ikegami: you forgot v1 :) (and "\cA", "\1", use charnames ':full'; "\N{START OF HEADING}", etc)Halyard
A
3

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))
Arman answered 21/4, 2011 at 20:12 Comment(2)
You need more than one byte for endianness to come into play.Redstone
Then again, the article talks about endianness of bits. I've never seen it used that way, though.Redstone
T
2

The ord(1) is 49. You must want something like sprintf("%064b", 1), although that does seem like overkill.

Tswana answered 21/4, 2011 at 20:12 Comment(0)
H
2

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
Hogarth answered 21/4, 2011 at 21:0 Comment(0)
R
0

You want "B" instead of "b".

$ perl -E'say unpack "b*", "1"'
10001100

$ perl -E'say unpack "B*", "1"'
00110001

pack

Redstone answered 21/4, 2011 at 20:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.