A hexadecimal signed integer is a base-16 numbering format used to represent both positive and negative values in digital systems, relying on the most significant bit (MSB) and Two's Complement arithmetic to denote the sign. When you are debugging an I2C sensor bus or configuring a hardware timer on an ESP32, the raw data dumps in hex. If you misinterpret the sign bit, a simple sub-zero temperature reading or a backward motor encoder tick instantly turns into a massive, nonsensical positive number, leading to hours of frustrating debugging.

The Core Concept: How Hexadecimal Handles Negative Numbers

In digital logic, hardware registers do not have a dedicated 'minus sign' character. Instead, microcontrollers and sensors use a system called Two's Complement to map negative decimal numbers into hexadecimal signed formats. The rule is dictated by the Most Significant Bit (MSB). In a 16-bit register, if the MSB (bit 15) is 0, the number is positive. If the MSB is 1, the number is negative.

The Golden Rule of 16-Bit Hex:
Values from 0x0000 to 0x7FFF are positive (0 to 32,767).
Values from 0x8000 to 0xFFFF are negative (-32,768 to -1).

The most common confusion among hobbyists and junior engineers is mixing up hexadecimal signed and unsigned representations. Take the hex value 0xFFFF. If your code treats this as an unsigned 16-bit integer, it reads as 65,535. If your code correctly treats it as a hexadecimal signed integer, it reads as -1. In a real circuit or installation, treating a register as unsigned instead of signed changes a backward motor encoder tick or a sub-zero temperature reading into a massive positive voltage or position command, which can physically damage actuators or cause thermal runaway in control loops.

To understand the math without getting lost in binary, think of a 4-digit mechanical digital counter wheel. If the counter is at 0000 and you roll it backward by one click, it wraps around to 9999. In 4-digit hexadecimal signed math, rolling backward from 0x0000 by one yields 0xFFFF. The system's arithmetic logic unit (ALU) inherently understands that this wraparound represents -1.

Worked Example: Decoding a 16-Bit Signed Sensor Register

Let us look at a real-world scenario using the Texas Instruments TMP117, a high-accuracy digital temperature sensor. The TMP117 outputs temperature data over I2C as a 16-bit Two's Complement word. The resolution is exactly 0.0078125 °C per Least Significant Bit (LSB).

Suppose your logic analyzer captures the sensor returning the hex bytes 0xFF and 0x9C over the I2C bus. Combined, the raw register value is 0xFF9C.

The Incorrect (Unsigned) Path

If your microcontroller code reads this into an unsigned 16-bit integer (uint16_t), the decimal value is 65,436. Multiplying this by the sensor resolution (0.0078125) yields 511.2 °C. Unless your PCB is actively on fire, this data is useless.

The Correct (Hexadecimal Signed) Path

To manually convert 0xFF9C to a signed decimal, apply the Two's Complement reversal:

  1. Invert the bits (One's Complement): 0xFF9C becomes 0x0063.
  2. Add 1: 0x0063 + 0x0001 = 0x0064.
  3. Convert to decimal: 0x0064 is 100 in decimal.
  4. Apply the negative sign: Because the original MSB was 1, the value is -100.

Now, multiply the signed decimal (-100) by the resolution (0.0078125 °C/LSB). The final physical temperature is -0.78125 °C.

TMP117 Register Value Interpretation Matrix
Raw Hex RegisterUnsigned DecimalSigned DecimalPhysical Temp (°C)System State
0x7FFF32,76732,767+255.99Max Positive
0x0000000.00Freezing Point
0xFF9C65,436-100-0.78Sub-Zero (Correct)
0x800032,768-32,768-256.00Max Negative

Where You Meet Hexadecimal Signed Data in Practice

You will rarely need to calculate Two's Complement by hand on the bench, but you must know where the boundary between signed and unsigned data lives in your firmware and tooling.

I2C and SPI Sensor Debugging

When reading accelerometers (like the MPU6050) or magnetometers, the X, Y, and Z axes are inherently signed. Gravity pulling 'down' on the Z-axis yields a positive hex value, while flipping the board upside down yields a negative hex value (MSB = 1). If your logic analyzer software (like Saleae Logic 2 or Sigrok) is set to decode SPI/I2C words as 'Unsigned', your Z-axis graph will spike to 65,000+ every time you tilt the board backward. Always configure your protocol decoder's data type to 'Signed (Two's Complement)' when mapping physical vectors.

ESP32 Timer and DAC Offsets

When configuring hardware peripherals via the Espressif ESP-IDF or Arduino core, you often write directly to 32-bit registers. If you are setting a DC offset for a DAC to center an AC audio signal at 1.65V, you might need to inject a negative signed hex value into a calibration register to trim out factory voltage drift. Passing an unsigned cast of a negative number will rail the DAC to its maximum voltage, potentially blowing out downstream audio amplifiers.

Motor Control and Encoder Tracking

In BLDC motor control, quadrature encoders track position. Moving forward increments the counter; moving backward decrements it. The position variable must be a signed integer. If a 16-bit encoder counter wraps backward past zero, it outputs hexadecimal signed values in the 0x8000 to 0xFFFF range. Your PID control loop must accept these as negative positional errors, otherwise the motor controller will interpret a slight backward bump as a 60,000-tick forward error and violently overcorrect.

Frequently Asked Questions

How do I convert signed hex to decimal in C++ for Arduino or ESP32?

You do not need to write a custom math function to invert bits and add one. The C++ compiler handles Two's Complement natively if you use the correct data types. Simply cast the raw unsigned hex data into a signed integer type. For a 16-bit sensor, cast your uint16_t to an int16_t:

uint16_t raw_unsigned = 0xFF9C; // Reads as 65436
int16_t raw_signed = (int16_t)raw_unsigned; // Automatically becomes -100
float temperature = raw_signed * 0.0078125;

Why does my logic analyzer show 65535 instead of -1 for 0xFFFF?

Logic analyzers default to unsigned integer decoding because raw bus data (like memory addresses or RGB LED commands) is usually unsigned. To fix this, open your I2C, SPI, or CAN protocol decoder settings and change the 'Data Type' or 'Radix' dropdown from Unsigned Integer to Signed Integer (Two's Complement). The hex values on the bus do not change; only the software's interpretation of the MSB changes.

What is the lowest negative number in 16-bit signed hexadecimal?

The lowest (most negative) value is 0x8000, which equals -32,768 in decimal. Notice the asymmetry: the maximum positive value is 0x7FFF (+32,767). There is no positive 32,768 in 16-bit Two's Complement because 0x8000 is reserved for the minimum negative value. This edge case frequently causes overflow bugs if a developer attempts to negate -32,768 using standard absolute value functions, as the positive equivalent cannot fit in the same 16-bit register.