Reading a signed byte in network byte order

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 Evans

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

···

On Nov 14, 2005, at 3:07 PM, Robert Evans 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 Evans

Robert Evans 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 Evans

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
----------------------------------------
{:bar=>12345678, :foo=>-12345678}
----------------------------------------
    byte: type name [size] description
----------------------------------------------------------------------
      @0: signed foo [ 32b] Something signed
      @4: unsigned bar [ 32b] Something UNsigned

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

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 11/16/05, Robert Evans <robert.evans@acm.org> wrote:

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

On Nov 14, 2005, at 3:07 PM, Robert Evans 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 Evans
>

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

···

On Wed, 16 Nov 2005, Robert Evans wrote:

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

--

ara [dot] t [dot] howard [at] gmail [dot] com
all happiness comes from the desire for others to be happy. all misery
comes from the desire for oneself to be happy.
-- bodhicaryavatara

===============================================================================

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.

···

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

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

···

On Nov 15, 2005, at 9:40 AM, David Balmain wrote:

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 11/16/05, Robert Evans <robert.evans@acm.org> wrote:

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

On Nov 14, 2005, at 3:07 PM, Robert Evans 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 Evans

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

Bob

···

On Nov 15, 2005, at 9:56 AM, Ara.T.Howard wrote:

On Wed, 16 Nov 2005, Robert Evans wrote:

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

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
--

> ara [dot] t [dot] howard [at] gmail [dot] com
> all happiness comes from the desire for others to be happy. all misery
> comes from the desire for oneself to be happy.
> -- bodhicaryavatara