rickss has asked for the wisdom of the Perl Monks concerning the following question:

I'm working with some old binary code where data values are read from a single byte as bits.

In this particular case, I have a value that is stored in a byte. The first (x0000000) bit represents whether the remaining bits (0xxxxxxx) are a positive or negative number from 1-128. If the value is negative (x0000000) = 1, the bits are flipped. (no, I don't know why they thought they needed to flip the bits as well as indicating if it was positive or negative).

I can tell whether I need to flip the bits (if the first bit is 1 or 0):     $z = $x & (1 << 7);

but I can't quite get right the flipping of the remaining 7 bits into a number. My goal is getting back a decimal number that is the inverse of the right-most 7 bits.

I support I could look at the decimal value, and, if > 127, simply subtract 256 from it, but that seems inelegant.

Replies are listed 'Best First'.
Re: Flipping partial bits
by jwkrahn (Abbot) on Nov 12, 2020 at 20:26 UTC

    This sounds like converting one's complement to two's complement number?

    If so, if the number is negative then add one.

Re: Flipping partial bits
by roboticus (Chancellor) on Nov 12, 2020 at 20:55 UTC

    rickss:

    From your description, I'd say that jwkrahn is correct, and that it's essentially the two's complement of a number from -128 to 127. The advantage of two's complement is that addition "just works" and you don't need to do any special logic to handle the sign.

    ...roboticus

    When your only tool is a hammer, all problems look like your thumb.

Re: Flipping partial bits
by dave_the_m (Monsignor) on Nov 12, 2020 at 19:27 UTC
    Please show the bit patterns (if any) that correspond to the values -128,-127, -1, 0, 1, 127, 128. That will make the format clearer to us.

    Dave.

Re: Flipping partial bits
by haukex (Archbishop) on Nov 12, 2020 at 19:46 UTC
    but I can't quite get right the flipping of the remaining 7 bits

    I agree with dave_the_m that some more examples would be best. But to answer this part of the question, you can mask the value to only get the lower 7 bits with $val & 0x7F, and then flip the lower 7 bits with an XOR, i.e. $val ^ 0x7F, put together that's my $y = ($x & 0x7F)^0x7F;.

    $z = $x & (1 << 7);

    BTW, since you're probably just looking to get a true/false value from this, you don't need the bit shift: $x & 0x80 is enough to tell you if the high bit is set or not, since the return value will be either 0 (false) or 0x80 (true). Update: Actually, never mind - the compiler is of course smart enough to optimize (1 << 7) into 128. I personally prefer 0x80 or 0b1000_0000 over (1 << 7), which is why I tripped over this at first, but TIMTOWTDI.

      The high bit does not need to be masked. Something like:
      for my $x (0..0xff) { my $y = ( $x & 0x80 ) ? -($x ^ 0xff) : $x; say "$x $y" }
        The high bit does not need to be masked.

        Yes, that's true, though I felt the specs were a little bit lacking (hence my request for more examples), which is why I played it safe and showed how to do the masking too.

Re: Flipping partial bits
by ikegami (Patriarch) on Nov 13, 2020 at 16:39 UTC

    Is 0b11111110 equal to -1 (ones' complement), or is 0b11111110 equal to -2 (two's complement)?

    The latter is what computers now use universally. The format is very convenient for computers because the same circuitry can be used for both signed and unsigned additions and subtractions.

    6 as 8-bit unsigned = 00000110 = 6 as 8-bit 2's comp 226 as 8-bit unsigned = 11100010 = -30 as 8-bit 2's comp + -------- 232 as 8-bit unsigned = 11101000 = -24 as 8-bit 2's comp

    You seem to suggest ones' complement, but I'm going to assume it's two's complement since that's far more likely.

    To cast the number from unsigned to signed, you can use any of the following:

    my $x = 0b11100010; # 226 $x = -( ( ~$x + 1 ) & 0xFF ) if $x & 0x80; -or- $x -= 0x100 if $x >= 0x80; -or- $x = unpack('c', pack('C', $x)); say $x; # -30

    ~x + 1 negates a two's complement integer, so the first solution converts the number to its positive counterpart, which we then negate using unary-minus.

    The second solution takes advantage of Perl's ability to work with integers larger than 8-bits in size.

    If you started with a string of bytes, you could use unpack 'c' from the start to avoid having to perform this cast.

Re: Flipping partial bits
by Marshall (Canon) on Nov 14, 2020 at 21:10 UTC
    This is a very odd format, but another solution for you:
    "~" is the complement operator, i.e. flip all bits
    #!/usr/bin/perl use strict; use warnings; #assume each value is a perl var masked to lower 8 bits foreach (0x85, 0x05) { my $x = $_; #allow mod of $x in loop my $neg = $x & 0x80; #decide if negative $x = ~$x & 0x7F if $neg; #adjust if so printf "%s%-08b%s%d\n",($neg)?"-":" ",$x,($neg)?"-":" ",$x; } __END__ -1111010 -122 101 5
    Update: I suppose many variations are possible:
    Here $x winds up being the signed decimal value
    #!/usr/bin/perl use strict; use warnings; #assume each value is a perl var masked to lower 8 bits foreach (0x85, 0x05) { my $x = $_; #allow mod of $x in loop my $neg = $x & 0x80; #decide if negative $x = ~$x & 0x7F, $x = -$x if $neg; #adjust if so printf "Input=%2x hex Input=%08b decimal=%d\n", $_, $_, $x; } __END__ Input=85 hex Input=10000101 decimal=-122 Input= 5 hex Input=00000101 decimal=5
    Normal 2's complement notation could represent within 8 bits, an integer number from -128 to +127.
    In the past computers have been designed with a "sign bit" followed by positive integer bits.
    That failed miserably because there was the possibility of a "plus zero" and "minus zero".
    This sign bit + one's complement is truly weird. I've never seen anything like it.