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
- Read source value from memory.
- ALU applies NOT to every bit position.
- Write inverted result to destination variable.
- Optional mask keeps only required bits for next logic step.
Bit Inversion Concept
sensorFlags = 10101100
~sensorFlags = 01010011
keep low nibble: 01010011 & 00001111 = 00000011In practice, inverted values are often masked because inversion flips all bits, not just the ones you care about.
~ vs ! (Comparison)
| Operator | Input type | Output behavior |
|---|---|---|
~ | Integer bits | Flips every bit |
! | Boolean expression | Returns 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
sensorFlagsstores original packed bits.invertedFlags = ~sensorFlagsflips all bits.selectedkeeps only two low bits from inverted value.lowNibblekeeps only lower 4 bits for easier observation.- If
selectedis non-zero, LED turns ON. - Serial prints original, inverted, and masked values in binary format.
What Happens Inside
- CPU loads
sensorFlags. - ALU applies bitwise NOT and writes
invertedFlags. - Mask operations clear irrelevant high bits.
- Branch logic checks masked result for LED output.
| Step | Expression | Binary result | Purpose |
|---|---|---|---|
| 1 | ~sensorFlags | 01010011 | Invert all bits |
| 2 | invertedFlags & 00000011 | 00000011 | Select low 2 bits |
| 3 | invertedFlags & 00001111 | 00000011 | Select 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.