Forum: Ruby Reading a signed byte in network byte order

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
robert.evans (Guest)
on 2005-11-15 01:08
(Received via mailing list)
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.
robert.evans (Guest)
on 2005-11-15 18:15
(Received via mailing list)
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
dbalmain.ml (Guest)
on 2005-11-15 19:42
(Received via mailing list)
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
ara.t.howard (Guest)
on 2005-11-15 19:57
(Received via mailing list)
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
robert.evans (Guest)
on 2005-11-15 20:36
(Received via mailing list)
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.evans (Guest)
on 2005-11-15 20:39
(Received via mailing list)
Sweet. That's a very slick answer. Thanks.

Bob
vjoel (Guest)
on 2005-11-16 01:11
(Received via mailing list)
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__

----------------------------------------
#<C foo=-12345678, bar=12345678>
----------------------------------------
"\377C\236\262\000\274aN"
----------------------------------------
C:
              Something signed = -12345678
            Something UNsigned = 12345678
vjoel (Guest)
on 2005-11-16 01:17
(Received via mailing list)
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.
This topic is locked and can not be replied to.