The hexadecimal multiplication table is a base-16 arithmetic matrix used to calculate memory offsets, scale PWM duty cycles, and manipulate bitwise registers without converting to decimal. When you are writing bare-metal C for an ESP32 or configuring I2C addresses on an Arduino, dropping out of base-10 and thinking directly in hex prevents off-by-one errors and register truncation bugs. Below is the complete, unabbreviated reference matrix, followed by the decision paths you need to apply it to real hardware.
How to Read the Hexadecimal Multiplication Reference Table
Before jumping to the matrix, you need to understand the mechanics of base-16 arithmetic. Unlike the decimal system you use for wire ampacity or voltage drop, hexadecimal uses 16 distinct symbols: 0-9 followed by A-F (where A=10, B=11, C=12, D=13, E=14, F=15).
0x0A instead of 0xA) to maintain byte-alignment visibility, which is critical when packing data into 8-bit registers.
According to the ISO/IEC 9899 C Standard for integer constants, hex literals prefixed with 0x are treated as unsigned integers by default unless cast otherwise. Keep this in mind: the table below assumes unsigned, positive base-16 multiplication.
The Complete Base-16 Multiplication Matrix (0x0 to 0xF)
This is the full, un-truncated 16x16 matrix. Bookmark this section for quick lookups when calculating buffer sizes or color scaling on the bench.
| × | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
| 1 | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 0A | 0B | 0C | 0D | 0E | 0F |
| 2 | 00 | 02 | 04 | 06 | 08 | 0A | 0C | 0E | 10 | 12 | 14 | 16 | 18 | 1A | 1C | 1E |
| 3 | 00 | 03 | 06 | 09 | 0C | 0F | 12 | 15 | 18 | 1B | 1E | 21 | 24 | 27 | 2A | 2D |
| 4 | 00 | 04 | 08 | 0C | 10 | 14 | 18 | 1C | 20 | 24 | 28 | 2C | 30 | 34 | 38 | 3C |
| 5 | 00 | 05 | 0A | 0F | 14 | 19 | 1E | 23 | 28 | 2D | 32 | 37 | 3C | 41 | 46 | 4B |
| 6 | 00 | 06 | 0C | 12 | 18 | 1E | 24 | 2A | 30 | 36 | 3C | 42 | 48 | 4E | 54 | 5A |
| 7 | 00 | 07 | 0E | 15 | 1C | 23 | 2A | 31 | 38 | 3F | 46 | 4D | 54 | 5B | 62 | 69 |
| 8 | 00 | 08 | 10 | 18 | 20 | 28 | 30 | 38 | 40 | 48 | 50 | 58 | 60 | 68 | 70 | 78 |
| 9 | 00 | 09 | 12 | 1B | 24 | 2D | 36 | 3F | 48 | 51 | 5A | 63 | 6C | 75 | 7E | 87 |
| A | 00 | 0A | 14 | 1E | 28 | 32 | 3C | 46 | 50 | 5A | 64 | 6E | 78 | 82 | 8C | 96 |
| B | 00 | 0B | 16 | 21 | 2C | 37 | 42 | 4D | 58 | 63 | 6E | 79 | 84 | 8F | 9A | A5 |
| C | 00 | 0C | 18 | 24 | 30 | 3C | 48 | 54 | 60 | 6C | 78 | 84 | 90 | 9C | A8 | B4 |
| D | 00 | 0D | 1A | 27 | 34 | 41 | 4E | 5B | 68 | 75 | 82 | 8F | 9C | A9 | B6 | C3 |
| E | 00 | 0E | 1C | 2A | 38 | 46 | 54 | 62 | 70 | 7E | 8C | 9A | A8 | B6 | C4 | D2 |
| F | 00 | 0F | 1E | 2D | 3C | 4B | 5A | 69 | 78 | 87 | 96 | A5 | B4 | C3 | D2 | E1 |
Quick-Jump Bookmark Rows: The 0x8 row is your most common multiplier for bit-shifting operations (multiplying by 8 is equivalent to << 3). The 0xF row is essential for scaling 4-bit nibbles to maximum 8-bit PWM duty cycles (e.g., 0xF * 0x11 = 0xFF).
Which Column Applies to Your Embedded Installation?
Just as an electrician must choose the correct temperature column (60°C vs 75°C) in NEC Table 310.16 based on terminal ratings, an embedded engineer must choose the correct mathematical approach based on the hardware peripheral they are addressing. Here is how the table maps to real-world ESP32 and Arduino installations:
- Memory Address Offsets (Pointer Math): If you are calculating offsets in the ESP32 Technical Reference Manual memory map (e.g., GPIO registers starting at
0x3FF44000), you are multiplying struct sizes (usually0x04for 32-bit words) by an index. Use the 0x4 column to find your exact byte offset without decimal conversion. - PWM Duty Cycle Scaling: When driving WS2812B LEDs or analog dimming via the LEDC peripheral, you often need to scale a 0-15 (4-bit) input to a 0-255 (8-bit) output. You will heavily rely on the 0x11 multiplier (not in this base table, but derived from it) or use the 0xF row to find maximum nibble bounds.
- I2C Address Shifting: 7-bit I2C addresses must be shifted left by 1 (multiplied by
0x02) to make room for the R/W bit. Use the 0x2 column to instantly verify your shifted bus addresses (e.g., a0x3FLCD backpack becomes0x7Eon the wire).
How Overflow and Register Limits Modify the Base Value (The Hex Equivalent of Derating)
In electrical wiring, ampacity derating reduces your baseline current capacity based on ambient temperature or conduit fill. In hexadecimal microcontroller math, register truncation and integer overflow modify your base mathematical value based on bit-width limits.
The table above provides the pure mathematical product. However, microcontrollers do not have infinite precision. If your installation uses an 8-bit register (like the Arduino Uno's PORTB or an 8-bit timer), any product exceeding 0xFF will silently truncate, dropping the carry bit.
0x40 * 0x04, the table correctly reads 0x100. But if you store this in an 8-bit uint8_t variable, the leading 1 (the 9th bit) is discarded, and your hardware registers 0x00. Always cast to uint16_t or uint32_t before multiplying operands that might exceed 0xFF.
Furthermore, the C standard dictates integer promotion. If you multiply two 8-bit hex values, the compiler temporarily promotes them to 16-bit or 32-bit integers to perform the math, then truncates them back upon assignment. Knowing your target register width is just as critical as knowing the math itself.
Decision Tree: Choosing the Right Hex Math Approach
Use this decision path to terminate your debugging and pick the exact register size or operator for your code.
| Your Task / Symptom | Condition / Constraint | Concrete Pick / Action |
|---|---|---|
| Scaling a 4-bit sensor reading to 8-bit PWM | Input is 0x0 to 0xF |
Pick: Multiply by 0x11 (e.g., 0xA * 0x11 = 0xAA), not 0x10. This ensures 0xF maps exactly to 0xFF. |
| Calculating an array index offset in SRAM | Array elements are 32-bit integers | Pick: Multiply index by 0x04 using a uint32_t cast to prevent 8-bit overflow on arrays larger than 63 elements. |
| Shifting an I2C address for a logic analyzer | Address is 7-bit (e.g., 0x68 for MPU6050) |
Pick: Do not use the multiplication table. Use bitwise left-shift << 1 (Result: 0xD0). It is faster and prevents accidental arithmetic carry bugs. |
Product yields 0x100 but hardware reads 0x00 |
Target is an 8-bit hardware timer register | Pick: Switch variable type to uint16_t for the math, then bitwise mask & 0xFF if you intentionally want the lower byte. |
What the Table Cannot Tell You
While this matrix is definitive for unsigned base-16 multiplication, it has three blind spots that will brick your firmware if ignored:
- Endianness: The table gives you the mathematical value (e.g.,
0x1234). It does not tell you how that value is stored in memory. On a little-endian ESP32,0x1234is stored as0x34then0x12. On a big-endian network packet, it is0x12then0x34. Always verify your byte order when writing hex products to SPI flash or UART buffers. - Signed Arithmetic (Two's Complement): If you are multiplying negative numbers (e.g., motor direction control where reverse is negative), this unsigned table will yield incorrect results.
0xFFin an unsigned 8-bit context is 255; in a signedint8_tcontext, it is -1. Multiplying-1 * 2requires two's complement logic, not this matrix. - Floating-Point Hex: IEEE 754 floating-point numbers can be represented in hex (e.g.,
0x1.2p3), but they do not follow standard base-16 integer multiplication rules. If you are calculating PID controller coefficients, stick to decimal floats or fixed-point Q-format math.
Keep this matrix on your second monitor or printed above your workbench. When you stop translating hex to decimal in your head, your register-level debugging speed will double.






