Reading a signed byte in network byte order


#1

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.


#2

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


#3

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


#4

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


#5

Sweet. That’s a very slick answer. Thanks.

Bob


#6

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


#7

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


#8

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.