A sign number (or signed number) in digital electronics is a binary data format that reserves the most significant bit (MSB) as a sign indicator to represent both positive and negative values, enabling microcontrollers to process bipolar signals like AC waveforms and bidirectional currents. In physical circuits, voltage and current have physical polarity. In the digital domain of an ESP32 or Arduino, we must map that physical reality into binary code. What a sign number changes in a real installation is how your firmware interprets sensor data: it dictates whether a current transformer reading is mapped as power flowing from the grid (positive) or to the grid (negative), and it determines the directional logic for H-bridge motor drivers. What beginners commonly confuse it with is the physical polarity of a multimeter probe, or the outdated 'sign-magnitude' format, rather than the modern 'two's complement' standard used by all 32-bit ARM and Xtensa processors.
The Mechanics of Signed Binary Data
Microcontrollers do not natively understand a 'minus' symbol. They only see 1s and 0s. To represent negative numbers, digital systems use a convention where the leftmost bit (the Most Significant Bit, or MSB) acts as the sign bit. If the MSB is 0, the number is positive. If the MSB is 1, the number is negative.
However, modern microcontrollers do not just flip a sign bit and keep the rest of the magnitude identical. They use a mathematical system called two's complement. In two's complement, the MSB doesn't just mean 'negative'; it carries a negative weight equal to the highest power of two for that bit-width. This allows the microcontroller's Arithmetic Logic Unit (ALU) to use the exact same addition circuitry for both addition and subtraction, vastly simplifying silicon design.
When programming an ESP32 or Arduino, you define these ranges using standard integer types:
• 8-bit (
int8_t): -128 to +127• 16-bit (
int16_t): -32,768 to +32,767• 32-bit (
int32_t): -2,147,483,648 to +2,147,483,647
Worked Example: Reading Bipolar AC Current with an ADS1115
Let's look at a real-world scenario. You are building an AC power monitor using an ADS1115 16-bit external ADC to read a voltage from a current transformer (CT) burden resistor. The AC waveform swings above and below a 2.5V virtual ground bias.
The ADS1115 is configured for a Full Scale Range (FSR) of ±4.096V. Because it outputs a 16-bit sign number in two's complement format, the raw digital values map as follows:
+4.096V(or just below it) yields the maximum positive sign number: +32,7670.000Vyields exactly 0-4.096Vyields the minimum negative sign number: -32,768
The Math:
Suppose the AC waveform swings to a negative peak, and the ADC measures a physical voltage of -1.000V relative to the bias point. What is the raw sign number the microcontroller receives via I2C?
- Calculate the ratio of the measured voltage to the FSR:
-1.000V / 4.096V = -0.244140625 - Multiply by the total number of negative steps (32,768):
-0.244140625 * 32768 = -8000
The microcontroller receives the raw 16-bit sign number -8000. In binary (two's complement), this is represented as 1110 0000 1100 0000. Notice the leading 1, which immediately tells the processor this is a negative value.
Here is how you handle this in your Arduino/ESP32 C++ firmware:
#include <Wire.h>
#include <Adafruit_ADS1X15.h>
Adafruit_ADS1115 ads;
void setup() {
Serial.begin(115200);
ads.setGain(GAIN_ONE); // +/- 4.096V range
ads.begin();
}
void loop() {
// Read the raw 16-bit sign number
int16_t raw_adc = ads.readADC_Differential_0_1();
// Convert back to physical voltage using floating point math
float voltage = raw_adc * (4.096 / 32768.0);
Serial.print('Raw Sign Number: ');
Serial.println(raw_adc);
Serial.print('Calculated Voltage: ');
Serial.println(voltage, 4);
delay(100);
}
Where You Meet Sign Numbers in Practice
If you are strictly building simple DC circuits with basic sensors (like a photoresistor or a potentiometer), you can often get away with unsigned numbers. But sign numbers become mandatory the moment your circuit interacts with alternating or bidirectional phenomena.
1. AC Power and Energy Monitoring
When using a split-core CT sensor (like the SCT-013-000) to measure home mains current, the output is an AC waveform. To read this with a unipolar microcontroller ADC, you bias the signal to VCC/2. The ADC reads values above the bias as positive (current flowing one way) and below the bias as negative (current flowing the other way). Calculating Real Power (Watts) requires multiplying instantaneous voltage and current sign numbers; if you use unsigned math, your power calculations will be wildly incorrect during the negative half-cycles.
2. Bidirectional Motor Control
When driving a DC motor with an H-bridge (like the DRV8871 or L298N), you often use a signed PID (Proportional-Integral-Derivative) control loop. A positive sign number commands the motor to spin forward (PWM applied to IN1), while a negative sign number commands reverse (PWM applied to IN2). The firmware simply checks if the sign number is < 0 to determine the GPIO direction pin state.
3. Digital Audio and DSP
If you are capturing audio using an I2S MEMS microphone (like the INMP441) on an ESP32 I2S peripheral, the microphone outputs 24-bit audio data padded into a 32-bit signed integer (int32_t). Sound waves are inherently bipolar pressure variations. The sign number perfectly maps the compression (positive) and rarefaction (negative) phases of the acoustic wave.
Common Confusions: Sign-Magnitude vs. Two's Complement
The most frequent conceptual error hobbyists make is assuming that a sign number works like human handwriting: a minus sign followed by a magnitude. This is called sign-magnitude representation. In an 8-bit sign-magnitude system, 1000 0001 means '-1' (the first bit is the negative sign, the rest is '1').
However, sign-magnitude creates a massive headache for silicon logic: it results in two distinct binary representations for zero (0000 0000 for +0, and 1000 0000 for -0), and requires complex, separate subtraction circuitry.
Two's complement solves this. In 8-bit two's complement, 1111 1111 is '-1', and 1000 0000 is '-128'. There is only one zero (0000 0000). If you attempt to manually parse an I2C or SPI sensor's raw bytes by just 'flipping the MSB' to check for a negative, your math will fail. You must cast the combined bytes directly into a signed integer type (like int16_t) and let the C++ compiler handle the two's complement interpretation natively.
unsigned int (uint16_t) 'just to see the raw hex value' without understanding the conversion. A raw sign number of -1 (0xFFFF) cast to an unsigned 16-bit integer becomes 65535. This routinely causes 'out of bounds' errors in mapping functions like Arduino's map().
Frequently Asked Questions
What is the difference between a sign number and an unsigned number?
An unsigned number uses all available bits to represent magnitude, starting from zero and going up to the maximum limit (e.g., an 8-bit unsigned number ranges from 0 to 255). A sign number sacrifices half of its positive range to represent negative values by dedicating the Most Significant Bit to indicate polarity (e.g., an 8-bit signed number ranges from -128 to +127). Use unsigned for absolute measurements like temperature in Kelvin or raw potentiometer positions; use signed for bipolar measurements like AC voltage, audio waves, or relative positional errors.
Why do microcontrollers use two's complement instead of sign-magnitude?
Two's complement allows the microcontroller's Arithmetic Logic Unit (ALU) to use the exact same hardware adder circuits for both addition and subtraction. For example, adding +5 and -3 in two's complement binary works identically to adding +5 and +3, with the overflow bit simply discarded. Sign-magnitude would require the processor to check the sign bit first, compare magnitudes, and then decide whether to add or subtract the absolute values, requiring vastly more silicon gates and clock cycles. For a deep dive into the logic gates involved, All About Circuits provides an excellent breakdown of two's complement circuitry.
How do I read a negative sign number from an I2C ADC like the ADS1115?
You read the two 8-bit registers (High Byte and Low Byte) provided by the ADC and combine them into a single 16-bit signed integer variable. In C/C++, you do this by shifting the high byte left by 8 bits and bitwise-ORing it with the low byte, then casting the result to int16_t. Most modern libraries (like the Adafruit ADS1X15 library) handle this bitwise reconstruction internally and return a properly formatted int16_t sign number directly to your sketch.
What happens if I cast a signed number to an unsigned variable in C++?
The compiler does not change the underlying binary 1s and 0s in memory; it only changes how the processor interprets them. If you have an int16_t sign number holding the value -100 (binary 1111 1111 1001 1100) and you cast it to a uint16_t, the processor reads that exact same binary pattern as a positive number, resulting in 65,436. This is a primary cause of erratic behavior in PID loops and motor controllers when variable types are mismatched.






