1

So I got a coding problem, the the program asks me to negate every bit of a binary, for example, the regular bitwise negation will be like this: ten = 00001010 the negation is 11110101 = 245, well to achieve this, in my opinion, I can just XOR it with 255.

But the real problem is they only ask to negation only until the last '1'. My teacher teaches me to read binary from right so the last '1' if the number is ten is 2^3.

So ten = 1010 the bitwise negation is 0101 = 5

another example is

5 = 00000101 -> take only until the last '1' which is 2^2

so its like ignore this ->[00000], and only use this ->[101]

so now 5 = 101 the negation will be '010' which is equal to 2

I wonder if is there any method to help me achieve this result, at first I thought about using a loop like:

for(int i = 7; i >= 0;i--)

And store the index to a char to represent the binary and try to negate it from there, but I quickly stop when I see the constraint is 0 < n <= 1000000

How can I solve this problem?

7
  • For the first "reversal" without adding the extra requirement, it's just the bitwise complement operator ~. As in ~10. If you just want the lowest eight bits then do a bitmask with 0x0ff, or assign the result to uint8_t variable. Commented Mar 25, 2024 at 16:40
  • 1
    So do you mean 00001010 -> 00000101? If so edit the question. Commented Mar 25, 2024 at 16:43
  • 2
    @penguinn please edit and add 2 or 3 examples of input and expected output. Commented Mar 25, 2024 at 16:43
  • They key is building the correct mask for using with XOR. The number of 1s to the 'right' of the mask (as we read the binary) is the same as the number of bits actually used in the number being inverted. So you just need a way to know how many bits in the given number are used... hint: the shift-right operator (>>), and a value of zero when all (used) bits are gone. You can then build your XOR mask. Commented Mar 25, 2024 at 16:46
  • 1
    Re "write binary from right", Do you write 123 from the right? so why would you write 1010 from the right? Commented Mar 25, 2024 at 17:06

5 Answers 5

2

What you call "last" bit is usually called "most significant bit" — less confusion with this name.

  1. Find the most significant bit using any of several known techniques.

  2. From that, create a mask which has 1 for all bits you want to flip and 0 for all other bits.

  3. Use XOR with this mask.

Sign up to request clarification or add additional context in comments.

Comments

2

What you want is a mask comprising all 1 bits up to and including the most significant set bit, and then XOR that with your original value:

uint32_t x = n; // n is your value
x |= (x >> 1);
x |= (x >> 2);
x |= (x >> 4);
x |= (x >> 8);
x |= (x >> 16);
return n ^ x;

5 Comments

Would you contemplate a loop to move through the >> x^2 to eliminate the 'hard' coded 1,2,4,8, 16 or do you think that would just add unnecessary extra looping assembler to the code given you only need to go to 2^4? (PS. CPlus, your answers for the 2024 moderator votes left me most impressed!)
@Pete855217 Given that it is only 5 lines, and a few instructions per line (move, or, shift), I find it likely that using a loop may very well cause more code to be created, due to the branch, the comparison, etc. Additionally creating the numbers to shift by on the fly in a loop presumably using 1 << i would add an extra step per iteration. Though on the other hand less shift constants will need to be stored.
@Pete855217 Upon further investigation, the code generated for the original version and the loop versions are the same, even with size optimizations enabled, -Os, GCC prefers the loop-less version. Without optimizations, they seem to be equally long in assembly.
And I guess more risk of something 'going wrong' in the compiler or the coder with additional logic and instructions (and dependent on the compiler too). For only 4 constants, it's absolutely clear what's going on (to read the code) and makes sense.
@Pete855217 Yeah, I would agree that the loop-less version is more readable than the loop.
1

In your example, you want to negate the bits for 10102. You suggest you could obtain the result you want with n ^ 0b1111. As this implies, XORing with zero has no effect, and XORing with one negates.

x y x ^ y x ^ y
0 0 0 x
1 0 1 x
0 1 1 ~x
1 1 0 ~x

This means n ^ 0b1111 is equivalent to n ^ 0b1 ^ 0b10 ^ 0b100 ^ 0b1000. That presents a simple solution that works for any unsigned integer type as long as we can loop the correct number of times.

To determine the number of times to loop, we just need to repeatedly shift.

10102 Not zero: Enter loop, then shift.
1012 Not zero: Enter loop, then shift.
102 Not zero: Enter loop, then shift.
12 Not zero: Enter loop, then shift.
02 Zero: Exit loop.
unsigned negate( unsigned n ) {
   unsigned mask = 1;

   for ( unsigned i = n; i; i >>= 1 ) {
      n ^= mask;
      mask <<= 1;
   }

   return n;
}

It's unclear what result you expect for zero. The above produces zero.

2 Comments

This solution is the slowest here. GCC: quick-bench.com/q/bdu3IuCBjsn27cV2OfGVOoXHVVU, Clang: quick-bench.com/q/-jC8a3VrjwATpFX2ovfrJZtcMhg. You are welcome to fix the issues in the benchmarks if any.
@273K, aye, but it's the clearest. Seeing as this is homework on bit manipulation, that is more valuable here. Note that the answers you identify as fastest as severely lacking in explanation, something crucial when answering a homework question.
1

A version for moden Intel/AMD processors that support the bit manipulation instruction extensions:

/* Compile with
 * gcc -O -mlzcnt -mbmi2 -Wall -Wextra foo.c
 * or
 * gcc -O -march=native -Wall -Wextra foo.c
 */

#include <stdio.h>
#include <inttypes.h>
#include <immintrin.h>

uint32_t flip_bits(uint32_t x) {
 return  _bzhi_u32(~x, 32 - _lzcnt_u32(x));
}

void print_binary(uint32_t x) {
  for (int i = 31; i >= 0; i--) {
    if (x & (1 << i)) {
      putchar('1');
    } else {
      putchar('0');
    }
  }
}

int main(int argc, char **argv) {
  for (int i = 1; i < argc; i++) {
    uint32_t x = strtoul(argv[i], NULL, 10);
    uint32_t y = flip_bits(x);
    printf("%" PRIu32 " (", x);
    print_binary(x);
    printf(") becomes %" PRIu32 " (", y);
    print_binary(y);
    fputs(")\n", stdout);
  }
}

It uses the LZCNT instruction to calculate the number of leading 0 bits, and BZHI to reset those bits to 0 after a bitwise-NOT of the original number. Might be interesting if you're looking to optimize a solution.

1 Comment

That print_binary is inefficient. Instead of an if just use putchar('0' + (x >> i & 1));
-1
#include <stdio.h>

int main() {
    unsigned input = 10;
    unsigned mask = 1u << 8*sizeof(input)-1; // most significant bit is always 1, no matter the variable type
    unsigned result = 0;
    
    while ( (input & mask) == 0) mask>>=1; // looking for first 1

    // >> puts 1 in front ( 1000 -> 1100 ) instead of ( 1000 -> 0100 )
    //    if most significant bit is 1
    mask &= input; // getting rid of all extra bits
    while (mask > 0) {
        result |= (input & mask) ^ mask;
        mask>>=1;
    }
    printf("Input: %u\nOutput: %u\n", input, result);
    return 0;
}

9 Comments

For the most portability, 1u << 8*sizeof(input)-1 should be 1u << CHAR_BIT*sizeof(input)-1
What's wrong with signed int? it would be just negative
what does the 'u' do?
Re "What's wrong with signed int?", Before my fix, your code was invoking undefined behaviour. For example, on a system with 32-bit int values, it was doing 1 << 31, which is ub
Re "what does the 'u' do?", 1 is a value of type int. 1u is a value of type unsigned
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.