When you are debugging an ESP32 I2C sensor register, configuring an Arduino PWM timer, or parsing a serial data stream, you need to instantly translate base-2 logic into base-10 and base-16 values. The binary numbers table below maps positional bit weights to their decimal and hexadecimal equivalents, alongside the maximum integer limits for standard microcontroller register widths. This reference is built on standard C99 <stdint.h> definitions and ISO/IEC 80000-13 binary prefix standards.
How to Read the Binary Numbers Table and Register Limits
To use this table effectively, you need to understand what each column represents and how it maps to the physical silicon on your workbench. The Bit Index (n) is the zero-indexed position of the bit, where Bit 0 is the Least Significant Bit (LSB). The Binary Weight is the decimal value of that single bit if it is set to HIGH (1). The Hexadecimal column shows how that weight is represented in base-16, which is the standard shorthand used in C/C++ code (prefixed with 0x).
Which Column Applies to Your Hardware Installation?
The 'Max Unsigned' and 'Max Signed' columns are entirely dependent on your microcontroller's architecture and the variable type you declare in your code:
- 8-Bit Systems (e.g., Arduino Uno/Nano with ATmega328P): You live in the
n=7row. Standardbyteoruint8_tvariables max out at 255. If you try to store 256, it overflows and wraps to 0. - 16-Bit Variables (Standard across most 8-bit and 32-bit boards): You look at the
n=15row. Standardunsigned int(on AVR) oruint16_tmaxes out at 65,535. This is critical when reading raw ADC values from a 16-bit sensor like the ADS1115. - 32-Bit Systems (e.g., ESP32-WROOM-32, Raspberry Pi Pico, ARM Cortex): You operate up to the
n=31row. A standardunsigned longoruint32_tholds up to 4,294,967,295. This is the native register width for ESP32 GPIO port manipulation and 32-bit timer counters.
| Bit Index (n) | Binary Weight (2^n) | Hexadecimal | Max Unsigned Value | Max Signed Value |
|---|---|---|---|---|
| 0 | 1 | 0x1 | 1 | 0 |
| 1 | 2 | 0x2 | 3 | 1 |
| 2 | 4 | 0x4 | 7 | 3 |
| 3 | 8 | 0x8 | 15 | 7 |
| 4 | 16 | 0x10 | 31 | 15 |
| 5 | 32 | 0x20 | 63 | 31 |
| 6 | 64 | 0x40 | 127 | 63 |
| 7 | 128 | 0x80 | 255 (8-bit max) | 127 |
| 8 | 256 | 0x100 | 511 | 255 |
| 12 | 4,096 | 0x1000 | 8,191 | 4,095 |
| 15 | 32,768 | 0x8000 | 65,535 (16-bit max) | 32,767 |
| 16 | 65,536 | 0x10000 | 131,071 | 65,535 |
| 24 | 16,777,216 | 0x1000000 | 33,554,431 | 16,777,215 |
| 31 | 2,147,483,648 | 0x80000000 | 4,294,967,295 (32-bit max) | 2,147,483,647 |
Source: Integer limits derived from standard C99 <stdint.h> specifications and NIST / ISO/IEC 80000-13 binary definitions.
Signed Ranges and Bit-Shifting: Modifying the Base Values
Just as wire ampacity tables require derating when ambient temperatures rise or conductors are bundled in a conduit, binary integer ranges 'derate'—specifically, they halve their positive capacity—when you switch from an unsigned to a signed data type. This is governed by two's complement formatting.
In two's complement, the Most Significant Bit (MSB) stops being a standard positive weight and becomes the sign bit. If you look at the n=15 row in the table above, an unsigned 16-bit integer (uint16_t) uses all 16 bits for positive magnitude, yielding a maximum of 65,535. However, a signed 16-bit integer (int16_t) sacrifices the top bit to indicate negative numbers. Consequently, the maximum positive value 'derates' to 32,767. If you attempt to store 32,768 in a signed 16-bit variable, it overflows into the sign bit and the microcontroller reads it as -32,768. This is the most common cause of erratic sensor readings when hobbyists use a standard int to store raw 16-bit unsigned ADC data.
Many 16-bit sensors (like the BME280 or MPU6050) transmit data as two separate 8-bit registers: a High Byte and a Low Byte. To reconstruct the full 16-bit value using the weights in the table, you must bit-shift the high byte left by 8 positions (multiplying it by 256, or
0x100) and bitwise-OR it with the low byte.uint16_t raw_value = (high_byte << 8) | low_byte;If
high_byte is 0x1A and low_byte is 0x4F, shifting 0x1A left by 8 yields 0x1A00. ORing it with 0x4F results in 0x1A4F (6,735 in decimal).
Bit-shifting (<< and >>) acts as a hardware-level multiplier and divider. Shifting a binary value left by n positions multiplies it by the Binary Weight of n (e.g., val << 3 multiplies by 8). Shifting right divides by that weight, discarding the remainder. This is vastly faster on 8-bit AVRs than using the * or / operators, which invoke heavy software math libraries.
What the Standard Binary Table Cannot Tell You
While the table above perfectly defines integer weights and limits, it leaves out two critical architectural realities that will break your code if ignored: Endianness and Floating-Point Representation.
Memory Endianness (Byte Order)
The table tells you that the hex value 0x12345678 requires 32 bits. It does not tell you how those bytes are physically arranged in the ESP32's SRAM. The ESP32 (Xtensa LX6/LX7 architecture) and ARM Cortex-M0+ (Raspberry Pi Pico) are Little-Endian. This means the Least Significant Byte is stored at the lowest memory address. If you cast a pointer to a 32-bit integer and read it byte-by-byte, you will read 0x78, then 0x56, then 0x34, then 0x12. Conversely, network protocols (like TCP/IP payloads) and some legacy Motorola-based systems use Big-Endian (Most Significant Byte first). When sending multi-byte binary data over UART or ESP-NOW, you must explicitly define and handle the byte order, or the receiving device will reconstruct a completely different number.
IEEE 754 Floating-Point Limits
This table applies strictly to integers. If you are working with float or double variables, the binary weights do not apply linearly. Floating-point numbers are encoded using the IEEE 754 standard, which divides the 32 bits into three distinct fields: 1 sign bit, 8 exponent bits, and 23 fraction (mantissa) bits. Because of this structure, a 32-bit float on an ESP32 only guarantees about 7 decimal digits of precision. If you attempt to store the integer 16,777,217 in a 32-bit float, it will silently round to 16,777,216 because the mantissa lacks the bit-depth to resolve the +1 increment. For high-precision timing or large cumulative counters (like millis() or energy monitoring), always use uint32_t or uint64_t integers instead of floats.






