进制转换器

在不同进制之间转换数字

为什么是 2、8、16:进制是比特分组,不是随意选择

程序员恰好使用这几种进制,是因为每一种都与比特整齐对应。一个十六进制位恰好是 4 个比特(半字节),两个十六进制位就是一个字节——0xFF 即 11111111 即 255,32 位值永远是 8 个十六进制位。内存地址、哈希摘要、MAC 地址、颜色值都用十六进制书写,原因就在这里:你能直接从文本上读出字节边界。八进制把比特三个一组,与 Unix 权限模型完美契合:rwx 是三个比特,所以 rwxr-xr-x 就是 111 101 101,即 755。 相比之下,十进制与比特边界毫无关系——255 这个数字完全无法直观告诉你哪些位被置位。十进制转二进制需要反复除以 2;十六进制转二进制则是逐位查表。这就是为什么老练的工程师能心算十六进制与二进制互转,却没人对大数做十进制到二进制的心算。 一个有用的记忆锚点是幂次阶梯:2^8 = 256,2^10 = 1024,2^16 = 65,536,2^32 = 4,294,967,296。由此可以推出实践中遇到的大多数上限——字节的 0–255 范围、TCP 端口最大值 65,535、32 位地址空间的 4 GiB 上限。另外注意 Base64 不是这种意义上的进制:它是把 6 比特一组映射为字符的二进制转文本编码。Base64 无法参与算术运算,用进制转换器去转它属于范畴错误。
hex F    F        octal 7   5   5
     |    |              |   |   |
bin 1111 1111       bin 111 101 101
     = 0xFF = 255        = rwxr-xr-x  (chmod 755)

# read a 32-bit value byte by byte
0xDEADBEEF = DE AD BE EF = 222 173 190 239

# the powers ladder
2^8 = 256   2^10 = 1,024   2^16 = 65,536   2^32 = 4,294,967,296

补码:符号取决于位宽

位模式 11111111 本身没有符号。按无符号 8 位整数解释是 255;按有符号(补码)8 位整数解释是 -1。同样的字节,两种契约。补码之所以胜出,是因为算术是统一的:取负就是"全部取反再加一",同一套加法器电路可以同时处理正负数。n 位有符号整数覆盖 -(2^(n-1)) 到 2^(n-1)-1——8 位就是 -128 到 127。这种不对称是真实存在且偶尔咬人的:在 8 位寄存器中对 -128 取负会溢出回 -128,这正是 C 语言中对最小负整数取 abs() 是未定义行为的原因。 这对转换器很重要:没有声明位宽的转换器无法展示负数——不存在"-5 的二进制"这种东西,只存在"-5 的 8 位(11111011)或 32 位(…11111011)补码"。 JavaScript 还有自己的花样:位运算符会把数字强制转成有符号 32 位整数。所以 0xFFFFFFFF 作为字面量是 4294967295,但 0xFFFFFFFF | 0 是 -1,因为第 31 位成了符号位。无符号右移零位(value >>> 0)是把值重新解释为无符号 32 位的标准写法,而类型化数组(Int8Array 与 Uint8Array)则把同一批字节的两种读法明示出来。当寄存器转储或抓包里的十六进制值看起来"巨大而怪异"时,先检查它是不是被按无符号打印的负数。
(0xFF | 0)                 // 255 — fits in int32, stays positive
(0xFFFFFFFF | 0)           // -1  — bit 31 became the sign bit
(-1 >>> 0).toString(16)    // 'ffffffff'
(-1 >>> 0).toString(2)     // '11111...' (32 ones)

// same byte, two contracts
new Int8Array([0xFF])[0]   // -1
new Uint8Array([0xFF])[0]  // 255

// negate = flip bits, add one
//  5 = 00000101
// ~5 = 11111010
// +1 = 11111011  = -5 in 8-bit two's complement

IEEE 754 与 2^53 悬崖:JavaScript 何时悄悄舍入你的整数

JavaScript 只有一种数字类型:IEEE 754 双精度浮点数。double 有 52 位尾数加一个隐含位,因此能精确表示到 2^53(9,007,199,254,740,991——Number.MAX_SAFE_INTEGER)为止的所有整数。再往上一步,可表示的值就开始跳跃:2^53 + 1 被舍入为 2^53,比较 9007199254740992 === 9007199254740993 结果为 true。不抛任何异常。舍入是无声的,这正是它危险的地方。 这在真实系统中不断偷袭开发者,因为 64 位整数 ID 无处不在:数据库 bigint 列、Twitter/X 雪花 ID、Discord ID、订单号。JSON 本身没有整数大小限制,但 JSON.parse 把 19 位 ID 实例化为 Number 的那一刻,低位数字就没了——而这个损坏的 ID 会以貌似合理的样子在你的代码里畅通无阻。解决办法:大 ID 用字符串传输,或用支持 BigInt 的 reviver 解析。BigInt 支持任意精度,其字符串形式还接受进制前缀。 同样的 IEEE 754 也解释了著名的 0.1 + 0.2 !== 0.3,它本质上是个进制转换问题:0.1 在十进制下有限,在二进制下却是无限循环小数(0.0001100110011...),必须舍入才能塞进 52 位。凡是分母不是纯 2 的幂的十进制小数都有这个性质。这就是为什么金额应该用整数分来存,也是为什么一个支持小数的进制转换器必须能展示循环展开。
Number.MAX_SAFE_INTEGER          // 9007199254740991 (2^53 - 1)
9007199254740992 === 9007199254740993   // true (!)

// 64-bit ID from an API, silently corrupted
JSON.parse('{"id": 900719925474099267}').id
// -> 900719925474099300

// exact alternatives
BigInt('900719925474099267')     // 900719925474099267n
BigInt('0xff')                   // 255n — radix prefix works
(255n).toString(16)              // 'ff'

// the fraction side of the same coin
0.1 + 0.2                        // 0.30000000000000004
// 0.1 in binary = 0.000110011001100... (repeats forever)

各语言中的进制字面量与解析陷阱

现代语言在前缀上达成了一致:0x 表示十六进制,0b 表示二进制,0o 表示八进制(JavaScript、Python、Rust、Swift 都接受),还有下划线数字分隔符如 1_000_000 提升可读性。历史遗留的疮疤是 C 风格八进制:仅凭前导零,010 就表示 8。这种写法造成了足够多的真实 bug——补零的数据列被粘进代码、chmod 值被当十进制传参——以至于严格模式的 JavaScript 把旧式八进制字面量定为语法错误,并引入 0o 替代。如果某个 API 用普通数字接收文件模式,644 和 0o644 是完全不同的值(十进制 644 等于 0o1204)。 JavaScript 的解析函数各有脾气。parseInt(str, radix) 应当永远显式传 radix:旧引擎会把前导零解释为八进制,即便今天 parseInt 也只自动识别 0x,不识别 0b 和 0o。Number('0b101') 理解全部三种现代前缀,但 Number('') 是 0;parseInt 遇到第一个非法字符就停止而不是报错——parseInt('12px', 10) 得 12,是功能还是地雷视当天心情而定。parseInt 还会先把参数转成字符串,在极小的浮点数上产生著名的荒谬结果(parseInt(0.0000005) 经由字符串 '5e-7' 得到 5)。 输出方面,toString(radix) 支持 2 到 36 进制,配合 padStart 可以生成定宽转储:(5).toString(2).padStart(8, '0') 输出 00000101。64 位级别的位运算请用 BigInt——基于 Number 的位运算符会截断到 32 位,毁掉高位一半。
parseInt('08')          // 8 today; 0 in pre-ES5 engines — always pass radix
parseInt('0x1F')        // 31 — hex auto-detected
parseInt('0b101')       // 0  — binary prefix NOT understood
Number('0b101')         // 5  — all modern prefixes work
parseInt('12px', 10)    // 12 — stops at first invalid char
parseInt(0.0000005)     // 5  — via the string '5e-7' (!)

0o755                   // 493 — modern octal literal
1_000_000               // digit separators, ignored by the engine
(255).toString(2).padStart(8, '0')   // '11111111'
(0xDEADBEEFn << 8n).toString(16)     // 'deadbeef00' — 64-bit-safe in BigInt

实战位运算:权限、标志位与颜色

一线开发者真正需要手动换算进制的三个场景是 Unix 权限、功能标志位和颜色——每个都是值得内化的小惯用法。 权限:八进制的每一位是读+写+执行,权重为 4-2-1。755 分解为 7 = 4+2+1(属主 rwx)、5 = 4+1(属组 r-x)、5(其他 r-x)。644 是 rw-、r--、r--。setuid/setgid/sticky 位构成可选的第四个前导位——4755 属于 setuid root 的领域,所以审计时看到前导的 4 或 2 要提高警惕。符号表示转八进制的心算不过是把权重相加。 标志位:把每个标志定义为 1 左移其索引位,用按位或组合,用按位与检测,用与非清除,用异或切换。转换器里的二进制视图就是调试视图:标志值 13 是 1101,说明第 0、2、3 位被置位。这种布局与文件权限掩码、Linux capabilities、CSS font-feature 位域以及无数网络协议里的 flags 整数完全相同。JavaScript 有一个注意事项:标志超过 31 个就必须换用 BigInt 或字数组,因为存在 32 位截断。 颜色:CSS 十六进制颜色就是三个字节(带 alpha 则四个)的拼接。提取通道就是移位加掩码:红色是值右移 16 位再与 0xFF 按位与。这就是 0xFF0000 是纯红的原因,也是转换器中十六进制视图与十进制视图 16711680 描述同一个像素的原因。
// permissions: weights 4-2-1 per octal digit
// 755 = rwx r-x r-x     644 = rw- r-- r--     4755 = setuid + 755

// flags: define, set, test, clear, toggle
const READ  = 1 << 0;  // 1
const WRITE = 1 << 1;  // 2
const EXEC  = 1 << 2;  // 4
let flags = READ | EXEC;          // 5 = 101
(flags & WRITE) !== 0             // false
flags &= ~READ;                   // clear -> 100
flags ^= EXEC;                    // toggle -> 000

// colors: bytes packed into one integer
const c = 0xff8800;               // 16746496
const r = (c >> 16) & 0xff;       // 255
const g = (c >> 8)  & 0xff;       // 136
const b =  c        & 0xff;       // 0
最后更新:

关于此工具

进制转换器在十进制、二进制、八进制、十六进制之间互转整数。程序员阅读内存地址、HTTP 字节数时常用十六进制,处理位标志时用二进制,设置 Unix 文件权限 755/644 时用八进制。

使用方法

  1. Type a number into any of the four fields — decimal, binary, octal, or hex.
  2. The other three fields update automatically with the equivalent representation.
  3. Click Copy on any row to grab that representation for your code or terminal.
  4. Use the leading 0x prefix as appropriate when pasting into source code.
  5. Watch for invalid input warnings if you mix incompatible characters (e.g., 9 in binary).

常见用例

  • Reading a memory address from a stack trace and finding the decimal offset.
  • Translating Unix file permissions between symbolic (rwxr-xr-x) and octal (755) form.
  • Decoding a network packet field given as hex into the underlying integer value.
  • Working with bitmask flags by viewing the binary representation.
  • Converting an RGB channel value (0–255) into the hex pair used in a CSS color.
  • Checking that a hex constant in your code matches the decimal value in a spec.

常见问题

Q. Why does 10 in hex equal 16 in decimal?

A. Hex (base 16) digits go 0-9 then A-F. Position values are 16⁰, 16¹, … so "10" hex means 1×16 + 0 = 16.

Q. Are negative numbers supported?

A. For most calculator use the tool sticks to non-negative integers. Negative values in binary need an explicit width and two-complement encoding, which differs by platform.

Q. How big a number can I convert?

A. JavaScript handles integers up to 2^53 - 1 safely. Beyond that you need BigInt; this tool focuses on the typical 32/64-bit range.

Q. Why does the hex representation use uppercase letters?

A. Convention only. Lowercase is equally valid (and what JavaScript .toString(16) produces by default). Many specs and tools display uppercase for readability.