☰
从二进制数1开始:进制转换、位运算与工程实战复盘
2026/9/30 3:11:35 网站建设 项目流程

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^0=1),第二位是 (2^1=2),第三位是 (2^2=4),第四位是 (2^3=8),所以它的真实值就是 (1\times1+0\times2+1\times4+1\times8=13)。

注意:位权的指数是从0开始,而不是从1开始。这是新手最容易搞混的地方。

2.2 位权展开式:从十进制迁移到任意进制

理解了位权之后,任何进制到十进制的转换都成了同一套动作:把每一位数字乘以它所在位置的位权,再全部加起来。这个方法有个正式名字叫位权展开法(按权展开求和)。

举几个例子:

  • 二进制10101:(1\times16+0\times8+1\times4+0\times2+1\times1=21)。
  • 八进制37:(3\times8+7\times1=31)。
  • 十六进制2F:(2\times16+15\times1=47),字母F代表15。

在学会用“除基取余”之前,我建议刚开始练转换的时候,老老实实先列出位权再逐位相加。这个过程能把“进制”的内核练成肌肉记忆。我自己就是这样练的:每天在 tracker 里记录三道转换题,先展开再化简,错的就标红,连续一周之后基本不会再犯“把指数搞错”的低级错误。

很多人问,既然掌握位权展开就能算十进制,为什么还要学除2取余?因为展开法需要你先知道每一位是什么,这相当于“结果已知求过程”;而编程中更常遇到的场景是反过来——给你一个十进制数,让你生成二进制表示,这时候就需要一个系统性的转换算法,也就是下一章的内容。

2.3 计算机选二进制的三个现实原因

聊到二进制,总有人问:十进制用得好好的,计算机为什么非要用0和1?我总结下来有三个核心原因,面试时也常被问到。

第一,物理实现最可靠。晶体管只有“导通”和“截止”两种稳定状态,对应电压的高和低。如果用十个电平状态去表达0到9,噪声稍大一点就无法区分,硬件成本会急剧上升;而二值状态抗干扰能力极强,这是工程上的最优解。

第二,二值逻辑和硬件天然匹配。“与、或、非”这些逻辑运算在布尔代数里只有真和假两种结果,晶体管电路能够直接实现。二进制的一位恰好就是布尔逻辑的一个条件,这让运算电路设计变得非常简单。

第三,运算规则简化。十进制乘法表有45条要背,二进制乘法表只有4条:0×0=0,0×1=0,1×0=0,1×1=1。运算规则越少,电路实现越简单。后面你会看到,二进制乘法和除法甚至能拆成“移位加加”和“移位减减”,一个加法器就能扛起大部分工作。

理解这三个原因之后,二进制就不再是一个“考试要考”的规矩,而是一个被工程需求倒逼出来的自然选择。

3. 转换套路全集:整数、小数和“一眼看出答案”的速算

3.1 整数转换:除2取余法

十进制整数转二进制,标准方法是除2取余,逆序排列。拿25举例:

  1. (25 \div 2 = 12) 余1
  2. (12 \div 2 = 6) 余0
  3. (6 \div 2 = 3) 余0
  4. (3 \div 2 = 1) 余1
  5. (1 \div 2 = 0) 余1

把余数从下往上读:11001,所以 (25_{10}=11001_2)。这里的关键是最后一步一定要除到商为0为止,很多人中途停下来,导致最高位丢失,这是我最常看到的低级错误。

我自己的检验习惯是算完之后立刻用位权展开法验证一遍:(11001 = 16+8+0+0+1=25),对上了才算通过。在 tracker 打卡时,我会把“除完最后一步商是否已经为0”作为检查清单里的一条,减少无谓的失误。

顺便提一下:这个套路对任何进制都适用。转八进制就除8取余,转十六进制就除16取余,只是十六进制的余数在10到15时需要写成A到F。所以学一个“除基取余”,等于学了所有进制往十进制的反向转换逻辑。

3.2 小数转换:乘2取整法

十进制小数转二进制和整数是完全相反的思路:乘2取整,顺序排列。以0.375为例:

  1. (0.375 \times 2 = 0.75),取整数部分0
  2. (0.75 \times 2 = 1.5),取整数部分1,剩下0.5
  3. (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.25+0.125=0.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。拆的过程是 (86=64+16+4+2)(也可以用84+2但没必要),从最高位往下填,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\times2=0.4(0))、(0.4\times2=0.8(0))、(0.8\times2=1.6(1))、(0.6\times2=1.2(1))、(0.2\times2=0.4(0)),因此前5位是00110。但请不要直接写“0.00110”,要注明这是保留5位的近似值,因为再往后算还会继续出0110的循环。

4. 运算里最容易被考到的:加法、补码、除法与校验

4.1 加法与减法:逢二进一,借一当二

二进制加法没有悬念:逢二进一。1+1=10,这里的10不是“十”,而是二进制的“二”。多位相加时,进位会像水波一样逐级往前传。试着算1011 + 1101:

  • 最低位:1+1=10,写下0,进1;
  • 第二位:1+0+进位1=10,写下0,进1;
  • 第三位:0+1+进位1=10,写下0,进1;
  • 最高位:1+1+进位1=11,写11。

结果是11000。这里最容易出错的不是某一位的加法,而是“进位叠加”——多位都有1时,进位会连续积累,初学者经常漏掉中间的某一级。

减法稍微绕一些,规则是借一当二。比如1000 - 0011,最低位0减1不够,向高位借1,借来的1在本位变成2,所以最低位是2-1=1;第二位此时又不够,继续向更高位借……这个过程和十进制减法极其相似,只是“借一”变成“借一当二”而不是“借一当十”。我建议初学阶段直接用“补码代替减法”的思路来理解,这样反而更接近计算机的实际做法,下一小节展开。

4.2 二进制除法:长除法与移位除法

很多人把除法当成二进制运算里的难点,其实它和十进制长除法没什么两样。以1101 ÷ 10(13 ÷ 2)为例:

  1. 从最高位开始取被除数,先看第一位1,不够除,继续取;
  2. 前两位11,除以10商1余1;
  3. 把第三位0落下来,变成10,除以10商1余0;
  4. 把第四位1落下来,变成1,不够除,商0余1。

所以商是110,余数是1。验证:(6\times2+1=13),完全正确。这个过程的启示是:二进制的除法本质是移位和减法,每一位的商只可能是0或1,不需要像十进制那样尝试1到9的倍数,硬件实现反而更简单。

在编程题里最常见的其实是“用移位判断整除性”。比如判断一个数是不是2的幂,经典写法就是n > 0 && (n & (n - 1)) == 0。这个式子的原理是:2的幂在二进制中只有一个1,减去1后,这个1会变成多位1,两者按位与会得到全0。我第一次做这道题时完全看不懂,后来把8(1000)和7(0111)写在纸上按位与了一下,瞬间就通了。这类题目考的不是“会不会做除法”,而是“能不能从二进制的角度理解乘除法”。

4.3 LRC校验码:一个需要“二进制相加”的真实例子

搜资料时看到不少人问“LRC校验码需要换算成二进制相加吗”,这里统一回答一下:LRC(Longitudinal Redundancy Check,纵向冗余校验)算的就是二进制运算,而且是最简单的按位异或。常见于串口通信和 ASCII 协议,做法是把所有数据字节先按位异或,得到一个8位校验值。

举例,要发送三个字节0x31 0x32 0x33(即字符"123"):

  1. 0x31 ^ 0x32 = 0x03(因为00110001 ⊕ 00110010 = 00000011)
  2. 0x03 ^ 0x33 = 0x30(00000011 ⊕ 00110011 = 00110000)

所以LRC是0x30。注意,这里用的是“按位异或”而不是“普通加法”,所以不需要进到高位。很多初学者把三个十六进制数加起来再去低8位,得到的结果通常会不同——只有异或才是LRC,加法那是 checksum 的算法。这个细节在真实调试串口时特别容易踩,被坑过一次就记住了。

常见误区:LRC不是“把二进制相加后取进位”,而是“逐字节做异或”。两者结果常常不一样,别搞混。

4.4 不要急着背原码反码补码

刷题打卡到运算这一步,很多人立刻开始背原码、反码、补码的定义,觉得“背下来才算学会”。我的看法正好相反:先理解“补码是为了解决什么问题”再背不迟。

假设只有4位二进制,想表示负数。原码方案里-5是1101,但这样会出现“+0=0000,-0=1000”两个零,且加法运算必须先判断符号,非常麻烦。补码的聪明之处在于让减法变成“加上一个负数”,而这个负数对应的二进制恰好是它相反数的补码。以 -5 为例,它的补码是1011,而且 (5+(-5)=0101+1011=10000),最前面的1超出4位直接丢掉,结果为0。正是这种“取反加一”的设计,让电路里只有加法器也能算减法。

我在 tracker 的笔记里给这个知识点单独开了一页,目的不是背表,而是记录“为什么计算机要用补码存储整数”。这个问题面试中出现的频率极高,而且只要把上述逻辑讲清楚,基本上就是合格的回答。等这一篇消化完,再往下学浮点数的 IEEE 754 表示,会顺畅很多。

5. 从牛客题到工程现场:二进制的几个“亲戚”

5.1 C++ 刷题中最常见的二进制题型

说回牛客的每日一题。二进制专题的常见 C++ 题目大致分三类:进制转换、位运算技巧、二进制计数。

第一类最典型:输入十进制整数,输出它的二进制表示。初学者写while (n > 0) { ans += n % 2; n /= 2; },但要注意结果的拼接顺序,因为低位先算出来,最终要反转。另外,如果输入是负数,直接对负数取余会得到 -1,必须先转成无符号类型,或者单独按补码处理。

第二类典型题是“统计二进制中1的个数”。朴素写法是逐位右移判断最低位是否为1:

int 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这三个数各自完成一遍“十进制转二进制 + 反向验证 + 做一次加法”的练习,再进入下一题。踩过了这些最基础的坑,后面的路会好走很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询