Hi,
In trying to read 4 bytes as a signed integer from an IO in big-
endian order, is there already a utility to do this in Ruby? I notice
unpack has lots of combinations already, but seemingly not one for
this. Maybe I am just missing it?
Thanks,
Bob E.
Hi,
So, I came up with a solution, and would be grateful for Ruby style
tips. It seemed a lot harder to do than it needed to be.
Any help appreciated.
Thanks,
Bob
The Tests:
class ByteReaderTest < Test::Unit::TestCase
def test_read_signed4_negative
negative = “” << 0xF2 << 0x34 << 0x56 << 0x78
assert_equal -231451016, signed4(StringIO.new(negative))
end
def test_read_signed4_positive
positive = “” << 0x02 << 0x34 << 0x56 << 0x78
assert_equal 36984440, signed4(StringIO.new(positive))
end
end
The Solution:
def signed4(file)
bits = (file.read(4).unpack(‘B32’))[0]
if bits[0…0].eql? “0” # sign bit is positive
bits.to_i(2)
else # sign bit is negative
compute_negative_number_from(bits)
end
end
def compute_negative_number_from(bits)
twos_complement = flip(bits).join.to_i(2) + 1
-1 * twos_complement
end
def flip(bits)
bits.scan(/\w/).collect { |bit| (bit.eql?(“1”)) ? “0” : “1” }
end
Hi Robert,
I’m probably missing something here, but how about this? It works for
your tests at least.
def signed4(file)
file.read(4).reverse.unpack(‘l’)[0]
end
Cheers,
Dave
On Wed, 16 Nov 2005, Robert E. wrote:
The Tests:
assert_equal 36984440, signed4(StringIO.new(positive))
else # sign bit is negative
bits.scan(/\w/).collect { |bit| (bit.eql?(“1”)) ? “0” : “1” }
end
harp:~ > cat a.rb
require “test/unit”
class ByteReaderTest < Test::Unit::TestCase
MAX_POS = 2 ** 31 -1
BIG_ENDIAN = [42].pack(‘N’) == [42].pack(‘i’)
def test_read_signed4_positive
positive = [0x02, 0x34, 0x56, 0x78].pack “c*”
assert_equal 36984440, signed4(positive)
end
def test_read_signed4_negative
negative = [0xF2, 0x34, 0x56, 0x78].pack “c*”
assert_equal -231451016, signed4(negative)
end
def signed4 buf
buf = buf.read 4 if buf.respond_to? “read”
raise RangeError unless buf.size == 4
n = buf.unpack(‘N’).first
if n > MAX_POS
(BIG_ENDIAN ? buf : buf.reverse).unpack(‘l’).first
else
n
end
end
end
harp:~ > ruby a.rb
Loaded suite a
Started
…
Finished in 0.000583 seconds.
2 tests, 2 assertions, 0 failures, 0 errors
-a
Sweet. That’s a very slick answer. Thanks.
Bob
Hi David,
The only thing I worried about that was according to the PickAxe v.2
book, I is in native order. I thought that would be a problem since I
am running this on unix boxes and windows boxes. Is that a true concern?
Thanks for your reply,
Bob
Robert E. wrote:
Hi,
In trying to read 4 bytes as a signed integer from an IO in big- endian
order, is there already a utility to do this in Ruby? I notice unpack
has lots of combinations already, but seemingly not one for this. Maybe
I am just missing it?
Thanks,
Bob E.
You can unpack with “N” and then use the following to interpret that
positive Integer as a signed number in 32 bit two’s complement
representation and convert it to a positive or negative Integer:
length = 32 # bits
max = 2**length-1
mid = 2**(length-1)
to_signed = proc {|n| (n>=mid) ? -((n ^ max) + 1) : n}
For example:
irb(main):012:0> to_signed[4294967295]
=> -1
This is a snippet from the implementation of my bit-struct lib (see
RAA). A BitStruct is basically a string with some extra accessors and
convenience methods. Currently it handles fields that are either
multiple bytes or 1-7 bits within a byte. (Eventually, longer odd-size
bit fields would be nice.) Also supports fields for: fixed length char
strings, null-terminated strings, hex octets, decimal octets, floats,
nested BitStructs, and “rest”–the rest of the string after defined
fields. It’s really useful for playing with net protocols in pure
ruby. (Someday, I’ll probably write a C extension for efficiency.)
Example:
require ‘bit-struct’
class C < BitStruct
signed :foo, 32, “Something signed”
unsigned :bar, 32, “Something UNsigned”
end
c = C.new
c.foo = -12345678
c.bar = 12345678
puts “-”*40
p c
puts “-”*40
p c.to_s
puts “-”*40
puts c.inspect_detailed
puts “-”*40
p c.to_h
puts “-”*40
puts C.describe
END
#
“\377C\236\262\000\274aN”
C:
Something signed = -12345678
Something UNsigned = 12345678
Oh, yeah, I forgot to show that you can use a BitStruct to parse strings
according to your defined format:
Joel VanderWerf wrote:
require ‘bit-struct’
class C < BitStruct
signed :foo, 32, “Something signed”
unsigned :bar, 32, “Something UNsigned”
end
c = C.new(socket.recv(…))
p c.foo
and so on.