Operators

Lesson 59: Using ~ Bitwise NOT Operator

Learn how ~ inverts bits, how it differs from !, and how inversion is used with masks in embedded logic.

Progress indicator

Lesson 59 of 59

Learning Objectives

  • Understand how bitwise NOT (~) inverts every bit in a value.
  • Predict inversion results in binary using 1->0 and 0->1 rule.
  • Compare ~ with logical NOT (!) and use the right operator by intent.
  • Use masks after inversion to keep only the bit range you need.
  • Avoid common signed/unsigned mistakes with inverted values.

Concept Explanation

What is the Bitwise NOT Operator (~)

The ~ operator flips every bit in the input value.

Bit inversion rule is simple: 1 becomes 0, and 0 becomes 1.

Bitwise NOT Syntax

result = ~value;
byte inv = ~flags;

How ~ Works

  1. Read source value from memory.
  2. ALU applies NOT to every bit position.
  3. Write inverted result to destination variable.
  4. Optional mask keeps only required bits for next logic step.

Bit Inversion Concept

sensorFlags    = 10101100
~sensorFlags   = 01010011
keep low nibble: 01010011 & 00001111 = 00000011

In practice, inverted values are often masked because inversion flips all bits, not just the ones you care about.

~ vs ! (Comparison)

OperatorInput typeOutput behavior
~Integer bitsFlips every bit
!Boolean expressionReturns true/false inverse

Use ~ for bit manipulation and ! for condition logic.

When to Use ~

  • Building inverse masks.
  • Active-low signal transformations.
  • Preprocessing packed status bytes before filtering.

Signed vs Unsigned Note

In C/C++, integer promotions can affect how ~ values are printed or compared. Prefer unsigned types (byte, uint8_t) for clean bit-level behavior.

Example Code

This sketch inverts a status byte, masks selected bits, and uses the result to drive LED output.

const int LED_PIN = 2;

byte sensorFlags = 0b10101100;
byte activeLowMask = 0b00000011;
byte keepLowNibbleMask = 0b00001111;

void setup() {
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  byte invertedFlags = ~sensorFlags;
  byte selected = invertedFlags & activeLowMask;
  byte lowNibble = invertedFlags & keepLowNibbleMask;

  if (selected) {
    digitalWrite(LED_PIN, HIGH);
  } else {
    digitalWrite(LED_PIN, LOW);
  }

  Serial.print("sensorFlags=");
  Serial.print(sensorFlags, BIN);
  Serial.print(", invertedFlags=");
  Serial.print(invertedFlags, BIN);
  Serial.print(", selected=");
  Serial.print(selected, BIN);
  Serial.print(", lowNibble=");
  Serial.println(lowNibble, BIN);

  delay(700);
}

Example Code Explanation

  1. sensorFlags stores original packed bits.
  2. invertedFlags = ~sensorFlags flips all bits.
  3. selected keeps only two low bits from inverted value.
  4. lowNibble keeps only lower 4 bits for easier observation.
  5. If selected is non-zero, LED turns ON.
  6. Serial prints original, inverted, and masked values in binary format.

What Happens Inside

  1. CPU loads sensorFlags.
  2. ALU applies bitwise NOT and writes invertedFlags.
  3. Mask operations clear irrelevant high bits.
  4. Branch logic checks masked result for LED output.
StepExpressionBinary resultPurpose
1~sensorFlags01010011Invert all bits
2invertedFlags & 0000001100000011Select low 2 bits
3invertedFlags & 0000111100000011Select low nibble

Common Mistakes with ~

  • Confusing ~ with logical NOT !.
  • Using inverted value directly without masking required bits.
  • Ignoring type promotion and signed printing confusion.
  • Assuming inversion changes only one target bit.

Best Practices for ~

  • Use explicit masks right after inversion.
  • Prefer unsigned types for bit operations.
  • Print binary values for debugging and teaching clarity.
  • Keep inversion and mask expressions simple and readable.

Try it now

Open the simulator workspace and inspect bit inversion with mask filtering.

Run in Simulator