Signed binary numbers are a digital representation method where the most significant bit (MSB) acts as a sign indicator, allowing microcontrollers and logic circuits to process both positive and negative integers using Two's Complement arithmetic. In a real circuit or installation, choosing a signed data type over an unsigned one fundamentally changes how your analog-to-digital converter (ADC) scales bipolar voltages, how your motor controller interprets reverse direction, and whether an 8-bit register rolls over to 255 or drops to -1 when crossing zero. While beginners often confuse signed binary with 'signed magnitude' (where only the MSB flips and the rest remains standard binary), modern microcontrollers from the ATmega328P to the ESP32-S3 rely exclusively on Two's Complement for hardware-level math.
The Mechanics of Two's Complement: A Worked Example
To understand signed binary numbers, you must understand Two's Complement. It is the universal standard for representing negative integers in digital logic because it allows subtraction to be performed using standard addition circuits, eliminating the need for separate hardware subtractors.
To find the negative equivalent of any binary number, invert every bit (change 0s to 1s and 1s to 0s), then add 1 to the least significant bit (LSB).
Let us walk through a concrete numeric example: converting -42 into an 8-bit signed binary number.
- Start with positive 42 in binary: 42 is (32 + 8 + 2). In 8-bit binary, this is
0010 1010. - Invert the bits (One's Complement): Flip every 0 to 1 and 1 to 0. This gives
1101 0101. - Add 1 to the LSB:
1101 0101+0000 0001=1101 0110.
The final 8-bit signed binary representation of -42 is 1101 0110 (or 0xD6 in hexadecimal). Notice that the MSB is 1, which immediately tells the microcontroller's ALU (Arithmetic Logic Unit) that this is a negative number. If we read this same byte as an unsigned integer, the microcontroller would calculate 128 + 64 + 16 + 4 + 2 = 214. This distinction is where most embedded software bugs originate.
Where You Meet Signed Binary in Practice
You will encounter signed binary numbers the moment your circuit needs to measure a value that can swing above and below a reference point (usually ground or a mid-scale bias voltage).
I2C Accelerometers and Gyroscopes
Take the ubiquitous MPU-6050 6-axis IMU. When you read the accelerometer's X-axis data from registers 0x3B (MSB) and 0x3C (LSB), the sensor outputs a 16-bit Two's Complement integer. If the board is tilted slightly to the left, the physical acceleration is negative. The sensor outputs a value like 0xFF38. If your C code stores this in a uint16_t (unsigned), your program sees a massive positive spike of 65,336. If you store it in an int16_t (signed), the compiler correctly interprets the MSB and reads it as -200, which accurately reflects the slight negative tilt.
Bipolar ADC Measurements
When measuring AC waveforms or differential sensor bridges (like load cells), you need an ADC that can read negative voltages relative to a reference. The Texas Instruments ADS1115 16-bit ADC is a staple for this. When configured for differential measurement (e.g., AIN0 to AIN1), it outputs a 16-bit signed Two's Complement word. A negative voltage difference yields a negative signed integer, allowing you to accurately map the full ±6.144V range without needing to artificially bias the signal to 1.65V in hardware.
Signed vs. Unsigned: The Configuration Decision Path
Choosing the correct data type and hardware configuration is not a matter of preference; it is dictated by the physical reality of the signal you are measuring. Use the decision tree below to select your approach.
| Signal Condition | Hardware / Sensor Type | Required Data Type | Concrete Configuration Pick |
|---|---|---|---|
| Unipolar (0V to VCC) | Potentiometer, LDR, standard 0-3.3V MCU ADC | Unsigned | Use uint16_t in C; configure ESP32 ADC for 12-bit attenuation (0-4095). |
| Bipolar (Swings + and -) | AC current transformer, audio microphone, load cell | Signed | Use int16_t or int32_t; use an external signed ADC like the ADS1115 in differential mode. |
| Relative Position / Direction | Quadrature rotary encoder, joystick Y-axis | Signed | Use int32_t for the accumulator to prevent overflow during continuous rotation. |
| DAC Waveform Generation | Generating a sine wave via SPI DAC (e.g., MCP4922) | Offset-Binary (Hybrid) | Calculate in int16_t, then add 2048 (mid-scale offset) before casting to uint16_t for the DAC register. |
int16_t / int32_t) unless the datasheet explicitly states the output is unsigned. The vast majority of physical environment sensors use Two's Complement.
Hardware Pitfalls: Sign Extension and Overflow
Working with signed binary numbers introduces specific failure modes that do not exist in unsigned arithmetic. Recognizing these will save you hours of debugging on the bench.
The Sign Extension Bug
When you read an 8-bit signed value from a sensor and need to store it in a 16-bit variable, you cannot simply pad the upper 8 bits with zeros. You must perform sign extension—copying the MSB into all the new higher-order bits.
Imagine an 8-bit temperature sensor reads -1°C. In 8-bit Two's Complement, this is 1111 1111 (0xFF). If you naively assign this to a 16-bit unsigned integer, the compiler pads it with zeros: 0000 0000 1111 1111 (0x00FF), which equals +255. Your code now thinks the temperature is boiling. In embedded C, always cast through a signed type first: int16_t temp = (int8_t)raw_8bit_value;. The compiler will automatically handle the sign extension, resulting in 1111 1111 1111 1111 (0xFFFF), correctly preserving the -1 value.
Asymmetric Ranges and Overflow
An 8-bit unsigned integer holds 256 values (0 to 255). An 8-bit signed integer also holds 256 values, but the range is -128 to +127. The asymmetry exists because zero occupies one of the positive slots (0000 0000), leaving the MSB-heavy state (1000 0000) to represent -128. If your control loop attempts to invert -128 (multiply by -1), the result (+128) cannot fit in an 8-bit signed register. It will overflow and wrap back around to -128. For motor control PID loops, always use 16-bit or 32-bit signed integers for the math, even if the final PWM output is truncated to 8 bits.
FAQ: Signed Binary Numbers in Embedded Systems
Why don't microcontrollers just use a separate 'sign bit' and keep the rest of the number normal?
That system is called 'Signed Magnitude.' While it is easier for humans to read, it creates massive inefficiencies in silicon. Signed magnitude results in two different binary representations for zero (+0 and -0) and requires complex, separate hardware logic circuits for addition and subtraction. Two's Complement ensures there is only one zero and allows the ALU to use the exact same adder circuit for both addition and subtraction, saving die space and power.
How do I read a 16-bit signed integer from an I2C sensor using Arduino/ESP32 Wire library?
The Wire library reads data one byte at a time. You must read the MSB first, shift it left by 8 bits, and bitwise-OR it with the LSB. Crucially, you must cast the final result to a signed type.
int16_t val = (Wire.read() << 8) | Wire.read();
Because val is declared as int16_t, the C++ compiler automatically interprets the 16th bit as the sign bit.
Does the Espressif ESP-IDF ADC API return signed or unsigned values?
The ESP32's internal SAR ADC measures unipolar voltages (0V to VCC) and returns unsigned raw integers (e.g., 0 to 4095 for 12-bit resolution). If you need to measure a bipolar AC signal with an ESP32, you must either bias the AC signal to 1.65V using a voltage divider and subtract the DC offset in software, or bypass the internal ADC entirely and use an external signed ADC like the ADS1115 over I2C.






