Negative numbers in binary are most commonly represented using two's complement, a mathematical convention where the most significant bit (MSB) acts as a negative weight, allowing microcontrollers to perform subtraction using standard addition circuits. When you are writing firmware for an Arduino, ESP32, or Raspberry Pi Pico, understanding this isn't just an academic exercise—it is the exact difference between a robot smoothly reversing its motors and a robot violently spinning in circles because a -1 was misinterpreted by your code as 255.
The Core Mechanism: Two's Complement Math
In a standard unsigned 8-bit binary system, the bit weights from left to right are 128, 64, 32, 16, 8, 4, 2, and 1. This gives you a range of 0 to 255. However, microcontrollers need to represent values below zero without requiring a dedicated 'minus sign' bit, which would complicate the arithmetic logic unit (ALU) hardware.
Two's complement solves this by assigning a negative weight to the Most Significant Bit (MSB). In an 8-bit signed system, the MSB is worth -128 instead of +128. The remaining bits keep their positive weights (64, 32, 16, 8, 4, 2, 1). This gives a usable range of -128 to +127.
Worked Numeric Example: Encoding -42
Let's walk through the exact bench-level math to encode -42 into an 8-bit binary register. You can do this using the standard invert-and-add-one method:
- Start with positive 42: In binary, 42 is
0010 1010(32 + 8 + 2). - Invert all bits (One's Complement): Flip every 1 to 0 and 0 to 1. This yields
1101 0101. - Add 1 (Two's Complement): Add binary 1 to the inverted string.
1101 0101+0000 0001=1101 0110.
Verification: Let's read 1101 0110 back using our signed weights:
(-128) + 64 + 0 + 16 + 0 + 4 + 2 + 0 = -42.
0000 0101 (5) and 1111 1100 (-4), the raw binary sum is 1 0000 0001. The 9th carry bit simply falls off the end of the 8-bit register, leaving 0000 0001 (1). No special subtraction logic is required.
Where You Meet This in Practice
You will encounter signed binary data constantly when polling I2C and SPI sensors. The most common example is the MPU6050 6-axis accelerometer and gyroscope. When you read the Z-axis acceleration register, the sensor outputs a 16-bit signed integer.
If the sensor is sitting flat, Z reads roughly +16384 (representing +1G of gravity). If you flip the board upside down, the Z-axis reads -16384. In 16-bit two's complement hex, this is 0xBFFC.
Because the I2C bus transfers data 8 bits (one byte) at a time, your microcontroller receives two separate bytes: the High byte and the Low byte. A common beginner mistake is reading these bytes and casting them into an unsigned int. If you read -42 from a sensor and cast it as a uint16_t (unsigned 16-bit integer), the microcontroller interprets the MSB as +32768 instead of -32768. Your code will suddenly see a value of 65494. If this value is fed into a PID control loop for a balancing robot, the derivative term will spike, and your motors will max out instantly.
// Correct way to combine two I2C bytes into a signed 16-bit integer
#include
int16_t readSigned16Bit() {
uint8_t highByte = Wire.read(); // Reads MSB
uint8_t lowByte = Wire.read(); // Reads LSB
// Shift high byte left by 8, OR with low byte, then cast to signed
int16_t signedValue = (int16_t)((highByte << 8) | lowByte);
return signedValue;
}
What It Changes in a Real Circuit Installation
Binary representation directly dictates how you wire and configure physical hardware, particularly Digital-to-Analog Converters (DACs), motor drivers, and Analog-to-Digital Converters (ADCs).
1. Motor Drivers and H-Bridges: If you are using a smart motor controller (like a RoboClaw or an ESC) that accepts serial or I2C commands, sending a signed vs. unsigned integer dictates forward vs. reverse. A command of -127 tells the H-bridge to flip the polarity of the output terminals and apply 50% duty cycle in reverse. If your firmware strips the sign bit and sends 127, the motor drives forward when it should be braking or reversing.
2. Bipolar ADC Measurements: The ESP32's built-in ADC is strictly unipolar. It maps 0V to 3.3V into a 12-bit unsigned range of 0 to 4095. It cannot natively read negative voltages. If you are building an audio preamp or reading an AC current transformer that swings from -1.65V to +1.65V, you must install a DC bias circuit (a simple resistor voltage divider) to shift the physical signal up to 0V-3.3V. In firmware, you then read the unsigned ADC value and subtract the 2048 offset using signed math to reconstruct the negative halves of the AC waveform.
Common Confusions: Unsigned Overflow vs. Negative Values
What people commonly confuse two's complement with is unsigned integer overflow. They look identical in raw binary, but the C++ compiler treats them completely differently based on how you declare the variable.
Imagine an 8-bit odometer on a car. If you are at 0000 0000 (0) and you roll backward one mile:
- Unsigned logic: The odometer rolls backward from 0 to its maximum physical limit:
1111 1111(255). This is an underflow. - Signed logic (Two's Complement): The odometer rolls backward from 0 to
1111 1111, which the ALU interprets as-1.
The binary string 1111 1111 is exactly the same on the silicon. The 'meaning' only exists in the C++ data type declaration. If you declare your variable as uint8_t, the compiler generates machine code that treats the MSB as +128. If you declare it as int8_t, the compiler generates instructions that treat the MSB as -128. Always explicitly declare your sensor variables using fixed-width signed types like int16_t or int32_t to prevent the compiler from guessing your intent.
Frequently Asked Questions About Negative Numbers in Binary
How do you read negative numbers in binary from an Arduino I2C sensor?
You must read the high and low bytes from the I2C bus, combine them using a bitwise left-shift and OR operation, and explicitly cast the result to a signed integer type like int16_t. If you store the combined bytes in an unsigned int or standard int (which is 16-bit on AVR Arduinos but 32-bit on ESP32s), you risk sign-extension bugs or misinterpreting negative values as massive positive numbers.
Why does my ESP32 ADC show 4095 when the voltage drops below ground?
The ESP32 ADC is hardware-limited to reading voltages between 0V and 3.3V (mapped to 0-4095). It has no concept of negative binary numbers at the hardware level. If the input voltage drops below 0V (ground), the internal sampling capacitor cannot discharge below the silicon substrate ground, and the ADC will either read 0, saturate, or behave erratically depending on the specific ESP32 silicon revision. To read negative voltages, you must use an external op-amp circuit to level-shift the signal into the 0-3.3V window before it reaches the GPIO pin.
What is the lowest negative number an 8-bit binary register can hold?
The lowest value is -128. In two's complement, this is represented as 1000 0000. Notice that there is no positive equivalent (+128) in an 8-bit signed system, because the MSB is strictly reserved for the negative weight. The total range is asymmetrical: -128 to +127.
How do I convert a negative decimal to binary without a calculator?
Use the subtraction method. Write down the bit weights for your register size (e.g., for 8-bit: -128, 64, 32, 16, 8, 4, 2, 1). To convert -42:
1. Can you subtract -128 from -42? No, that makes it more negative. Put a 0.
2. Wait, the rule is: if the target number is less than or equal to the current weight, put a 1 and subtract the weight.
-42 is greater than -128, so we put a 1. (-42) - (-128) = +86.
Now we have 86 left. Next weight is 64. 86 >= 64, so put a 1. 86 - 64 = 22.
Next is 32. 22 < 32, put a 0.
Next is 16. 22 >= 16, put a 1. 22 - 16 = 6.
Next is 8. 6 < 8, put a 0.
Next is 4. 6 >= 4, put a 1. 6 - 4 = 2.
Next is 2. 2 >= 2, put a 1. 2 - 2 = 0.
Next is 1. 0 < 1, put a 0.
Result: 1101 0110.






