Search code examples
rubyendiannesssignedtwos-complement24-bit

Convert three bytes, two's complement, signed integer


In the SEG-D seismic data format, some header parameters are formatted as three bytes, two’s complement, signed binary. All values are big-endian.

Using String#unpack, Ruby core can only convert 16-bit and 32-bit values, but not 24-bit.

How can I get the binary values converted into integers in the following two’s complement way:

"\x00\x00\x00" => 0x000000 (0)
"\x00\x00\x01" => 0x000001 (1)
"\x7F\xFF\xFF" => 0x7FFFFF (8388607)
"\xFF\xFF\xFF" => -0x000001 (-1)
"\x80\x00\x00" => -0x800000 (-8388608)

Solution

  • Convert the fist byte as signed 8-bit (two’s complement) and the second and third as unsigned 16-bit.

    # String to be converted (-8388608)
    bin = "\x80\x00\x00"
    
    # Convert as signed 8-bit and unsigned 16-bit (big-endian)
    values = bin.unpack("c S>")
    
    # Add with the first byte weighted
    converted = values[0] * 2**16 + values[1]
    

    Alternate version of the last line using bitwise operations shift and OR (probably more efficient):

    converted = values[0] << 16 | values[1]