使用方法
- 在「十进制数值」里填 1234.5678(示例值),可以带小数点与负号;数值框里直接按 Enter 就出结果,不用点按钮。
- 「小数位数」默认 16,表示小数部分最多展开 16 位,可填 1~64 之间的整数。想看清循环节就把位数调大,只关心整数位就调到 1。
- 「补码位宽」下拉框决定补码那一行按几位算,可选 8 / 16 / 32 / 64 位。只有整数才给补码,小数会显示「不适用」。
- 「每 4 位加空格」默认勾选,把位串按 4 位一组分开——4 位二进制正好对应 1 位十六进制,对照着看不数格子。
- 下面的「计算过程」表把每一步都列出来:整数部分是「÷ 2 取余」,小数部分是「× 2 取整」。示例一共 27 行,先显示 12 行,点「加载更多」展开其余 15 行;「导出 CSV」「复制步骤表」可以把整张表带走。
这个十进制转二进制转换器能解决什么问题
一是对齐位序与掩码。拿到一个十进制数要写进寄存器、拼进协议报文、或者核对某一位开关时,先看它占几位、哪些位是 1。示例里的 1234 转成 10011010010,一共 11 位;按 0 起编号,第 1 位是 1、第 4 位是 1、第 10 位是 1,其余为 0。这类判断用位串比用十进制快得多。
二是把浮点误差讲清楚而不是背下来。「0.1 加 0.2 不等于 0.3」是常识,但为什么不等、到底差在哪一位,把 0.1 按乘 2 取整展开一遍就明白了:它的循环节是 0011,长度 4 位,永远除不尽,双精度只能截到 53 位有效位为止。
三是当教具用。课本上写「除 2 取余、逆序排列」,但学生常常搞不清「逆序」是逆哪一段。步骤表把每一步的商、余数与得到的位并排列出,末位在哪一目了然:最后一行的余数就是最高位。
四是看负数的补码长什么样。-10 在 8 位下是 1111 0110,在 16 位下多出 8 个 1;写硬件寄存器、解析二进制协议里的有符号字段时,先知道该填哪一串,比事后调试省事。
本页不做这些事:不做字符编码(把字符 A 变成 01000001 属于编码转换,请用「文本转二进制」)、不做二进制的加减乘除(用「二进制计算器」)、不把位串反解回十进制小数(有专门的二进制转十进制页)、也不做任意精度整数——超过双精度能表示的范围会直接报错,而不是给你一串看着对、实际错的 0 和 1。
转换原理:除 2 取余、乘 2 取整与补码
这个十进制转二进制转换器把整数与小数分开处理。整数部分用「除 2 取余、逆序拼接」。代码里就是两行:r = q % 2 取余数、q = Math.floor(q / 2) 取商,一直做到商为 0。第一次得到的余数是最低位,所以要把每轮的余数往前插(bits = r + bits),这也是「逆序」两个字的来历。示例里 1234 要除 11 次,得到 11 位:10011010010。检验一下:1024 + 128 + 64 + 16 + 2 = 1234。
小数部分用「乘 2 取整」。取小数部分 f,算 f × 2,整数位就是下一位二进制数字,剩下的小数部分继续乘 2。示例里 0.5678 的第一轮是 0.5678 × 2 = 1.1356,取走整数位 1,剩下 0.1356 接着算;算满 16 位得到 1001000101011011,而且还没算完。这套算法之所以可靠,是因为「乘 2」在二进制浮点里只是把指数加 1,不引入新的舍入误差——所以前 53 位的结果是被 IEEE 754 精度允许的。
什么时候能除尽?判据不在小数本身,而在化简后的分母:一个分数在二进制下能有限展开,当且仅当它的最简分母是 2 的幂。0.5 = 1/2、0.25 = 1/4、0.0625 = 1/16 都能除尽;0.5678 = 2839/5000,5000 = 23 × 54,除掉 2 的因子后还剩 625,不是 2 的幂,所以永远除不尽。更进一步,循环节的长度等于 2 对 625 的乘法阶:2 对 5、25、125、625 的阶分别是 4、20、100、500,所以 0.5678 的二进制小数循环节长达 500 位;0.1 的分母化简后是 5,阶为 4,循环节就是 0011 这 4 位。这解释了为什么把「小数位数」调到 64 位也看不到重复——你需要的可能是 500 位。
计算与显示分开。IEEE 754 的双精度存储本身有误差,直接打印中间量会得到 0.6704000000000629 这种噪声。工具的做法是:位串的判定用完整的双精度值,一步都不截断;表格里显示的小数按 10 位有效数字,所以看到的是 0.5678 × 2 = 1.1356 这样干净的式子。整数商不参与这个舍入(它们是精确整数,直接原样输出),否则 9007199254740991 会被显示成 9.007199255e+15。
负数的补码按位宽取模。w 位补码的严格定义是「对 2w 取模后的那个非负同余代表」,代码里用 ECMAScript 的 BigInt.asUintN(w, BigInt(x)) 一步算出,示例:-10 在 8 位下得 246,位串 1111 0110;-1 在任意位宽下都是全 1;-128 在 8 位下是 1000 0000——它的相反数 +128 在 8 位补码里没有表示,所以 8 位能表示的范围是 -128~127,而不是对称的 ±127。常见的「按位取反再加一」只是这个定义的等价算法,遇到最小值时会溢出,取模的写法不会。
双精度位模式是另一套规则。小数的位模式遵循 IEEE 754 的 binary64 布局:1 位符号 + 11 位指数(偏移量 1023)+ 52 位尾数,尾数隐含一个前导 1,所以实际有效位是 53 位。示例里 1234.5678 的位模式是 0x40934A456D5CFAAD,指数域 10000001001 等于 1033,减去 1023 得真实指数 10(因为 1234.5678 约等于 1.2056 × 210)。补码与这个布局是两套互不兼容的表示:整数用补码,小数用符号加绝对值,所以本页对含小数的输入不会给补码。
完整示例(数值仅作演示)
示例条件与页面打开时的默认值完全一致:数值 1234.5678、小数位数 16、补码位宽 16 位、勾选每 4 位加空格。实测结果是:
- 整数部分 1234 要除 11 次,得到
10011010010(11 位);小数部分 0.5678 展开 16 位得1001000101011011,16 位内没有终止; - 合并结果是
10011010010.1001000101011011,按 4 位分组显示为100 1101 0010.1001 0001 0101 1011; - 步骤一共 27 行(整数 11 行 + 小数 16 行),表格先显示 12 行,剩下 15 行要点「加载更多」;
- 十六进制
4d2.915b(小数按每 4 位二进制折 1 位十六进制,16 位正好折出 4 位),八进制2322.442555(每 3 位折 1 位,向上取整到 6 位); - IEEE 754 双精度位模式
0x40934A456D5CFAAD,符号位 0、指数域10000001001(1033,真实指数 10)、尾数0011010010100100010101101101010111001111101010101101; - 补码那一行显示「不适用」,因为 1234.5678 含小数;把数值改成整数 1234,16 位补码就是
0000 0100 1101 0010。
顺带做一个对照:JavaScript 内置的 (1234.5678).toString(2) 给出的结果是 10011010010.100100010101101101010111001111101010101101,整数位 11 位、小数位 42 位,加起来刚好 53 位有效位——这不是巧合,而是双精度的有效位数上限。内置方法给的是「最短的、能原样读回来的」那一串,位数随数值浮动;本工具给的是「按你指定的小数位数逐位展开」的那一串,并且把每一步都摆出来,两者的用途不同。
输入范围与对照表
先看整数,从 0 到 16 的对照关系如下表。注意 0 的二进制是单独的一个 0,不是空串——很多手写实现会在这里返回空字符串。
| 十进制 | 二进制 | 四位分组 | 八进制 | 十六进制 | 位数 |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 1 |
| 1 | 1 | 1 | 1 | 1 | 1 |
| 2 | 10 | 10 | 2 | 2 | 2 |
| 3 | 11 | 11 | 3 | 3 | 2 |
| 4 | 100 | 100 | 4 | 4 | 3 |
| 7 | 111 | 111 | 7 | 7 | 3 |
| 8 | 1000 | 1000 | 10 | 8 | 4 |
| 10 | 1010 | 1010 | 12 | A | 4 |
| 12 | 1100 | 1100 | 14 | C | 4 |
| 15 | 1111 | 1111 | 17 | F | 4 |
| 16 | 10000 | 1 0000 | 20 | 10 | 5 |
| 255 | 1111 1111 | 1111 1111 | 377 | FF | 8 |
| 1234 | 10011010010 | 100 1101 0010 | 2322 | 4D2 | 11 |
| 9007199254740991 | 53 个 1 | 1111 1111 … | — | 1F FFFF FFFF FFFF | 53 |
再看小数。能否除尽只取决于化简后的分母是不是 2 的幂,下面这张表把常见小数分成两拨:
| 十进制小数 | 化简后的分数 | 二进制(最多 16 位) | 是否终止 |
|---|---|---|---|
| 0.5 | 1/2 | 0.1 | 终止(1 位) |
| 0.25 | 1/4 | 0.01 | 终止(2 位) |
| 0.125 | 1/8 | 0.001 | 终止(3 位) |
| 0.0625 | 1/16 | 0.0001 | 终止(4 位) |
| 0.1 | 1/10 | 0.0001100110011001 | 不终止,循环节 0011(4 位) |
| 0.2 | 1/5 | 0.0011001100110011 | 不终止,循环节 0011(4 位) |
| 0.3 | 3/10 | 0.0100110011001100 | 不终止,循环节 0011(4 位) |
| 0.05 | 1/20 | 0.0000110011001100 | 不终止,循环节 0011(4 位) |
| 0.5678 | 2839/5000 | 0.1001000101011011 | 不终止,循环节 500 位 |
补码的可表示范围随位宽变化,这里列出实际会用到档位。注意范围是不对称的:负端比正端多一个数。
| 位宽 | 可表示范围 | 取值个数 | -10 的位串 | -1 的位串 |
|---|---|---|---|---|
| 8 位 | -128 ~ 127 | 256 | 1111 0110 | 1111 1111 |
| 16 位 | -32768 ~ 32767 | 65536 | 1111 1111 1111 0110 | 16 个 1 |
| 32 位 | -2147483648 ~ 2147483647 | 4294967296 | 28 个 1 加 0110 | 32 个 1 |
| 64 位 | -9223372036854775808 ~ 9223372036854775807 | 18446744073709551616 | 60 个 1 加 0110 | 64 个 1 |
输入侧的限制有三条要说清:数值绝对值不能超过 9007199254740991(253−1),否则双精度已经不能精确表示整数;小数位数只能是 1~64 的整数,超范围会被拒绝;数值框只接受十进制写法,不接受 0x 前缀、千分位逗号和空格,填了会直接报错而不是被悄悄解析成别的数。
结果边界与舍入
- 53 位有效位是硬边界。253−1 = 9007199254740991 能精确表示,再往上就开始丢位:把 9007199254740993 交给 JavaScript,双精度里实际存的是 9007199254740992,差了整整 1。判断方式也很简单——
253 + 1 === 253返回true。这个十进制转二进制转换器在这条边界上直接报错而不是照算,因为「位串看上去对、实际差 1」比「明确拒绝」危险得多。 - 小数部分是截断,不是四舍五入。算满你指定的位数就停,最后一位不做进位。所以把 0.5678 的小数位数从 16 改成 15,得到的是前 15 位,而不是把第 15 位四舍五入过的值。
- 显示层有一次 10 位有效数字的舍入,但它不影响位串。表格里的 0.6704 实际是 0.6704000000000629 这类双精度噪声的显示形式;判定「取 0 还是取 1」用的仍是完整值,整数商也不参与这个舍入,它们是精确整数。
- 「终止」判定的是双精度里存的那个近似值,不是数学上的十进制数。0.1 的数学展开永远不终止(循环节
0011),但双精度存下的近似值恰好等于 3602879701896397 / 255,所以把小数位数调到 64 位时,工具会在第 55 位报「精确终止」。同类数据:0.2 与 0.3 在第 54 位终止,0.5678 在第 53 位终止。两种说法并不矛盾——本页报告的是「计算机里真正参与运算的那个数」,因为决定程序结果的是它,而不是纸面上的 0.1。 - 浮点误差会累积,而且方向随数值而变。经典例子是
0.1 + 0.2 = 0.30000000000000004,它们的二进制展开都是无限循环、只能截断,相加后误差落在第 17 位小数。要求精确小数运算(比如对账、金额)就不该用双精度,本页也因此不承诺小数部分「精确等于」你写的十进制数。 - 补码只对整数有意义。位宽选 8 位、输入 200 时,工具照样给出
1100 1000,但会提示 200 超出了 8 位有符号范围 -128~127——这一串按有符号读是 -56,这是一个非常典型的协议解析坑。 - 负零与负小数。
-0与0的位串都是0(补码同为全 0),工具会在提示里说明;-0.5则输出-0.1,符号独立于位串保留,这与 JavaScript 内置toString(2)的行为一致。 - 八进制与十六进制按二进制位数折算。4 位二进制折 1 位十六进制、3 位折 1 位八进制,所以十六进制小数位取
⌈小数位数 ÷ 4⌉、八进制取⌈小数位数 ÷ 3⌉:示例的 16 位小数折出 4 位十六进制(915b)与 6 位八进制(442555)。这两个数的来源是内置的Number.prototype.toString(16)与toString(8),本工具只做了位数截断,没有另写一套进位算法。
常见问题
为什么 0.1 转成二进制永远转不完?
因为 0.1 = 1/10,化简后分母 10 = 2 × 5,除掉一个 2 还剩因子 5,不是 2 的幂。二进制小数只能在分母为 2 的幂时有限展开(0.5、0.25、0.125 都是这样)。0.1 的循环节是 0011,长度 4 位,双精度最多保留 53 位有效位,所以只能截断——这就是 0.1 + 0.2 = 0.30000000000000004 的根源。顺带一提,把小数位数调到 64 位时工具会显示「精确终止于第 55 位」,那是双精度近似值的展开长度(0.1 存成 3602879701896397 / 255),与「0.1 本身不终止」并不矛盾,细节见上一节的边界说明。
0.5678 展开 16 位还没完,要多少位才循环?
循环节长度是 500 位。0.5678 = 2839/5000,5000 = 23 × 54,奇因子是 625;循环节长度等于 2 对 625 的乘法阶,而 2 对 5、25、125、625 的阶分别是 4、20、100、500。工具的小数位数上限是 64 位,所以你看不到重复——这不是工具算错,而是 64 位本来就不够。
负数为什么显示成一串 1 开头的位串?
那是补码。w 位补码的定义是「对 2w 取模后的非负同余代表」,所以 -10 在 8 位下等于 256 − 10 = 246,位串 1111 0110。位宽越大,前面补的 1 越多:16 位是 1111 1111 1111 0110,32 位是 28 个 1 加 0110。工具里用 BigInt.asUintN 一步算出,避免手写「取反加一」在最小值处溢出。
JavaScript 里 (-10).toString(2) 得到 -1010,和这里不一样?
它们本来就不是一件事。toString(2) 输出的是「负号 + 绝对值」,不涉及位宽,也不涉及补码;而补码必须指定位宽才有意义(8 位和 16 位的 -10 位串长度不同)。要位模式就用补码,要数学意义上的二进制表示就用 toString(2),两者不能互相替代。
为什么小数没有补码?
补码是整数的一种编码规则,建立在对 2w 取模之上,小数没有「按位取模」这回事。IEEE 754 给小数用的是另一套:1 位符号位 + 11 位指数 + 52 位尾数,符号用「符号加绝对值」的方式单独记录。所以本页对含小数的输入不给补码,而是给出双精度位模式,两者对应的场景完全不同。
表格里的小数为什么比我输入的数字短了一点?
显示时按 10 位有效数字整理过,目的是不把 0.6704000000000629 这种浮点噪声摆在表格里。位串的判定没有被这一步影响,用的是完整的双精度值;整数商也不做这个处理,它们是精确值,改了反而会出错。
填一个特别大的整数会怎样?
绝对值超过 253−1 = 9007199254740991 会直接报错。原因是双精度能精确表示的整数到此为止:9007199254740993 存进去会变成 9007199254740992,差的 1 不会报任何错,却会让整条位串从某个位置起全部作废。如果你确实要处理更大的整数,应该用支持任意精度的 BigInt 自己算,本页不做这种静默降级。
每 4 位空一格有什么用?能直接当字符编码看吗?
空格是为了对齐:4 位二进制正好等于 1 位十六进制,分组之后 100 1101 0010 可以直接读成 4D2,和十六进制编辑器里的显示对得上。但要提醒一句:这里的 10011010010 是数值 1234 的二进制形式,不是某个字符的编码;想把文字变成二进制要用「文本转二进制」,那才是按 UTF-8 逐字节展开。