Open a browser console and type ~0. The answer is -1, not 4294967295. Bitwise NOT of zero, in a language where every number is a 64-bit double, returns a signed 32-bit integer. This is not a JavaScript bug. It is the ECMAScript specification doing exactly what it was designed to do, and one of the small set of facts that explain why hex calculators on the web give different answers for the same operation depending on whose conventions they follow.
Most developers reach for a hex calculator without thinking about integer width. The tool gives an answer, the answer matches expectations 99% of the time, and the 1% where it doesn't usually involves a register mask near the high bit, a left shift that wraps, or a sum that crosses some boundary nobody warned them about. Three boundaries do most of the work: the 32-bit signed/unsigned divide that goes back to two's complement, the 2^53 safe-integer ceiling baked into IEEE 754, and the right-shift dialect war between >> and >>>.
Two's complement and why subtraction is just addition
Two's complement is the representation underneath every signed integer on every CPU you are likely to touch. It encodes negative numbers by flipping all the bits and adding one, which sounds arbitrary and is the point of the whole exercise. The reason the trick is worth knowing is that in two's complement, hardware subtraction reduces to addition: to compute A minus B, you negate B (flip bits, add one) and add. The ALU doesn't need a separate subtractor. The integer arithmetic unit of a 1950s mainframe could do every operation with one circuit, and so can a modern x86 chip.
The convention wasn't always universal. Early machines used signed magnitude (one bit for sign, the rest for value, which gives you positive and negative zero) or one's complement (flip bits to negate, no carry-add, also two zeros). The IBM 7090 used signed magnitude. The CDC 6600 used one's complement. The PDP-11, released by DEC in 1970, standardized on two's complement for minicomputers, and by the late 1970s essentially every new processor design followed. C99 left the door open for signed-magnitude and one's-complement implementations until C++20 closed it in 2020 by mandating two's complement. C23, finalized in 2024, did the same for C.
The relevant artifact of this history shows up in bitwise NOT. In two's complement, ~x equals -x - 1. So ~0 is -1. ~1 is -2. ~0xFFFFFFFF, treated as a 32-bit value, is 0. This is consistent and predictable, but the predictability runs through the signed interpretation. If you want the unsigned interpretation, you have to mask the result back to the positive range yourself, or use a language construct that does it for you.
ECMAScript forces bitwise operands to Int32
Every browser-based calculator that uses JavaScript inherits a constraint that does not exist in C or Rust: the bitwise operators always operate on 32-bit signed integers. The ECMAScript specification defines a ToInt32 abstract operation that runs before any of &, |, ^, ~, <<, or >> produces a result. The operands get coerced to a signed 32-bit integer first, the operation runs, and the output is a signed 32-bit integer that gets converted back to a Number for return.
This is why 0xFFFFFFFF | 0 returns -1 in JavaScript, even though the input looks like a 32-bit unsigned maximum. The | 0 is a common idiom for forcing an integer conversion, and the conversion path goes through Int32, not Uint32. A hex calculator that wants to report the unsigned representation has to mask the negative result with something like result >>> 0, where the unsigned right shift operator is the one ECMAScript bitwise operator that returns a Uint32 instead of an Int32.
The width is hardcoded. There is no ECMAScript construct that lets you ask for a 64-bit signed bitwise AND on Numbers. BigInt, added in ECMAScript 2020, supports bitwise operations on arbitrary precision integers, but BigInt is a different type with its own conversion rules, and mixing it with regular Numbers throws TypeError. A web calculator that wants to offer wider-than-32 bitwise math has to either route everything through BigInt or simulate the wider width manually.
The >> versus >>> distinction
Right shift on signed integers has to decide what to fill the leading bits with. Two options: copy the sign bit (arithmetic right shift, preserves the sign) or fill with zeros (logical right shift, treats the value as unsigned).
| Operator | Width | Fill on right shift | 0x80000000 >> 1 |
|---|---|---|---|
>> | Int32 | Sign bit (1 for negatives) | 0xC0000000 (-1073741824) |
>>> | Uint32 | Zero | 0x40000000 (1073741824) |
C and C++ historically left this implementation-defined for signed types. Compilers commonly do arithmetic shift, but the standard didn't require it until C++20. JavaScript split the cases at the operator level: >> is arithmetic, >>> is logical. This is one of those small wins for predictability that gets buried because nobody talks about logical right shift outside of low-level code.
The same fork shows up in hex calculators. A calculator built on signed 32-bit semantics will sign-extend on right shift unless it overrides the operator explicitly. A calculator built on unsigned 32-bit semantics will zero-fill. RapidTables and most browser-based hex calculators standardize on unsigned 32-bit, which matches the intuition embedded engineers usually want when reading bit patterns out of a register dump. The cost is that the same operation can give different answers in different tools, and the answer that matches your IDE debugger might not match the web calculator.
The 2^53 ceiling on plain arithmetic
Bitwise width is one boundary. Arithmetic width is the other, and it lives at 2^53 - 1, which is 9007199254740991, exposed as Number.MAX_SAFE_INTEGER since ECMAScript 2015.
The reason is IEEE 754. Every JavaScript Number is a 64-bit double-precision floating-point value, standardized as binary64 in the IEEE 754 specification published in 1985 and revised in 2008 and 2019. The format reserves 1 bit for sign, 11 for exponent, and 52 for the significand. Because the significand has an implicit leading 1, integers up to 2^53 can be represented exactly. Past that boundary, the spacing between representable integers doubles: above 2^53, only every other integer is representable. Above 2^54, only every fourth. The arithmetic doesn't error; it silently rounds.
This is why 9007199254740992 + 1 === 9007199254740992 evaluates to true in any JavaScript engine. The right-hand side is the answer the engine can represent. The actual sum is not. Hex calculators that use plain Number arithmetic (rather than BigInt) inherit this ceiling. A hex value like 0xFFFFFFFFFFFFFF is 56 bits and cannot be added to another 56-bit value with exact arithmetic unless the calculator escapes to BigInt or a custom integer routine.
BigInt landed in ECMAScript 2020. It supports arbitrary precision integers with the literal suffix n (9007199254740993n) and its own set of arithmetic and bitwise operators. Mixing BigInt with Number throws unless one is coerced first. The performance cost is real but usually trivial for calculator workloads. Most modern hex calculators that handle large values route arithmetic through BigInt and only step back to Number for the display panels.
When width matters in practice
For everyday hex arithmetic on values that fit in 32 bits, none of this surfaces. Register masks, RGB triples, ASCII offsets, port numbers, and similar values all live well below 0x80000000, and the signed/unsigned distinction only bites at the top half of the 32-bit range. The arithmetic ceiling at 2^53 is high enough that 13 hex digits of precision are safe, which covers most 64-bit address arithmetic in practice if not in principle.
Three places it does surface.
The first is bitmask construction for the top half of a 32-bit register. 1 << 31 evaluates to -2147483648 in JavaScript, not 2147483648. Calculators that show the signed result confuse people; calculators that mask through >>> 0 show the unsigned result and everyone is happy until they paste the value somewhere that expects signed.
The second is right shift of a value with the high bit set. 0xFFFFFFFF >> 1 is -1 (sign-extended), not 0x7FFFFFFF. The unsigned variant >>> 1 gives the intuitive answer. Tools that default to signed right shift produce results that look correct in the binary panel but not in the hex panel.
The third is arithmetic on 64-bit register values, MAC addresses, or any hex string longer than 13 digits. Past 0x1FFFFFFFFFFFFF (which is 2^53 - 1, the safe-integer maximum), the Number type loses precision, and arithmetic results become approximations. Anything that needs to handle this correctly has to use BigInt or accept the rounding.
The reason hex calculators split on these decisions is that there is no single correct answer. The signed/unsigned interpretation depends on what the bits represent, not what the bits are. A tool that picks one default and exposes the other through a toggle, with all four bases visible at once so the disagreement between hex and binary is impossible to miss, is the version of the calculator that catches these surprises before they reach the code. Oktal operates under 32-bit unsigned semantics for bitwise ops and uses BigInt for arithmetic, with hex, decimal, octal, and binary panels visible together so the boundary effects show up where you can see them.