Convert `FFFFFFFFFFFFFFFF` to decimal and the answer is 18446744073709551615. A great many converters will tell you 18446744073709552000 instead. Nothing failed and nothing warned you; the last four digits were simply spent on a rounding error that happens before the conversion even starts.
Where the Missing Digits Go
JavaScript has one numeric type, and it is a IEEE 754 double-precision float. That is 64 bits, but not 64 bits of integer: 1 for the sign, 11 for the exponent, and 52 stored for the significand, which behaves as 53 because the leading bit is implied.
53 bits of significand means every integer up to 2^53 − 1 is exact and nothing above it is guaranteed to be. That number has a name in the language, Number.MAX_SAFE_INTEGER, and it is 9007199254740991. Past it the representable values start stepping in twos, then fours, and a value that lands between two of them is rounded to whichever is nearer.
So a converter that does parseInt(hex, 16) on a 64-bit value is not converting a 64-bit number. It is converting the nearest double to that number, and then printing that. Nothing in the code looks wrong, which is exactly why the bug survives.
The fix has existed since ES2020: BigInt holds integers of arbitrary size, and BigInt.prototype.toString(base) converts them exactly for any base from 2 to 36. This site's converter uses it throughout, which is why the answer above comes out with all twenty digits intact.
One Value, Read at Four Widths
The bit pattern is identical in every row. Only the declared width and signedness change.
| Bits | Unsigned | Signed | Hex |
|---|---|---|---|
| 8-bit | 255 | −1 | FF |
| 16-bit | 65535 | −1 | FFFF |
| 32-bit | 4294967295 | −1 | FFFFFFFF |
| 64-bit | 18446744073709551615 | −1 | FFFFFFFFFFFFFFFF |
Why All Ones Means Minus One
Two's complement is the reason. To negate a number, flip every bit and add one. Do that to 1 in eight bits: 00000001 flips to 11111110, add one and you get 11111111. So −1 is all ones, at every width.
The scheme looks arbitrary until you compare it with the alternatives. Sign-magnitude reserves the top bit for a sign and leaves you with two zeros, positive and negative, which every comparison then has to special-case. Ones' complement has the same problem. Two's complement has exactly one zero.
The bigger win is in the hardware. With two's complement, the circuit that adds two unsigned numbers adds two signed numbers correctly with no changes at all: 11111111 + 00000001 overflows to 00000000, which is right whether you read it as 255 + 1 wrapping to 0 or as −1 + 1 giving 0. One adder, both interpretations. That is why essentially every processor built since the 1960s uses it.
The cost is the asymmetry you meet in edge cases. An 8-bit signed byte runs from −128 to 127, not −127 to 127, because the extra pattern freed up by having only one zero goes to the negative side. It is why Math.abs(-128) on an 8-bit type gives back −128, and why the same thing happens at 32 bits with −2147483648.
The leading zero that costs eight
In C, and in JavaScript before strict mode outlawed it, a literal starting with a zero is octal. 010 is 8, not 10. This has produced a long tail of bugs in date handling, where 08 and 09 are not even valid octal and would fail outright. Both languages fixed it the same way: an explicit 0o prefix, and an error for the old form. Python 3 made 010 a syntax error for exactly this reason. If you ever pad a numeric string with zeros before parsing it, pass the base explicitly.
Why Hex Won and Octal Did Not
Both exist for the same reason: binary is correct and unreadable. A 32-bit value is thirty-two characters of ones and zeros that no one can compare by eye. Group the bits and you get something a person can hold.
Octal groups them in threes, hexadecimal in fours. That choice used to be a toss-up, because early machines had word sizes divisible by three: the PDP-8 had 12-bit words, four octal digits exactly, and octal was the natural notation for it.
The eight-bit byte settled the argument. A byte is two hex digits, exactly, with no digit straddling a byte boundary. In octal a byte is two and two-thirds digits, so an octal dump has to break alignment somewhere. Once IBM's System/360 fixed the byte at eight bits in 1964 and everyone followed, hexadecimal was the only grouping that lined up with the hardware.
Octal survives in the one place where the grouping happens to be three bits wide: Unix file permissions. chmod 755 is three sets of read, write and execute, one octal digit each. That is not nostalgia, it is the notation fitting the data exactly, which is the same reason hex fits everywhere else.
Convert between every base at once
Binary, octal, decimal, hex and anything up to base 36, exact past 64 bits.
The Traps Worth Knowing
Base conversion is arithmetic a child can do. Nearly every bug around it comes from something else.
parseInt without a radix
`parseInt("0x1F")` gives 31 because the prefix is honoured, but `parseInt("08")` gave 0 in older engines that read the leading zero as octal. Always pass the base: `parseInt(text, 16)`.
Endianness is not base conversion
`0x1234` stored little-endian hits memory as `34 12`. A hex dump shows byte order; a converter shows the value. Reading one as the other reverses your bytes.
Leading zeros carry width, not value
`0F` and `F` are the same number, but the first says it is a byte. Strip them from a value and you lose the width; keep them in a comparison and string equality fails on equal numbers.
Hex is case-insensitive, checksums are not
`ff` and `FF` are the same value, so compare hex by parsing it or by lowercasing both sides. A raw string comparison of two digests that differ only in case reports a mismatch that is not there.
JSON has no integers past 2^53
A 64-bit id in a JSON payload is a double the moment it is parsed, and it comes back rounded. This is why APIs that use snowflake ids send them as strings.
Frequently Asked Questions
Related Tools
Keep Reading
Why HSL Says Yellow and Blue Are Equally Light
Two colors, both hsl 50% lightness, eight to one apart in contrast. Where the sRGB gamma curve hides the difference, and how OKLCH puts it back.
Base64 Is Not Encryption: What It Does and Why It Exists
Three bytes become four characters, which is where the 33 percent size increase and the trailing equals signs come from. And why it hides nothing.
Which UUID Version to Use, and Why v7 Changed the Answer
A random primary key splits B-tree pages and scatters an InnoDB table. RFC 9562 fixed that in 2024 by putting the clock first. What each version really holds.
SHA-256 Explained: What Hashes Guarantee and What They Do Not
Three properties make a hash cryptographic, and each one supports a different use. Why SHA-256 is the wrong tool for storing passwords.