Number Base Converter: Binary, Hex, Decimal, Any Base
Convert between binary, octal, decimal, hexadecimal and any base from 2 to 36, with two’s complement views at 8, 16, 32 and 64 bits and a bitwise playground. Every conversion runs on BigInt, so a 64-bit value comes back exact rather than rounded — which is not true of most converters you will find.
Every conversion runs on BigInt, so there is no 253 ceiling — a full 64-bit hex value converts to its exact decimal digits rather than a rounded approximation. Whitespace, underscores, commas and 0x / 0b / 0o prefixes are accepted and ignored.
Bit representation
Bits are grouped in nibbles because one hexadecimal digit is exactly four bits — so two hex digits are exactly one byte, and you can read hex off a bit pattern without doing any arithmetic. That one-to-one mapping is the entire reason bytes are written in hex rather than decimal.
| Width | Hex | Binary | As unsigned | As signed | Fits? |
|---|---|---|---|---|---|
| 8-bit | ff | 1111 1111 | 255 | -1 | exact |
| 16-bit | 00ff | 0000 0000 1111 1111 | 255 | 255 | exact |
| 32-bit | 000000ff | 00000000 00000000 00000000 11111111 | 255 | 255 | exact |
| 64-bit | 00000000000000ff | 00000000 00000000 00000000 00000000 00000000 00000000 00000000 11111111 | 255 | 255 | exact |
There is no such thing as “the” binary form of a negative number — two's complement is width-dependent. −1 is ff in 8 bits, ffff in 16, ffffffff in 32 and ffffffffffffffff in 64. Rows marked truncated mean the value does not survive that width: the high bits are discarded, which is exactly what happens when you assign it to a smaller integer type.
Bitwise playground
| A | 1100 1101 |
| & B | 0000 1111 |
| = | 0000 1101 |
⚠️ At 8 bits the top bit of A is set, so this register holds -51 when read as signed and 205 when read as unsigned. That is why the arithmetic and logical right shifts give different answers here — the same bits, two different meanings.
Arithmetic vs logical right shift. An arithmetic shift (>> in C, Java and Rust on signed types) copies the sign bit back in, so −8 >> 1 stays −4 and the number keeps its sign. A logical shift (>>> in Java and JavaScript, >> in C on unsigned types) feeds in zeros, so the same −8 becomes a very large positive number. Pick the wrong one on a negative value and you get a nonsense result rather than an error — try it above with A = −8 at 8-bit. Left shift is unambiguous, but bits pushed past the top of the register are simply lost, which is where silent overflow comes from.
| Width | Bytes | Hex digits | Unsigned range | Signed range |
|---|---|---|---|---|
| 8-bit | 1 | 2 | 0 – 255 | -128 – 127 |
| 16-bit | 2 | 4 | 0 – 65,535 | -32,768 – 32,767 |
| 32-bit | 4 | 8 | 0 – 4,294,967,295 | -2,147,483,648 – 2,147,483,647 |
| 64-bit | 8 | 16 | 0 – 18,446,744,073,709,551,615 | -9,223,372,036,854,775,808 – 9,223,372,036,854,775,807 |
A JavaScript Number is a 64-bit float and only holds integers exactly up to 253 − 1 (9,007,199,254,740,991). Anything larger — a 64-bit ID, a hash, a timestamp in nanoseconds — silently rounds. This converter uses BigInt throughout, so those values stay exact.
Bases 2 through 36 are supported (base 36 uses 0–9 then a–z, one character per digit). Everything runs locally in your browser — nothing you type is uploaded, logged or sent anywhere, so it is safe to paste values from private systems.
TL;DR
Most browser base converters call parseInt, which produces a JavaScript Number — a float that stops being exact above 253 − 1. Feed one 0xFFFFFFFFFFFFFFFF and it answers 18,446,744,073,709,552,000 when the true value is 18,446,744,073,709,551,615. No warning, no error, just a number that is 385 too big. This tool converts with BigInt, which has no size limit, so the answer is exact at every magnitude. It also shows the two’s complement bit pattern at four widths and lets you run AND, OR, XOR, NOT and all three shifts inside a fixed register width.
The 64-bit test most converters fail
A JavaScript Number is an IEEE 754 double. It has 53 bits of significand, so it represents every integer up to 9,007,199,254,740,991 perfectly and then starts skipping. Above that boundary the nearest representable value is used silently. That is fine for prices and counters and catastrophic for the things people actually paste into a base converter: 64-bit database keys, Discord and Twitter snowflake IDs, hashes, memory addresses, register dumps.
Try these three in any converter you are considering. If it returns the right-hand column, its arithmetic is running through a float and you cannot trust it with anything wide.
| Input | Exact (BigInt) | What a Number returns | Error |
|---|---|---|---|
| 0xFFFFFFFFFFFFFFFF | 18446744073709551615 | 18446744073709552000 | +385 |
| 0xDEADBEEFCAFEBABE | 16045690984503098046 | 16045690984503097000 | −1,046 |
| 9007199254740993 | 9007199254740993 | 9007199254740992 | −1 |
The middle column is what this page returns. The third row is the smallest possible demonstration: 9,007,199,254,740,993 is 253 + 1, the very first integer a double cannot hold, and it quietly becomes its even neighbour. Strictly, the double behind the first row holds 264 and JavaScript prints the shortest decimal that round-trips to it, which is why you see a run of zeros where real digits should be — a useful tell that a value has been through a float.
Why bytes are written in hex and never in decimal
Sixteen is 24, so one hexadecimal digit is exactly four bits and two hex digits are exactly one byte. That is the whole reason hex won. Converting hex to binary needs no arithmetic at all: swap each digit for its four-bit group and stop. 0xCD is 1100 1101, and you can read it back digit by digit without carrying anything. Decimal has no such alignment — 10 is not a power of two, so the digit boundaries and the bit boundaries never line up, and 205 tells you nothing about which bits are set.
Octal has the same property with three bits per digit, which is why Unix file permissions are octal: 755 is 111 101 101, three groups of read-write-execute, one group per digit. It fits three-bit fields perfectly and eight-bit bytes badly, which is why it survives in permissions and almost nowhere else. This is also the reason a leading 0 means octal in C: a habit from an era when word sizes were multiples of three.
The table below is the set of values worth memorising. Once you know the boundaries at 15, 255 and 65,535, most bit-twiddling stops requiring a calculator.
| Decimal | Binary | Octal | Hex | Why it matters |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | Zero is zero in every base |
| 1 | 1 | 1 | 1 | One is one in every base |
| 8 | 1000 | 10 | 8 | First value needing two octal digits |
| 15 | 1111 | 17 | F | One full nibble — the largest single hex digit |
| 16 | 10000 | 20 | 10 | First value needing two hex digits |
| 64 | 1000000 | 100 | 40 | 2⁶ — the usual CPU cache-line size in bytes |
| 127 | 1111111 | 177 | 7F | Largest signed byte, and the last ASCII code |
| 128 | 10000000 | 200 | 80 | Sign bit only — reads as −128 in a signed byte |
| 255 | 11111111 | 377 | FF | Largest unsigned byte, all eight bits set |
| 256 | 100000000 | 400 | 100 | One past a byte — the first 9-bit value |
| 1023 | 1111111111 | 1777 | 3FF | All ten bits set — the mask for a 10-bit field |
| 1024 | 10000000000 | 2000 | 400 | 2¹⁰, one kibibyte |
| 65535 | 1111111111111111 | 177777 | FFFF | Largest unsigned 16-bit value |
Hex is shown uppercase here for legibility; the tool has a case toggle, since Unix tooling tends to emit lowercase and Windows and older documentation tend to emit uppercase. The value is identical either way.
The same eight bits are 205 or −51
11001101 does not have a value. It has two, and which one you get depends entirely on how the surrounding code declared the variable. Read as an unsigned byte it is 205. Read as a signed byte in two’s complement it is −51. Nothing in the bits distinguishes the two cases — the type does, and a mismatched type is why a sensor reading or a byte from a network packet sometimes arrives as a large positive number where a small negative one was expected.
Two’s complement is the near-universal way signed integers are stored. The top bit carries a negative weight instead of a positive one: in a byte it is worth −128 rather than +128, and the remaining bits add as usual. So 11001101 is −128 + 64 + 8 + 4 + 1 = −51. Three consequences fall out of that one rule:
- Addition needs no special case. The same adder circuit handles signed and unsigned values, which is precisely why this representation beat sign-and-magnitude.
- −1 is all ones at every width:
0xFF,0xFFFF,0xFFFFFFFF. That is why an error return of −1 shows up in a hex dump as a solid run of F. - The range is lopsided. A signed byte runs −128 to 127, not −127 to 127 — there is one more negative value than positive, and negating the most negative number overflows back to itself.
The two’s complement panel in the tool shows all of this at once: enter any value and you get its bit pattern, its unsigned reading and its signed reading at 8, 16, 32 and 64 bits, with a marker on the widths it does not fit.
Two right shifts that look identical and are not
An arithmetic right shift copies the sign bit back in as it shifts, so a negative number stays negative and the operation keeps behaving like division by two. A logical right shift feeds in zeros, so the sign bit is destroyed and a negative value becomes a large positive one. Both are written with angle brackets, both compile, and neither raises an error. Take −8 in a single byte:
Which operator gives you which depends on the language. In C and C++ the shift is chosen by the operand type: >> on a signed int is arithmetic, on an unsigned int it is logical, so changing a declaration from int to unsigned silently changes the behaviour of code you did not touch. Java and JavaScript spell them differently — >> is arithmetic, >>> is logical — which is safer, though JavaScript then forces both operands through 32-bit integers first, so bitwise operators there quietly truncate anything wider. Rust picks by type like C; Python has only the arithmetic shift, because its integers are unbounded and have no sign bit to lose.
Left shift has no such ambiguity, but it has its own trap: bits pushed past the top of the register are discarded with no signal at all. In the bitwise playground above, set the width to 8 bits and left-shift 0x80 by one to watch a value become zero. The playground evaluates every operation inside the width you choose, exactly as a real register would, rather than in arbitrary precision.
Integer widths, and where a value stops fitting
Overflow is not an exotic failure; it is what happens the moment a value needs one more bit than its type has. Keeping these four rows in your head prevents most of it.
| Width | Bytes | Hex digits | Unsigned range | Signed range (two’s complement) |
|---|---|---|---|---|
| 8-bitbyte, char, uint8_t | 1 | 2 | 0 to 255 | −128 to 127 |
| 16-bitshort, uint16_t | 2 | 4 | 0 to 65,535 | −32,768 to 32,767 |
| 32-bitint, int32, IPv4 address | 4 | 8 | 0 to 4,294,967,295 | −2,147,483,648 to 2,147,483,647 |
| 64-bitlong, int64, database IDs | 8 | 16 | 0 to 18,446,744,073,709,551,615 | −9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
The C type names are the common cases, not a guarantee: the C standard fixes minimum sizes rather than exact ones, so int and long genuinely differ between platforms. Use the fixed-width names — int32_t, uint64_t— when the width matters.
One limitation worth stating plainly: the conversion itself has no upper bound, but the two’s complement panel is defined only at these four widths. Odd widths — a 12-bit ADC reading, a 24-bit colour value, a 7-bit field in a protocol header — are not modelled, and the panel will simply report that the value does not fit. There is no fractional or floating-point support either: this converts integers only, so 3.5 is rejected rather than silently truncated to 3.
Everything runs in your browser. Nothing is uploaded, there are no accounts and nothing is saved, so pasting an internal identifier or a fragment of a key is safe. If you are decoding data rather than numbers, the Base64 encoder and decoder is the companion tool, and how Base64 encoding works covers the same bits-into-digits idea at a different group size.
Frequently asked questions
Why do online base converters give the wrong answer for large hex values?
Because most of them call parseInt, which returns a JavaScript Number, and a Number is a 64-bit float that only holds integers exactly up to 253 − 1. Convert 0xFFFFFFFFFFFFFFFF in such a tool and it prints 18,446,744,073,709,552,000 instead of the true 18,446,744,073,709,551,615, an error of 385. This converter uses BigInt throughout, so there is no rounding at any magnitude.
How do I convert hexadecimal to binary by hand?
Replace each hex digit with its four-bit group and join them, because one hex digit is exactly four bits. So 0x3F becomes 0011 1111 and 0xCD becomes 1100 1101. Going the other way, split the bits into groups of four from the right and write each group as one hex digit. No division or arithmetic is needed in either direction.
Why is 0xCD 205 in one place and −51 in another?
Because 11001101 is only a bit pattern, and the width and signedness decide what it means. Read as an unsigned 8-bit value it is 205. Read as a signed 8-bit value in two’s complement the top bit is a sign bit, so it is −51. Both readings are correct for the same eight bits, which is why a language declaring a byte as signed or unsigned changes the number you see.
What is the difference between the >> and >>> operators?
The arithmetic right shift, written >> in C, Java and Rust on signed types, copies the sign bit back in, so −8 shifted right by one stays −4. The logical right shift, written >>> in Java and JavaScript, feeds in zeros, so the same value becomes a large positive number. Choosing the wrong one on negative data produces a plausible-looking wrong answer rather than an error.
What is the largest number this base converter can handle?
There is no fixed limit. Conversion runs on BigInt, which grows to whatever size the value needs, so hashes, 128-bit identifiers and numbers hundreds of digits long convert exactly. The only bounded parts are the two’s complement panels, which are defined at 8, 16, 32 and 64 bits and will tell you when a value does not fit the width you picked.
Is anything I type into this converter sent to a server?
No. Every conversion runs in your own browser with JavaScript, nothing is uploaded, logged or stored, and there are no accounts. You can paste an internal identifier, a key fragment or a memory address without it leaving your machine, and the page keeps working if you go offline after it has loaded.