读懂代码里的魔法数字
& 0xFF、>>> 24、| (1 << 3)、0o644——学完第 11–15 关,这些片段各自在取哪几位、置哪一位,一眼可见。
使用指南
进制与位运算课是一门 17 关的交互式教程,把「二进制」从抽象概念变成手能摸到的开关:输入一个数,二、八、十、十六进制四种写法同步变化;点亮或熄灭某一位,数值立刻更新;两个数做 AND / OR / XOR / 移位,结果逐位对齐显示;输入一个小数,看它被拆成符号、指数、尾数三个字段,以及它在双精度里精确等于哪个十进制小数。每一关一个具体目标——换算某个数、凑出 200、写出 -5 的补码、算出 5 << 3——判定器即时反馈。
更新于 2026-09-094 个来源约 13 分钟读完
进制与位运算课是一门 17 关的交互式教程,把「二进制」从抽象概念变成手能摸到的开关:输入一个数,二、八、十、十六进制四种写法同步变化;点亮或熄灭某一位,数值立刻更新;两个数做 AND / OR / XOR / 移位,结果逐位对齐显示;输入一个小数,看它被拆成符号、指数、尾数三个字段,以及它在双精度里精确等于哪个十进制小数。每一关一个具体目标——换算某个数、凑出 200、写出 -5 的补码、算出 5 << 3——判定器即时反馈。
课程分四段:进制换算(二 ↔ 十、2 的幂、十六进制与八进制为什么是「速记」);有符号数(补码怎样让减法变加法、溢出为什么让 127 + 1 变成 -128);位运算(AND 掩码、OR 置位、XOR 翻转、移位乘除,以及它们在 Linux 权限位 755 里的直接应用);浮点数(IEEE 754 的字段布局,与 0.1 + 0.2 !== 0.3 的真正原因)。
约 45 分钟学完。面向初学编程的人、准备面试的开发者、以及每次看到 0x1F、& 0xFF、chmod 644 都要查一遍的人;不需要数学基础,会加法即可。
0x。?lesson=n 可直达某关。本页上方就是课程本体:改数值、点位开关、填答案。
第 9 关「求补码」要求写出 -5 的 8 位补码:
第 16 关「IEEE 754 字段」输入 0.15625,面板显示:
绝对值 5 0 0 0 0 0 1 0 1
按位取反 1 1 1 1 1 0 1 0
加一 1 1 1 1 1 0 1 1 ← 答案 11111011
验证 无符号 251,有符号 251 − 256 = −5 ✓0.15625 = 0.00101₂ = 1.01₂ × 2⁻³
符号 0
指数 −3 + 127 = 124 = 01111100 ← 答案 124
尾数 01000000000000000000000(隐含前导 1)
32 位 0 01111100 01000000000000000000000 = 0x3E200000任何进制都是「位权求和」。十进制 203 = 2×100 + 0×10 + 3×1;二进制 1011 = 1×8 + 0×4 + 1×2 + 1×1 = 11。从右往左位权依次是 1、2、4、8、16、32、64、128……二进制转十进制就是把为 1 的位权加起来;十进制转二进制则反复除 2 记余数,或者从大到小贪心减去 2 的幂(第 7 关的位开关就是这个过程的可视化)。
,与 1000 只差 2.4%,所以早期用 KB 表示 1024 字节。这造成了至今的混乱:硬盘厂商按 标 1 GB,操作系统按 算,500 GB 的盘显示 465 GiB。IEC 为此定义了 KiB、MiB、GiB(二进制前缀),Linux 与 macOS 已逐步采用,Windows 仍显示 GB 却按 GiB 计算。记住几个数:、、、。
二进制太长,人读不了。十六进制(0–9 加 A–F)用一个符号表示 4 位,一个字节恰好两位:11111111 = FF,10110110 = B6。换算不需要经过十进制——把二进制从右往左每 4 位切一段、每段查表即可,第 5 关就练这个。八进制用一个符号表示 3 位,历史上用于 12、24、36 位字长的机器,今天主要活在 Linux 权限(755)和 C 的 \012 转义里。程序里的前缀:0x 十六进制、0o 八进制、0b 二进制。
每个非负整数只有一种二进制写法,所以一个 8 位数可以当 8 个互不干扰的开关(标志位)用:flags = READ | WRITE、if (flags & EXEC)。CSS 的 font-weight、Unix 的文件权限、网络协议头里的标志字段都是这样打包的。第 7 关要你只点开关凑出 200——从最高位贪心:200 ≥ 128 点亮,剩 72 ≥ 64 点亮,剩 8 = 2³ 点亮,得到 11001000。
负数怎么存?直觉是拿一位当符号,但那样会有「+0」和「−0」两个零,加法电路也要分情况。补码(two's complement)的规定是:最高位为 1 表示负数,数值等于无符号值减 。8 位里 11111111 是 255,也是 255 − 256 = −1;10000000 是 −128。求一个负数的补码:写出绝对值、按位取反、加一——−5 就是 00000101 → 11111010 → 11111011。
补码的精妙之处在于加法不需要知道正负:−5 + 5 = 11111011 + 00000101 = 1 00000000,第 9 位进位丢弃后正好是 0。减法于是变成「加上补码」,CPU 只需要一个加法器。8 位补码的范围是 −128 到 127:负数比正数多一个,因为 0 占了正数一侧的位置。
位宽有限,加过头就绕回去。int8 里 127 = 01111111,加一得 10000000,按补码解读是 −128。C、Java、Go 的整数溢出都是这样静默发生的;JavaScript 的普通 Number 是浮点数所以不会溢出,但 Int8Array、|0 与所有位运算都在 32 位补码上进行,(2 ** 31) | 0 会得到 −2147483648。著名的 2038 年问题是 32 位有符号秒数在 2038-01-19 03:14:07 UTC 溢出为负;早年游戏的「分数变负」「金币归零」也是同一回事。
三种按位逻辑运算各有固定用途:
| 运算 | 规则 | 惯用法 |
|---|---|---|
AND & |
两位都 1 才 1 | 掩码取位:x & 0x0F 取低 4 位;IP & 子网掩码 得网络地址;n & 1 判奇偶 |
OR | |
任一位 1 就 1 | 置位:flags | READ 打开一个标志而不影响其它 |
XOR ^ |
两位不同才 1 | 翻转 / 还原:x ^ m ^ m = x;一次性密码本、校验和、无临时变量交换 |
「关闭」某位是 AND 与 NOT 的组合:flags & ~FLAG。XOR 的两个恒等式 、 是它一切用途的根源:a ^= b; b ^= a; a ^= b 交换两个数,找出数组里唯一出现奇数次的元素只需把所有数 XOR 起来。
左移 n 位等于乘 (低位补 0),右移 n 位等于除以 向下取整。5 << 3 = 101000 = 40。移位是最便宜的乘除法,更常见的用途是把小字段打包进一个整数:(r << 16) | (g << 8) | b 把三个 0–255 拼成 24 位颜色值,反过来 (rgb >> 8) & 0xFF 取出 G。注意右移有两种:算术右移 >> 保留符号位(负数仍是负数),逻辑右移 >>> 补 0。JavaScript 的移位在 32 位上进行,1 << 31 得到 −2147483648,1 << 32 等于 1 << 0 = 1,因为移位数按模 32 处理。
Linux 文件权限是 9 个位:所有者、组、其他人各 3 位,依次是读 r = 4、写 w = 2、执行 x = 1。每组三位求和就是一位八进制:rwxr-xr-x = 7、5、5 → 755;rw-r--r-- = 644。这是位权与掩码在日常运维里最直接的应用:chmod u+x 是 OR 上 0o100,chmod o-w 是 AND 上 ~0o002。前面还可能有第 10–12 位(setuid、setgid、sticky),所以你有时会见到四位数 4755。
浮点数是二进制的科学计数法:
单精度 32 位 = 1 位符号 s + 8 位指数 e(偏移 127)+ 23 位尾数 f;双精度 64 位 = 1 + 11(偏移 1023)+ 52。尾数只存小数部分,整数部分的 1 是隐含的,白赚一位精度。指数加偏移量存储是为了让浮点数按位比较大小时和整数一样单调。0.15625 = = ,指数字段是 −3 + 127 = 124 = 01111100,尾数字段是 01 后面补零。
几个特殊编码:指数全 0 是非规格化数(表示极接近 0 的值和 ±0);指数全 1 且尾数全 0 是 ±∞;指数全 1 且尾数非 0 是 NaN——这就是为什么 0/0 是 NaN 而 1/0 是 Infinity,也是为什么 NaN 有很多种位模式却都「不等于自己」。
二进制小数只能精确表示 2 的负幂之和:0.5、0.25、0.125…… 在二进制里是无限循环小数 ,52 位尾数必须舍入。于是双精度里的 0.1 精确地等于
略大于 0.1;0.2 同样略大;两者相加再舍入得到 0.3000000000000000444…,而字面量 0.3 最近的双精度值是 0.29999999999999998889…——两个不同的二进制数,所以 0.1 + 0.2 === 0.3 为 false。这不是 JavaScript 的 bug,任何用 IEEE 754 的语言(Python、Java、C、Go)都一样。
对策取决于场景:钱用整数「分」或十进制库(decimal、BigDecimal、Decimal.js);比较用误差范围 Math.abs(a − b) < ε;显示用 toFixed(2) 只在最后一步取整;计数用整数,双精度能精确表示 以内的所有整数,超过就该用 BigInt。课程最后一关的选择题「存的 0.1 比真值大还是小」,答案是「大」——看精确展开第 18 位以后的 55511… 就知道。
BigInt 的位运算。Math.fround、半精度与 bfloat16。0x。10000000 是 −128 而不是 −0,−1 是全 1。>> 就是除以 2」:对负数是向下取整而不是向零取整:−5 >> 1 得 −3,Math.trunc(−5 / 2) 得 −2。toFixed 就能解决金额问题」:toFixed 只是显示取整,中间运算的误差已经累积;金额从一开始就该用整数分。Int32Array、| 0 都在 32 位补码上运行,会溢出;Number 超过 会丢失整数精度。& 0xFF、>>> 24、| (1 << 3)、0o644——学完第 11–15 关,这些片段各自在取哪几位、置哪一位,一眼可见。
第 10 关的溢出与第 17 关的舍入解释了绝大多数这类 bug:前者换更宽的类型或 BigInt,后者换整数分或十进制库。
子网掩码就是 AND 掩码(第 11 关):192.168.1.37 & 255.255.255.0 = 192.168.1.0;chmod 755 / 644 的含义与「为什么脚本没有执行权限」(第 15 关)。
补码、溢出、XOR 交换、移位乘除、IEEE 754 字段、0.1 + 0.2 是最常被问到的基础题,课程每关的判定就是一道自测。
大多数关卡允许前导零省略(1011 与 00001011 都对);标明「8 位补码」的关卡要求恰好 8 位,因为位宽本身就是答案的一部分。
0x?没有区别,ff、FF、0xFF 都算对。
同一串位的两种解读:无符号把最高位当 ,有符号把它当 。11111111 在 8 位下无符号是 255、有符号是 −1。
把双精度的尾数与指数按定义展开成有理数,再做长除法得到全部十进制位——它不是近似,而是这个二进制数的精确十进制表示。
当前浏览器的 localStorage,不上传;「分享本关」的链接只带关卡号,不带进度。
课程全部在浏览器本地运行:进制换算、位运算、浮点字段拆解与精确展开都是页面内的纯函数计算,不请求服务器;通关进度存在本机 localStorage。
chmod 工具——符号模式与八进制模式的定义:https://pubs.opengroup.org/onlinepubs/9799919799/utilities/chmod.html(访问日期:2026-09-09)更新于 2026-09-09
17 关:二/八/十六进制换算、位开关、补码与溢出、AND/OR/XOR/移位、chmod 权限位、IEEE 754 拆解与 0.1+0.2 之谜
目标数值面板里是二进制 1011。它等于十进制多少?
每一位的「位权」是 2 的幂:最右边是 2⁰=1,往左依次 2、4、8、16……把为 1 的位的权相加就是十进制值:1011 = 8 + 0 + 2 + 1 = 11。面板里点任何一位都能立刻看到十进制随之变化——比背口诀更快建立直觉。