Reading a signed byte in network byte order

R

Robert Evans

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
 
R

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

David Balmain

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

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 =3D "" << 0xF2 << 0x34 << 0x56 << 0x78
assert_equal -231451016, signed4(StringIO.new(negative))
end

def test_read_signed4_positive
positive =3D "" << 0x02 << 0x34 << 0x56 << 0x78
assert_equal 36984440, signed4(StringIO.new(positive))
end

end

The Solution:

def signed4(file)
bits =3D (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 =3D 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,

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
 
A

Ara.T.Howard

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

Robert Evans

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


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

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,

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
 
R

Robert Evans

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

Bob


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

Joel VanderWerf

Robert said:
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>
 
J

Joel VanderWerf

Oh, yeah, I forgot to show that you can use a BitStruct to parse strings
according to your defined format:

Joel said:
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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top