table of contents feature [open]

Binary, Decimal, Hex & Octal: Converting Number Bases Explained

Table showing the same number written in binary, octal, decimal and hexadecimal notation

Last reviewed: October 1, 2026

Short answer: A number base (or radix) is how many distinct digits a positional number system uses. Decimal uses ten (0-9), binary two (0-1), octal eight (0-7) and hexadecimal sixteen (0-9 and A-F). To convert a decimal number to another base, divide by the base repeatedly and read the remainders from last to first. To convert back, multiply each digit by its place value and add the results. Binary, octal and hex convert to each other without arithmetic: one hex digit is exactly four bits and one octal digit is exactly three bits.

Example: decimal 156 is 10011100 in binary, 234 in octal and 9C in hex. All four are the same quantity written in different notations.

This guide walks through every common conversion with worked examples, then covers fractions, negative numbers, where each base shows up in real code, and the built-in conversion functions in JavaScript and Python. If you only need a result, a free number base converter that runs in the browser will do the job in a second; the methods below are for when you want to understand, check or reproduce that result yourself.

What a number base is

A base, also called a radix, is the number of digit symbols a positional system has, and it is also the factor by which each position is worth more than the one to its right. In decimal, each step left multiplies the place value by 10; in binary by 2; in hex by 16.

Take the decimal number 507. It means 5 hundreds, 0 tens and 7 ones:

507 = 5 x 10^2 + 0 x 10^1 + 7 x 10^0
    = 500     + 0        + 7

The same rule works in any base. The binary number 1101 is 1 x 2^3 + 1 x 2^2 + 0 x 2^1 + 1 x 2^0 = 8 + 4 + 0 + 1 = 13. The value does not change when you change the base; only the notation does. That is why a string of digits such as 10 is ambiguous until you know its base: it is ten in decimal, two in binary, eight in octal and sixteen in hex.

The four bases and how languages write them

Because bare digits are ambiguous, programming languages mark non-decimal literals with a prefix: 0b for binary, 0o for octal and 0x for hex in both JavaScript and Python. C uses 0x for hex and a plain leading 0 for octal.

BaseNameDigitsJavaScriptPython 3C
2Binary0, 10b10100b10100b1010 (documented by GCC as an extension)
8Octal0-70o7550o7550755 (leading zero)
10Decimal0-9493493493
16Hexadecimal0-9, A-F0x1ED0x1ED0x1ED

A few details worth knowing, all taken from the language documentation:

  • Case does not matter. In JavaScript and Python the prefix letter and the hex digits A-F can be upper or lower case, so 0XfF equals 0xff.
  • Legacy octal in JavaScript. A literal with a bare leading zero such as 0777 is a deprecated octal form (511 in decimal) and is a syntax error in strict mode. If it contains an 8 or 9, as in 0888, it is read as decimal instead. Use 0o.
  • Python 3 rejects leading zeros. 0123 is not a valid literal; the reference says this avoids confusion with the C-style octal literals Python used before version 3.0.
  • C still treats a leading zero as octal. In C, 034 is 28, not 34.
  • Digit separators. Both JavaScript and Python allow underscores between digits for readability, for example 0b1010_0001 or 0xA0_B0_C0.

Reference table: 0 to 16 in all four bases

Memorizing the first sixteen rows is the single most useful thing you can do, because every hex digit maps to one row.

DecimalBinaryOctalHex
0000000
1000111
2001022
3001133
4010044
5010155
6011066
7011177
81000108
91001119
10101012A
11101113B
12110014C
13110115D
14111016E
15111117F
16100002010

Decimal to binary and back

To go from decimal to binary, divide by 2 repeatedly and collect the remainders; to go back, add up the place values of the 1 bits.

Decimal to binary: repeated division

Divide the number by 2, write down the remainder, and repeat with the quotient until the quotient is 0. The remainders, read from the last one to the first, are the binary digits. Here is 156:

156 / 2 = 78  remainder 0
 78 / 2 = 39  remainder 0
 39 / 2 = 19  remainder 1
 19 / 2 =  9  remainder 1
  9 / 2 =  4  remainder 1
  4 / 2 =  2  remainder 0
  2 / 2 =  1  remainder 0
  1 / 2 =  0  remainder 1

Read upward: 10011100

So 156 in decimal is 10011100 in binary. The first remainder is the least significant bit, which is why you read the column from the bottom up.

Binary to decimal: place values

Write the powers of 2 under each bit, starting with 1 on the right, and add the ones that sit under a 1 bit. Here is 101101:

bit:     1   0   1   1   0   1
value:  32  16   8   4   2   1

32 + 8 + 4 + 1 = 45

Decimal to hex and back

The method is identical to binary, but you divide by 16 and the place values are powers of 16 (1, 16, 256, 4096, ...). Remainders from 10 to 15 are written as A to F.

Decimal to hex

Convert 748:

748 / 16 = 46  remainder 12  (C)
 46 / 16 =  2  remainder 14  (E)
  2 / 16 =  0  remainder  2  (2)

Read upward: 2EC

So 748 is 0x2EC.

Hex to decimal

Convert 2AF. The digits are 2, A (10) and F (15):

2 x 256 = 512
A x  16 = 160   (10 x 16)
F x   1 =  15   (15 x 1)

512 + 160 + 15 = 687

Octal works the same way with 8: octal 755 is 7 x 64 + 5 x 8 + 5 = 448 + 40 + 5 = 493.

Binary, hex and octal by digit grouping

Because 16 is 2^4 and 8 is 2^3, you can convert between binary and hex or octal by regrouping bits, with no division at all. Use groups of four bits for hex and groups of three for octal, always starting from the right.

Binary to hex and back (4-bit groups)

Convert 1011011110 to hex. Split into groups of four from the right and pad the leftmost group with zeros:

1011011110  ->  0010 1101 1110
                   2    D    E

Result: 2DE  (decimal 734)

Going the other way, replace each hex digit with its four bits. 9C becomes 1001 1100, which is the 156 from earlier.

Binary to octal and back (3-bit groups)

Convert 110101011 to octal:

110101011  ->  110 101 011
                 6   5   3

Result: 653  (decimal 427)

In reverse, octal 755 expands to 111 101 101.

Hex to octal via binary

There is no direct digit-for-digit mapping between hex and octal, so go through binary: expand to bits, then regroup. Convert 1F4:

1F4          ->  0001 1111 0100      (4 bits per hex digit)
drop padding ->  111110100
regroup by 3 ->  111 110 100
                   7   6   4

Result: octal 764  (decimal 500)

Fractions and why 0.1 is not exact

Digits after the point use negative powers of the base, and a fraction that terminates in decimal may repeat forever in binary. That is the reason 0.1 + 0.2 does not print exactly 0.3 in most languages.

In binary the places after the point are worth 1/2, 1/4, 1/8 and so on. The decimal fraction 0.625 is 1/2 + 1/8, so it is exactly 0.101 in binary. To convert, multiply by 2 repeatedly and take the integer parts: 0.625 x 2 = 1.25 (1), 0.25 x 2 = 0.5 (0), 0.5 x 2 = 1.0 (1).

Try the same with 0.1 and it never ends. The Python tutorial shows that 1/10 in binary is the repeating fraction 0.0001100110011.... A float has limited room, so the value is rounded. Python floats use IEEE 754 binary64 (double precision), which keeps 53 bits of precision, and the tutorial gives the stored value of 0.1 as the fraction 3602879701896397 / 2^55, slightly more than one tenth. The practical rules:

  • Do not compare floats for exact equality (Python offers math.isclose()); for money use integers in the smallest unit or a decimal type. This is a property of binary floating point, not a bug in one language.

Negative numbers and two's complement

Computers usually store signed integers in two's complement: to negate a number at a fixed width, invert every bit and add 1. The leftmost bit then acts as the sign, and an 8-bit value covers -128 to 127.

Worked example: -42 in 8 bits.

 42          = 0010 1010
invert bits  = 1101 0101
add 1        = 1101 0110   ->  0xD6

Read as an unsigned byte, 11010110 is 214, and 214 = 256 - 42. That is the general rule: in n bits, -x is stored as 2^n - x. To decode a pattern whose top bit is 1, apply the same invert-and-add-1 steps and put a minus sign in front. Two more landmarks: 11111111 is -1 and 10000000 is -128.

The width matters: -42 in 16 bits is 0xFFD6. Conversion functions generally do not print two's complement. MDN documents that (-10).toString(2) returns "-1010", and Python's bin(-10) returns '-0b1010'. JavaScript bitwise operators do work on 32-bit two's complement integers, which is why ~x equals -(x + 1).

Bits, nibbles, bytes and why hex is compact

A bit is one binary digit, a nibble is four bits and a byte is eight bits. One hex digit represents exactly one nibble, so any byte fits in exactly two hex digits, from 00 to FF (0 to 255).

That alignment is why hex is the default for raw data. The 32-bit value 11001010111111101011101010111110 is hard to read or copy correctly; as CAFEBABE it is eight characters, and each pair is one byte. Decimal digits do not line up with byte boundaries, and octal digits cover three bits, which does not divide evenly into 8.

Where each base is used

Binary is for thinking about individual bits, octal survives mainly in Unix file permissions, and hex is used wherever bytes are shown to humans.

Binary: bitmasks and flags

When each bit is an independent on/off option, binary literals make the intent visible:

const READ  = 0b100;  // 4
const WRITE = 0b010;  // 2
const EXEC  = 0b001;  // 1

let perms = READ | WRITE;        // 0b110 = 6
const canWrite = (perms & WRITE) !== 0;  // true
perms = perms & ~WRITE;          // 0b100 = 4

Octal: Unix file permissions

Unix permissions are three groups of three bits (owner, group, others), which is why octal fits them perfectly. POSIX defines the chmod octal bits: 0400, 0200 and 0100 are read, write and execute for the owner; 0040, 0020, 0010 for the group; 0004, 0002, 0001 for others. Within each digit, read is 4, write is 2 and execute is 1.

ModeBinaryOwnerGroupOthersTypical use
755111 101 101rwx (4+2+1)r-x (4+1)r-x (4+1)Directories, executable scripts
644110 100 100rw- (4+2)r-- (4)r-- (4)Regular files

In code, remember the prefix: 0o755 is decimal 493 and 0o644 is decimal 420. Passing plain 755 to an API that expects a mode sets a different, wrong set of bits.

Hex: colors, addresses, code points and byte dumps

  • CSS colors. In #RRGGBB notation each pair is one channel from 00 to ff (0 to 255). #FF8800 breaks down as red FF = 255, green 88 = 136 (8 x 16 + 8) and blue 00 = 0, the same color as rgb(255 136 0). The three-digit shorthand doubles each digit, so #F80 is the same color. Hex colors are case-insensitive.
  • Memory addresses. Debuggers and crash logs print addresses in hex.
  • MAC addresses. A 48-bit MAC address is six octets, each written as two hex digits. RFC 7042 writes them separated by hyphens, as in 00-00-5E-00-00-00; colons are a common alternative.
  • Unicode code points. Code points are written as U+ followed by hex, and the Unicode codespace runs from 0 to 10FFFF. U+0041 is decimal 65 (the letter A) and U+1F600 is decimal 128512.
  • Byte dumps. Hex dump tools, hashes and binary file signatures show two hex digits per byte.

Converting in JavaScript and Python

Both languages have built-in conversions, so you rarely need to implement the division method yourself. Parsing takes a string plus a base; formatting takes a number plus a base.

JavaScript

// String -> number: always pass the radix
parseInt("ff", 16);      // 255
parseInt("1010", 2);     // 10
parseInt("755", 8);      // 493
parseInt("0xFF");        // 255 (0x prefix is detected)
Number("0b1010");        // 10  (Number understands 0b and 0o)

// Number -> string
(255).toString(2);       // "11111111"
(255).toString(16);      // "ff"
(493).toString(8);       // "755"
(5).toString(2).padStart(8, "0");   // "00000101"
(255).toString(16).toUpperCase();   // "FF"

// 8-bit two's complement of a negative number
(-42 & 0xFF).toString(2);   // "11010110"

Per MDN, the radix must be between 2 and 36, toString() returns lowercase letters, and parseInt() does not understand the 0b or 0o prefixes. It stops at the first character that is invalid for the radix, so parseInt("15px", 10) is 15 and parseInt("2", 2) is NaN.

Python

# int -> string (with prefix)
bin(10)     # '0b1010'
oct(493)    # '0o755'
hex(255)    # '0xff'

# int -> string (no prefix, padding, upper case)
format(255, 'b')     # '11111111'
format(5, '08b')     # '00000101'
format(255, 'X')     # 'FF'
f'{255:#x}'          # '0xff'

# string -> int
int('ff', 16)        # 255
int('755', 8)        # 493
int('0b1010', 0)     # 10  (base 0 reads the prefix)
int('01110011', base=2)   # 115

# 8-bit two's complement of a negative number
format(-42 & 0xFF, '08b')   # '11010110'

The Python documentation states that int() accepts bases 0 and 2 to 36, that base 0 interprets the string like a code literal using its prefix, and that strings for bases 2, 8 and 16 may optionally carry the 0b, 0o or 0x prefix.

Common mistakes

Most base-conversion bugs come from an implied base, a wrong grouping direction or a number that is too large for the type.

MistakeWhat happensFix
Calling parseInt() without a radixThe base is guessed from the string: "0x1F" is read as hex, everything else as decimalAlways pass the radix: parseInt(s, 10)
Passing parseInt straight to map()["10","10","10"].map(parseInt) gives [10, NaN, 2] because the array index is used as the radixmap(s => parseInt(s, 10)) or map(Number)
Leading-zero literals0755 is octal in C and in non-strict JavaScript, a syntax error in strict mode and in Python 3Write 0o755; never pad decimal literals with zeros
Grouping bits from the left1011011110 grouped left to right gives the wrong hex digitsGroup from the right and pad the leftmost group
Expecting two's complement from toString(2) or bin()You get a minus sign plus the magnitudeMask to the width first, for example n & 0xFF
Parsing large hex values into a JavaScript NumberIntegers above Number.MAX_SAFE_INTEGER (2^53 - 1 = 9007199254740991) lose precisionUse BigInt: BigInt("0x1fffffffffffff")

On the precision limit: MDN explains that JavaScript numbers are IEEE 754 doubles, so Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2 evaluates to true, and a 64-bit ID or hash does not fit. BigInt values are written with an n suffix (0xFFn), support toString(radix), and cannot be mixed with Numbers in arithmetic.

Mental shortcuts

  • Know the powers of 2: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024. Most small conversions are sums of these.
  • Know four hex anchors: F = 15, 10 = 16, 80 = 128, FF = 255. 100 = 256 and 400 = 1024.
  • Two-digit hex to decimal: first digit times 16 plus the second. C8 = 12 x 16 + 8 = 200.
  • All ones: n one-bits equal 2^n - 1. 1111 = 15, 11111111 = 255.
  • Octal permissions: 4 read, 2 write, 1 execute; 7 is all, 6 is read-write, 5 is read-execute.

Practice problems

Try each one on paper before checking the answer.

#ProblemAnswer
1Decimal 200 to binary11001000
2Decimal 200 to hex and octalC8 and 310
3Binary 11110000 to decimal240
4Decimal 1000 to hex3E8
5Hex BEEF to decimal48879
6Hex CAFE to binary1100 1010 1111 1110
7Hex CAFE to octal145376
8-100 as an 8-bit two's complement byte10011100 (0x9C)

Note that the answer to problem 8 is the same bit pattern that meant 156 earlier: the type, not the bits, decides whether a byte is 156 or -100.

FAQ

How do I convert decimal to binary by hand?

Divide the number by 2 repeatedly, writing down each remainder, until the quotient is 0. Then read the remainders from last to first. For 13: 13/2 = 6 r 1, 6/2 = 3 r 0, 3/2 = 1 r 1, 1/2 = 0 r 1, giving 1101.

Why do programmers use hexadecimal instead of binary?

Hex is four times shorter and maps cleanly onto bytes: one hex digit is four bits, two hex digits are one byte. You keep the ability to see bit patterns without reading long strings of ones and zeros.

What do 0x, 0b and 0o mean?

They are literal prefixes that tell the language which base follows: 0x hexadecimal, 0b binary and 0o octal. JavaScript and Python support all three.

Does parseInt("08") treat the string as octal?

No. MDN states that parseInt() does not treat strings starting with 0 as octal, so parseInt("08") is 8. Passing the radix explicitly, as in parseInt("08", 10), removes any doubt.

Why is 0.1 + 0.2 not equal to 0.3?

Neither 0.1 nor 0.2 has an exact binary representation, so each is stored as a nearby IEEE 754 value and the tiny errors show up in the sum. Compare with a tolerance or use integer or decimal arithmetic.

How do I convert a negative number to binary?

Pick a width, write the positive value in that many bits, invert all bits and add 1. For -42 in 8 bits the result is 11010110. In code, mask the value to the width before formatting, such as n & 0xFF.

What does chmod 755 mean?

Each octal digit is three permission bits. 7 is read, write and execute for the owner; 5 is read and execute for the group; the last 5 is read and execute for everyone else.

Can JavaScript convert hex numbers larger than 2^53?

Not with Number, which loses precision above Number.MAX_SAFE_INTEGER. Use BigInt: BigInt("0xffffffffffffffff").toString(10) handles a full 64-bit value exactly.

Conclusion

Every base conversion comes down to two ideas: place values (divide to convert out of decimal, multiply and add to convert into it) and bit grouping (four bits per hex digit, three per octal digit). Learn the 0 to 15 table, always state the base when parsing, and mind the width when negative numbers are involved. For everyday work a base converter tool or the built-in functions shown above are faster and less error-prone than hand arithmetic; you can find this one alongside other free developer tools.

Sources referenced in this guide

Previous Post Next Post