
1. 从“二进制数1”说起为什么每日一题要从进制开始刷最早在牛客的每日一题列表里看到《二进制数1》这个标题时我其实有点不以为然。心想二进制嘛十进制转二进制、二进制转十进制大一C语言课早就学过的东西还需要专门开一个 series 来讲直到我自己动手把 tracker 里记录的每日一题过了一遍才意识到问题没那么简单——很多人对二进制的理解停留在“会换算”的层面可一旦涉及二进制除法、复杂位运算、校验码计算甚至工程里的二进制数据格式立刻就会卡壳。这也就是我想认真写这篇复盘的原因。牛客把“二进制数1”放在每日一题的靠前位置并不是让你背一张转换表就完事而是因为它几乎是所有后续知识的地基位运算的底层逻辑、整数的存储方式、网络协议里的数据校验、图像和音频文件的数据组织全部从二进制展开。这篇内容适合正在刷题准备面试的人、写 C/C/Java 但总在位上吃亏的开发者以及单纯想把计算机基础补扎实的学生。我会结合自己打卡期间的踩坑经历把基数、位权、转换、运算和真实工程场景串成一条线来讲。标题里的“1”值得多说一句。它代表这是二进制专题的第一篇核心任务是先把“进制本身是什么”和“怎么转换”讲透。搞清楚这一课后面的补码、浮点数、位运算技巧才有讨论的前提如果这里靠死记硬背混过去越往后坑越深。我的建议是跟着这一篇的节奏走认真推一遍进位和借位再拿 tracker 记录下来的错题做复盘会比单纯“刷完打卡”有用得多。2. 位权与计数规则先把“进制”这件事想明白2.1 进制到底是什么符号、基数、位权很多教材一上来就抛定义进制是“逢几进一”的计数方法。这句话没问题但太抽象。我更喜欢用一个例子带学生入门——码表。十进制有0到9十个符号数到9之后没有新符号可用于是进位得到“10”。这里的“10”不是数字十而是“1个十加0个一”的简写。进制里真正决定“每个位置值多少钱”的东西是两样基数radix和位权weight。十进制的基数是10从右往左每一位的位权依次是 (10^0、10^1、10^2)…… 二进制的基数是2位权变成 (2^0、2^1、2^2)…… 就这么一点差别其他规则完全一样。我最初学的时候总把“位权”当成一个需要背的表格后来发现根本不用背。你看某个数字符号站在第几位把基数的“位数-1”次幂算出来就是这一位实际代表的量。比如二进制1101从右往左数第一位是 (2^01)第二位是 (2^12)第三位是 (2^24)第四位是 (2^38)所以它的真实值就是 (1\times10\times21\times41\times813)。注意位权的指数是从0开始而不是从1开始。这是新手最容易搞混的地方。2.2 位权展开式从十进制迁移到任意进制理解了位权之后任何进制到十进制的转换都成了同一套动作把每一位数字乘以它所在位置的位权再全部加起来。这个方法有个正式名字叫位权展开法按权展开求和。举几个例子二进制10101(1\times160\times81\times40\times21\times121)。八进制37(3\times87\times131)。十六进制2F(2\times1615\times147)字母F代表15。在学会用“除基取余”之前我建议刚开始练转换的时候老老实实先列出位权再逐位相加。这个过程能把“进制”的内核练成肌肉记忆。我自己就是这样练的每天在 tracker 里记录三道转换题先展开再化简错的就标红连续一周之后基本不会再犯“把指数搞错”的低级错误。很多人问既然掌握位权展开就能算十进制为什么还要学除2取余因为展开法需要你先知道每一位是什么这相当于“结果已知求过程”而编程中更常遇到的场景是反过来——给你一个十进制数让你生成二进制表示这时候就需要一个系统性的转换算法也就是下一章的内容。2.3 计算机选二进制的三个现实原因聊到二进制总有人问十进制用得好好的计算机为什么非要用0和1我总结下来有三个核心原因面试时也常被问到。第一物理实现最可靠。晶体管只有“导通”和“截止”两种稳定状态对应电压的高和低。如果用十个电平状态去表达0到9噪声稍大一点就无法区分硬件成本会急剧上升而二值状态抗干扰能力极强这是工程上的最优解。第二二值逻辑和硬件天然匹配。“与、或、非”这些逻辑运算在布尔代数里只有真和假两种结果晶体管电路能够直接实现。二进制的一位恰好就是布尔逻辑的一个条件这让运算电路设计变得非常简单。第三运算规则简化。十进制乘法表有45条要背二进制乘法表只有4条0×000×101×001×11。运算规则越少电路实现越简单。后面你会看到二进制乘法和除法甚至能拆成“移位加加”和“移位减减”一个加法器就能扛起大部分工作。理解这三个原因之后二进制就不再是一个“考试要考”的规矩而是一个被工程需求倒逼出来的自然选择。3. 转换套路全集整数、小数和“一眼看出答案”的速算3.1 整数转换除2取余法十进制整数转二进制标准方法是除2取余逆序排列。拿25举例(25 \div 2 12) 余1(12 \div 2 6) 余0(6 \div 2 3) 余0(3 \div 2 1) 余1(1 \div 2 0) 余1把余数从下往上读11001所以 (25_{10}11001_2)。这里的关键是最后一步一定要除到商为0为止很多人中途停下来导致最高位丢失这是我最常看到的低级错误。我自己的检验习惯是算完之后立刻用位权展开法验证一遍(11001 16800125)对上了才算通过。在 tracker 打卡时我会把“除完最后一步商是否已经为0”作为检查清单里的一条减少无谓的失误。顺便提一下这个套路对任何进制都适用。转八进制就除8取余转十六进制就除16取余只是十六进制的余数在10到15时需要写成A到F。所以学一个“除基取余”等于学了所有进制往十进制的反向转换逻辑。3.2 小数转换乘2取整法十进制小数转二进制和整数是完全相反的思路乘2取整顺序排列。以0.375为例(0.375 \times 2 0.75)取整数部分0(0.75 \times 2 1.5)取整数部分1剩下0.5(0.5 \times 2 1.0)取整数部分1剩下0从上往下读011所以 (0.375_{10}0.011_2)。可以验证(0\times2^{-1}1\times2^{-2}1\times2^{-3}0.250.1250.375)。这里有个非常经典的坑十进制的有限小数转成二进制可能是无限循环小数。比如0.1转二进制会变成0.0001100110011……无限循环下去。这也是为什么计算机里浮点数总有不精确的问题——不是计算机算错而是0.1在二进制里根本写不完。我最初刷题时天真地把0.1直接转成“0.00011001100”结果验证完全对不上后来才意识到有限位数必然有舍入误差。干货如果题目要求必须是有限二进制小数检查能否化成 (\frac{k}{2^n}) 的形式。分数只要约分后分母含2之外的质因数二进制表示里就会出现循环。3.3 拆位扩展法与常用速查表除2取余虽然万能但遇到比较大的数比如32768一步步除会非常繁琐。这时候可以用拆位扩展法把一个十进制数拆成若干个2的幂之和然后按位填1。以86为例先列出不超过它的2的幂64、32、16、8、4、2、1。拆的过程是 (86641642)也可以用842但没必要从最高位往下填8、4、2这些位分别对应二进制从高到低的位置。64是第7位(2^6)因为从 (2^0) 数起所以86的二进制是1010110第7位1第6位0第5位1第4位0第3位1第2位1第1位0。这个方法熟练之后非常快本质上是人脑做贪心选择。我还会配合一张常用幂表2的幂值二进制位数(2^0)11位(2^4)165位(2^8)2569位(2^{10})102411位(2^{16})6553617位记住几个关键锚点256、1024、65536以后遇到类似“4096的二进制是什么”这类问题可以直接写出1后面跟12个0不需要动笔。3.4 两个容易“翻车”的边界第一个边界是“正整数和0”。0的二进制就是0但“0”和“0000”在固定位宽的语境下是不同概念。比如一个8位的二进制数00000000对应的十进制是0可如果写成10000000在有符号数里它可能是-128或128取决于解释规则。刷题时题目如果给了位宽如32位千万不能只写有效位否则补码计算会出问题。第二个边界是小数的“四舍五入”。十进制转二进制的小数练习经常会要求保留若干位比如0.2转二进制保留5位。计算过程是 (0.2\times20.4(0))、(0.4\times20.8(0))、(0.8\times21.6(1))、(0.6\times21.2(1))、(0.2\times20.4(0))因此前5位是00110。但请不要直接写“0.00110”要注明这是保留5位的近似值因为再往后算还会继续出0110的循环。4. 运算里最容易被考到的加法、补码、除法与校验4.1 加法与减法逢二进一借一当二二进制加法没有悬念逢二进一。1110这里的10不是“十”而是二进制的“二”。多位相加时进位会像水波一样逐级往前传。试着算1011 1101最低位1110写下0进1第二位10进位110写下0进1第三位01进位110写下0进1最高位11进位111写11。结果是11000。这里最容易出错的不是某一位的加法而是“进位叠加”——多位都有1时进位会连续积累初学者经常漏掉中间的某一级。减法稍微绕一些规则是借一当二。比如1000 - 0011最低位0减1不够向高位借1借来的1在本位变成2所以最低位是2-11第二位此时又不够继续向更高位借……这个过程和十进制减法极其相似只是“借一”变成“借一当二”而不是“借一当十”。我建议初学阶段直接用“补码代替减法”的思路来理解这样反而更接近计算机的实际做法下一小节展开。4.2 二进制除法长除法与移位除法很多人把除法当成二进制运算里的难点其实它和十进制长除法没什么两样。以1101 ÷ 1013 ÷ 2为例从最高位开始取被除数先看第一位1不够除继续取前两位11除以10商1余1把第三位0落下来变成10除以10商1余0把第四位1落下来变成1不够除商0余1。所以商是110余数是1。验证(6\times2113)完全正确。这个过程的启示是二进制的除法本质是移位和减法每一位的商只可能是0或1不需要像十进制那样尝试1到9的倍数硬件实现反而更简单。在编程题里最常见的其实是“用移位判断整除性”。比如判断一个数是不是2的幂经典写法就是n 0 (n (n - 1)) 0。这个式子的原理是2的幂在二进制中只有一个1减去1后这个1会变成多位1两者按位与会得到全0。我第一次做这道题时完全看不懂后来把81000和70111写在纸上按位与了一下瞬间就通了。这类题目考的不是“会不会做除法”而是“能不能从二进制的角度理解乘除法”。4.3 LRC校验码一个需要“二进制相加”的真实例子搜资料时看到不少人问“LRC校验码需要换算成二进制相加吗”这里统一回答一下LRCLongitudinal Redundancy Check纵向冗余校验算的就是二进制运算而且是最简单的按位异或。常见于串口通信和 ASCII 协议做法是把所有数据字节先按位异或得到一个8位校验值。举例要发送三个字节0x31 0x32 0x33即字符1230x31 ^ 0x32 0x03因为00110001 ⊕ 00110010 000000110x03 ^ 0x33 0x3000000011 ⊕ 00110011 00110000所以LRC是0x30。注意这里用的是“按位异或”而不是“普通加法”所以不需要进到高位。很多初学者把三个十六进制数加起来再去低8位得到的结果通常会不同——只有异或才是LRC加法那是 checksum 的算法。这个细节在真实调试串口时特别容易踩被坑过一次就记住了。常见误区LRC不是“把二进制相加后取进位”而是“逐字节做异或”。两者结果常常不一样别搞混。4.4 不要急着背原码反码补码刷题打卡到运算这一步很多人立刻开始背原码、反码、补码的定义觉得“背下来才算学会”。我的看法正好相反先理解“补码是为了解决什么问题”再背不迟。假设只有4位二进制想表示负数。原码方案里-5是1101但这样会出现“00000-01000”两个零且加法运算必须先判断符号非常麻烦。补码的聪明之处在于让减法变成“加上一个负数”而这个负数对应的二进制恰好是它相反数的补码。以 -5 为例它的补码是1011而且 (5(-5)0101101110000)最前面的1超出4位直接丢掉结果为0。正是这种“取反加一”的设计让电路里只有加法器也能算减法。我在 tracker 的笔记里给这个知识点单独开了一页目的不是背表而是记录“为什么计算机要用补码存储整数”。这个问题面试中出现的频率极高而且只要把上述逻辑讲清楚基本上就是合格的回答。等这一篇消化完再往下学浮点数的 IEEE 754 表示会顺畅很多。5. 从牛客题到工程现场二进制的几个“亲戚”5.1 C 刷题中最常见的二进制题型说回牛客的每日一题。二进制专题的常见 C 题目大致分三类进制转换、位运算技巧、二进制计数。第一类最典型输入十进制整数输出它的二进制表示。初学者写while (n 0) { ans n % 2; n / 2; }但要注意结果的拼接顺序因为低位先算出来最终要反转。另外如果输入是负数直接对负数取余会得到 -1必须先转成无符号类型或者单独按补码处理。第二类典型题是“统计二进制中1的个数”。朴素写法是逐位右移判断最低位是否为1int countBits(int x) { int count 0; while (x) { if (x 1) count; x 1; } return count; }考虑到负数右移会在高位补符号位所以要么把 x 转成unsigned int要么改用x x (x - 1)每次消掉最右边的1。第二种方式的性能更好它的执行次数等于1的个数而不是位数。第三类则是“判断是否为2的幂”“原地交换两个数”“找只出现一次的数字”这类经典位运算题。它们考察的本质都能在《二进制数1》这篇里找到源头位权、移位、按位与或异或。把“逢二进一”和“按位运算”想透了题目的答案几乎是水到渠成的。5.2 HDF5 数据格式属性与数据集的分工二进制的应用绝不只在刷题里。做科学计算或深度学习的人应该都接触过 HDF5这是一种专门用来存大规模数据的文件格式它的设计非常能体现“二进制思维”。HDF5 把数据分成两类对象**数据集Dataset**存放真正的二进制数据比如图像像素、传感器数值**属性Attribute**存放元数据比如单位、时间戳、描述信息。为什么要把它们分开因为纯数据文件用二进制存储效率最高占空间小、写入读取快、不需要解析文本。而属性是供人理解用的所以它可以是字符串、数字等相对轻量的信息。我最早遇到 HDF5 时犯过糊涂以为“打开文件后全是乱码说明文件坏了”。实际上二进制数据本来就不是给人眼直接看的需要配套的库去解释。换一个角度理解一个.npy或.h5文件你可以把它想象成一个压缩包里面既有描述信息属性也有真正的“内部档案”数据集。机器读取时先读属性知道每个维度多大、什么类型再去数据集按二进制偏移精确读取。这种设计本质上就是对“二进制是什么、元数据是什么”的巧妙拆解。5.3 mariadb 二进制安装预编译包到底“预”了什么如果你做过数据库部署还会碰到一个术语叫“二进制安装”比如 MariaDB 官方提供的“Linux 二进制发布包”。这里的“二进制”二字指的就是已经编译好的可执行程序而不是源码。我最初接触“mariadb 二进制安装”时以为它像 Windows 安装包一样双击即可其实流程是下载 tar.gz解压到指定目录然后初始化数据目录、创建系统表最后用bin/mysqld启动服务。它不需要像源码安装那样执行configure和make省去了本机的编译步骤所以部署更快、更稳定出问题的概率更低。对比一下两种方式的适用场景二进制包适合生产环境追求快速上线、环境相对标准化的情况源码安装则适合需要定制编译参数、或者目标设备CPU架构比较特殊的情况。这里的“二进制”强调的是“机器码已经生成好了直接跑就行”的概念。把“二进制”理解为一种交付物形态而非单纯指0和1对理解很多工程文档非常有帮助。5.4 二进制的应用面把话题拉回更广的视野二进制在我们身边的足迹远比想象中多哈希与校验文件校验值、数据包校验码全都要在比特层面运算。网络协议IP 地址掩码、TCP 标志位、报文头结构本质都是定长二进制的比特字段。字符编码ASCII 码、UTF-8 的编码规则本质是二进制数的映射。位图索引数据库里用位图做快速过滤也是基于二进制的与或运算。这些应用中真正难的不是二进制本身而是“如何把一个复杂对象映射成比特结构”。不过那已经超出了《二进制数1》的范畴属于后续进阶内容这里先留个伏笔。6. 用 tracker 打卡的复盘每日一题怎么记才不白记6.1 我记录的字段与节奏讲完技术内容最后聊聊打卡本身。我用牛客 tracker 记录每日一题最初只是简单打勾“已完成”后来发现复盘时完全想不起来当时卡在哪里等于白刷。调整之后我给自己规定了几个必填字段题目标题、核心知识点、代码思路、踩坑点、是否一次通过。每道题少则两三行多则一段代码加注释。以二进制转换题为例我会写“知识点除2取余位权展开踩坑小数转二进制出现无限循环是否通过第2次才通过原因是忽略了保留位数精度”。这些内容看起来简单但在之后复习的时候价值极大。我能清楚地看到自己哪些地方薄弱而不是对着一个打了勾的列表发呆。6.2 复盘时发现的三个误区坚持记录一个月后我发现自己和一起打卡的朋友都踩过同样的坑。第一个误区是“题目做得越多越好”。我一度每天刷四五道转换题自我感觉良好结果合上书自己写一遍还是错在指数从0开始。后来改成“每天三题但必须手写推导过程”正确率反而上来了。刷题不是上发条状态好不好要看有没有真正动手推演。第二个误区是“只关注十进制与二进制的互转忽略二进制内部的运算”。我最初理解的“二进制数1”就是“转换”直到在每日一题里碰到需要手算1011 * 101的题目才发现自己连乘法都没练熟。所以后来强制自己每周至少做一组二进制加减乘除的手算题把运算当成与转换同等重要的基础。第三个误区是“看答案前不给自己思考时间”。Tracker 里记录“是否一次通过”之后我养成了至少独立思考20分钟再参考答案的习惯。哪怕想不出来这20分钟里对题目的“卡感”也是宝贵的复盘素材。直接看解析然后复制代码看起来快实际上什么都没留下。6.3 下一道题怎么选最后给个选题的实用建议《二进制数1》之后的进阶路线我建议按照“转换 - 位运算 - 补码 - 浮点数 - 实际应用”这个顺序来。牛客的每日一题列表其实已经帮你排好了坡度跟着顺序走就好如果你是自己刷题不要直接跳到“位运算求最大值”这种进阶题先把与、或、异或、右移、左移这五个基础操作在纸上推熟再上难度。我个人在实际操作中的体会是二进制这块能不能过关不在于会背多少公式而在于你愿不愿意拿着笔在纸上多推几遍进位、退位和移位。刷题打卡的真正价值是用固定节奏逼自己把这些笨功夫做到位tracker 只是个记录工具真正让知识点留在脑子里的还是每次复盘时亲手写下的那几行推导过程。如果你现在刚打开《二进制数1》这个题目不妨按这篇的顺序先把25、0.375、86这三个数各自完成一遍“十进制转二进制 反向验证 做一次加法”的练习再进入下一题。踩过了这些最基础的坑后面的路会好走很多。