Two's complement binary is a mathematical operation and number representation system where negative integers are encoded by inverting the bits of their positive counterpart and adding one, allowing microprocessors to handle subtraction using standard addition circuits. In a real circuit or embedded installation, this format dictates how Analog-to-Digital Converters (ADCs) and digital sensors transmit signed data over SPI or I2C; misinterpreting the Most Significant Bit (MSB) as a standard positive value will instantly turn a negative sensor reading into a massive, erroneous positive spike.

The Core Mechanism: A Worked Numeric Example

To understand how hardware encodes negative numbers, let's look at a concrete 16-bit example. Suppose you are reading a bipolar voltage signal from a Texas Instruments ADS1115 16-bit ADC, and the measured voltage translates to a decimal value of -42.

If the ADC used standard unsigned binary, it could only represent 0 to 65,535. Because it measures bipolar signals (both positive and negative voltages), it encodes the data in two's complement binary. Here is the exact mathematical sequence the silicon logic gates perform to store -42:

Step 1: Start with the positive binary equivalent.
+42 in 16-bit binary is: 0000 0000 0010 1010

Step 2: Invert all the bits (One's Complement).
Flip every 0 to 1, and every 1 to 0: 1111 1111 1101 0101

Step 3: Add 1 to the result (Two's Complement).
1111 1111 1101 0101 + 1 = 1111 1111 1101 0110

Final Hex Value: 0xFFD6

When your microcontroller reads the I2C register, it receives the bytes 0xFF and 0xD6. If you cast this raw data into an unsigned 16-bit integer (uint16_t), your code will read it as 65,494. If you cast it into a signed 16-bit integer (int16_t), the compiler recognizes the MSB (the leftmost '1') as a sign flag and correctly interprets the value as -42.

Where You Meet This in Practice: Sensors and ADCs

You will encounter this encoding scheme constantly when interfacing with real-world physical sensors that measure bidirectional phenomena. Here are the three most common bench scenarios where this format dictates your firmware design:

  • Bipolar ADCs (e.g., ADS1115, ADS1256): When measuring AC waveforms or differential voltages across a shunt resistor, the ADC outputs signed data. A 16-bit ADC outputs values from -32,768 to +32,767.
  • Accelerometers and Gyroscopes (e.g., MPU6050, LSM6DS3): These MEMS sensors measure acceleration and rotation in both directions along the X, Y, and Z axes. Tilting a board backward yields a negative raw register value, transmitted as a 16-bit two's complement integer over I2C.
  • 24-Bit Load Cell ADCs (e.g., HX711): This is where most hobbyists and junior engineers get trapped. The HX711 outputs 24-bit signed data. Because standard microcontrollers (like the ESP32 or Arduino Uno) process data in 8-bit, 16-bit, or 32-bit chunks, you must read the 24 bits into a 32-bit variable and manually perform sign extension.
The 24-Bit Sign Extension Gotcha: If your HX711 reads a negative weight (e.g., -150), the 24th bit (the MSB of the third byte) will be 1. If you just drop those 3 bytes into a 32-bit int32_t, the top 8 bits default to 0. The microcontroller reads it as a massive positive number (over 16 million). You must force the top 8 bits to 1 using a bitwise OR mask: if (val & 0x800000) val |= 0xFF000000;.

Sign-Magnitude vs. Two's Complement: The Common Confusion

The most frequent conceptual error in digital logic design is confusing two's complement with sign-magnitude representation. In sign-magnitude, the MSB simply acts as a plus/minus flag, while the remaining bits represent the absolute value.

Let's look at an 8-bit example to see why this matters:

Representation Binary (8-bit) Decimal Value Hardware Behavior
Sign-Magnitude 1010 1010 -42 MSB is 1 (negative), remaining bits are 42.
Two's Complement 1010 1010 -86 MSB is 1, indicating a negative two's complement integer.

Why do silicon designers universally choose two's complement over sign-magnitude for ALUs (Arithmetic Logic Units) and ADCs? Two reasons. First, sign-magnitude has two zeros: 0000 0000 (+0) and 1000 0000 (-0). Two's complement has only one zero, freeing up an extra value for the negative range. Second, in two's complement, the hardware adder circuit doesn't need to check the sign bit before performing math; adding a positive and a negative number works identically to adding two positive numbers, saving silicon area and clock cycles.

Embedded C Decision Tree: Handling Signed Binary Data

When writing firmware in C or C++ (via the Arduino framework or ESP-IDF), you must explicitly tell the compiler how to interpret the raw bytes arriving from your sensor. Use this decision path to select the correct data type and bitwise operation.

Sensor Bit-Depth Raw Register Read C/C++ Data Type Required Action
8-bit (e.g., 8-bit DAC) 1 Byte int8_t Direct cast. Range: -128 to +127.
16-bit (e.g., MPU6050, ADS1115) 2 Bytes (High/Low) int16_t Combine bytes: (high << 8) | low. Cast directly to int16_t.
24-bit (e.g., HX711, ADS1220) 3 Bytes int32_t Combine 3 bytes, then apply 0xFF000000 mask if MSB is 1.
32-bit (e.g., High-res DSP) 4 Bytes int32_t Direct cast. Ensure endianness (byte order) matches the sensor.
Default Recommendation: Stop using generic int or long variables for sensor data, as their bit-width changes depending on whether you are compiling for an 8-bit Arduino Uno or a 32-bit ESP32. Always default to fixed-width types: use int16_t for standard 16-bit I2C sensors, and explicitly cast to int32_t with bitwise sign-extension for 24-bit SPI ADCs.

Frequently Asked Questions

Why does my accelerometer read 65535 when I tilt it backward?

You are reading a 16-bit signed register into an unsigned variable. When the sensor outputs -1 (binary 1111 1111 1111 1111 or 0xFFFF), an unsigned 16-bit integer (uint16_t) interprets those exact same bits as 65,535. Change your variable declaration from unsigned int to int16_t to resolve this instantly.

How do I convert a floating-point voltage back to two's complement for a DAC?

If you are writing to a bipolar DAC (like the DAC8562) and need to output -1.5V from a ±5V range, first calculate the decimal fraction of your full scale. For a 16-bit DAC, -1.5V out of a 10V total span is -1.5 / 10.0 * 65536 = -9830. In C, simply cast this float to an int16_t. The compiler will automatically handle the two's complement binary conversion in the background, and you can then shift the bytes out over SPI.

Does endianness affect two's complement math?

Endianness (Big-Endian vs. Little-Endian) only dictates the order in which bytes are transmitted over the wire (e.g., whether the MSB or LSB arrives first on the I2C bus). It does not change the underlying two's complement math. However, if you reassemble the bytes in the wrong order in your microcontroller's memory, the sign bit will end up in the wrong position, completely corrupting your signed integer. Always check the sensor datasheet's 'Data Format' section to verify byte transmission order.