ChatGPT 十六进制转二进制总出错?原因分析与四种可靠做法
把一个 16 位寄存器值拆成逐位对照表,模型给出的二进制串是对的,逐位表却错了一半。这不是提问方式的问题,而是把算术任务派给了文本预测模型。文章拆解出错机制,说明寄存器位定义本身的多版本陷阱,并给出用脚本、工具调用、分治提示词和回读校验来获得可靠结果的完整做法。
读出一个以太网 PHY 状态寄存器的值 0x796d,要求把它拆成 16 个 bit,再对着 IEEE 802.3 的位定义逐条解释。模型给出一串 0b0111100101101101,看着没问题;可往下看逐位对照表,bit2 被标成 0,追问一句「bit2 应该是 1 吧」,它道歉、重新输出,往往又错在另外几个位上。
这件事容易被归因为「提示词没写好」或者「模型选错了」。但把整个交互拆开看,真正的问题在于把一个算术任务派给了文本预测模型,而且在任务里同时混进了「换算」和「查表解释」两件性质完全不同的事。下面把出错机制、隐藏的位定义陷阱和几种稳妥做法讲清楚。
二进制串是对的,逐位表是错的
先把事实核一遍。0x796d 按半字节拆开是 7→0111、9→1001、6→0110、d→1101,拼起来正是 0111 1001 0110 1101,和模型给出的串一致。也就是说,它写出的二进制串本身没有错,错的是后面那张逐位对照表。
对比一下就能看出问题(只列出有分歧的位):
| 位 | 模型标注 | 实际值 |
|---|---|---|
| Bit 11 | 0 | 1 |
| Bit 9 | 1 | 0 |
| Bit 8 | 0 | 1 |
| Bit 7 | 1 | 0 |
| Bit 4 | 1 | 0 |
| Bit 2 | 0 | 1 |
| Bit 1 | 1 | 0 |
16 位里错了 7 位,而且错得很「分散」——不是整段偏移,也不是低四位整体错位。这正是关键线索:模型并不是真的把那个二进制串按位置切开再逐位读,而是把「二进制串」和「逐位对照表」当成两段文本分别续写。两段各自按概率生成,中间没有任何一致性检查,所以串对、表错的情况非常常见。追问「你再看看」只会让它重新采样一次,而不是重新计算。
为什么这类任务天生不适合直接问
模型看不到「位」
十六进制字符串在进入模型前会被切成 token,796d 可能是一个整体片段,也可能被拆成几块。模型处理的最小单位是 token,不是字符,更不是二进制位。要求它「按位切开」,本质上是在要求一个看不见位的东西做位运算。
没有中间变量,也没有进位和对齐约束
手算 16 位转换时,你会守住一个约束:输出必须刚好 16 个字符,且每 4 位必须能拼回原十六进制数字。自回归生成没有这个约束机制,它只保证「下一个字符看起来合理」。输出越长、结构越规整(16 行 × 3 个字段),后半段漂移的概率越高。
它擅长的是把结构复述完整,而不是算对
模型很会生成一张「看起来像寄存器手册」的表格:位号连续、术语专业、格式整齐。至于表格里的数字和上文的串是否自洽,它不会主动去核对。所以你会看到二进制串正确、逐位表错误这种内部矛盾的结果。
所谓的「复查」通常是重写
当被指出错误后,模型的常见反应是道歉加重新输出一份格式略有变化的表。如果它仍然不做实际运算,第二次结果的对错基本靠运气——可能改对了两处,又错了两处新的。
还有一层容易被忽略的坑:位定义不止一份
就算二进制完全算对,逐位解释也未必可信,原因有三个:
- 标准只给了框架。IEEE 802.3 对 MII 寄存器 1 有通用定义,但其中相当一部分位是保留位或由厂商自行定义,不同厂商、同一厂商的不同 revision 都可能不同。
- 模型容易套模板。这次回复里 bit7 被填上了具体含义,而按常见 MII 定义这一位多为保留位——典型的「按格式补全」而非查询结果。
- 有些位不是简单的 1/0。状态寄存器里常见 latching-low(读到 1 后自动清零)、self-clearing 之类的语义,把它们当成普通状态位解读,结论会完全反过来。
结论很直接:换算可以交给工具,位定义必须以手上那颗芯片的 datasheet 为准,模型最多帮你整理格式和解释术语。
四种可靠做法
做法一:让它写脚本,你来跑
这是成本最低、最稳的方式。不要问它「0x796d 的二进制是多少」,而是要求输出一段可执行代码:
val = 0x796d
print(format(val, '016b')) # 0111100101101101
for i in reversed(range(16)):
print(f"bit{i:>2} = {(val >> i) & 1}")
一行版本用于快速核对:
python3 -c "v=0x796d;print(format(v,'016b'));print([(i,(v>>i)&1) for i in range(16)])"
先看 format(v,'016b') 的输出是否等于手工分组的 0111 1001 0110 1101,再拿脚本打印的位列表去填表。这样得到的每一位都有确定来源,不依赖模型的口算。
如果本地不方便装环境,或者需要反复跑批量解析脚本、长期挂着执行(比如配合 Claude Code 之类的命令行工具做批量寄存器解析),可以放到一台长期在线的远程机器上跑,例如整台放在新加坡机房的独享 Mac mini,远程连上去执行,运行环境和网络都稳定,也不占本地资源。这只是可选方案,一次性转换用本地 Python 就够。
做法二:用带代码执行或计算器能力的入口
在支持代码解释器的会话里,明确要求「写 Python 并执行,把 print 的输出原样贴出来」,然后只根据贴出来的输出去解读。判断是否可信的标准只有一个:有没有可复现的中间输出。只给结论、不给执行过程的,一律当作自然语言推测。
需要注意,换成数学类插件也不保证 100% 正确——有人反馈同类问题在数学专用模型上仍然出错。所以看工具是否被真正调用,比看工具叫什么名字更重要。
做法三:分治 + 强制回读校验的提示词
把任务拆开,并明确禁止模型「自行推导」:
任务分三步,不要跳步:
1) 先用代码把 0x796d 转成 16 位二进制,打印 bit15 到 bit0 的列表,把输出原样贴出来。
2) 只使用第 1 步打印出来的列表填对照表,不要重新推导任何一位。
3) 填完后做回读校验:把 16 位按每 4 位拼回十六进制,确认等于 796d。
任何一步对不上就停下来指出,不要猜。
几个要点:
- 要求贴出工具输出原文,而不是转述;
- 要求回读校验(bit → hex),这是唯一能自动发现错位的检查;
- 一次只做一件事:先换算,再对着 datasheet 解释含义,不要塞进同一个提问;
- 也可以按半字节拆:0x7、0x9、0x6、0xd 分别转四位,人眼核对起来也更快。
做法四:保留一份本地核对手段
手边常备两种低成本验证:一是上面的一行 Python;二是记牢 0–F 的四位对照表(0=0000、1=0001……7=0111、8=1000、d=1101、f=1111),任何模型给出的结果都可以在几秒内用半字节分组粗查一遍。半字节对不上,后面的逐位表就不用看了。
收尾前的检查清单
- 二进制串长度是否为 16,高位在前还是低位在前是否说明清楚;
- 逐位表能否反算回原值(这一步能抓出绝大多数错位);
- 位定义的来源是 datasheet 还是模型补全;
- 是否存在 latching、self-clearing 等非普通状态位;
- 保留位是否被强行赋予了含义;
- 换一次提问或换一个入口,结果是否一致——不一致一定有问题,一致也不代表正确。
小结
这不是提问措辞的问题,而是任务分派的问题。进制换算、位运算、校验和计算这类工作交给脚本和工具;术语解释、位含义归纳、脚本框架、结果排版交给模型;每一位的确切定义交给芯片手册。追问「你再算算」基本无效,因为它并不会因为被质疑就开始执行运算。
另外,模型能力和工具开关会持续变化,具体表现请以你当前使用的版本和官方说明为准,别把某一次的失败或成功当成固定结论。