3. 字节序与编码基础:大端序与小端序、ASCII与BCD编码、二进制与十六进制表示法

各位同学,欢迎来到第三讲。今天我们要聊的话题,是金融消息协议解码中最基础、也最容易踩坑的部分——字节序和编码。

说实话,我见过太多新手甚至老手,在这个环节翻车。明明报文解析对了,结果金额算出来差了好几个数量级。为什么?字节序搞反了。所以这一章,咱们把底子打扎实。

3.1 大端序与小端序:谁先谁后的问题

先问个问题:一个16位的整数 0x1234,在内存里是怎么存的?

答案取决于CPU的「脾气」。有的CPU喜欢把高位字节放在低地址,这叫大端序(Big-Endian)。有的CPU反过来,把低位字节放在低地址,这叫小端序(Little-Endian)

举个例子:

数值:0x1234
大端序内存: [0x12] [0x34]   (低地址存高位)
小端序内存: [0x34] [0x12]   (低地址存低位)

我个人习惯把大端序想象成「正常书写顺序」——从左到右,高位在前。小端序嘛,就是反着来。

金融协议中的铁律:绝大多数金融消息协议(比如FIX、ISO 8583、SWIFT)都使用大端序(网络字节序)。这是TCP/IP协议栈定的规矩,金融行业沿用了这个传统。

我在项目中遇到过一件事:某次对接一家海外清算系统,对方文档里写的是「Big-Endian」,结果实际报文发过来是小端序。排查了整整两天,最后发现是对方开发人员把文档写错了。嗯,从那以后,我拿到任何新协议,第一件事就是写个测试用例验证字节序。

3.1.1 如何判断当前系统字节序?

写Python的同学有福了,标准库直接支持:

import sys
print(sys.byteorder)  # 输出 'little' 或 'big'

但更常用的场景是:你收到一串字节流,需要按大端序或小端序解析。用 struct 模块:

import struct

# 大端序解析(网络字节序)
data = b'\x12\x34'
value = struct.unpack('!H', data)[0]  # '!' 表示网络字节序(大端)
print(hex(value))  # 0x1234

# 小端序解析
value_le = struct.unpack('<H', data)[0]  # '<' 表示小端
print(hex(value_le))  # 0x3412

避坑指南:我曾经在解析一个多字节长度字段时,忘了指定字节序,直接用 int.from_bytes() 默认参数。结果在Windows(小端)上跑得好好的,部署到Linux服务器(也是小端)也没问题,但换到一台嵌入式设备(大端)上就全崩了。记住:永远显式指定字节序

3.2 ASCII与BCD编码:数字的两种面孔

金融报文里,数字的表示方式主要有两种:ASCII和BCD。说白了,一个是用字符表示数字,一个是用二进制表示数字。

3.2.1 ASCII编码

ASCII编码就是把每个数字字符转成对应的ASCII码。比如数字 123,在ASCII里就是三个字节:0x31 0x32 0x33

优点:人类可读,调试方便。
缺点:浪费空间。一个数字占一个字节,实际上4位就够了。

举个例子,ISO 8583报文中的金额字段,很多就是用ASCII表示的:

# 金额 123.45 美元,通常表示为 "000000012345"
# 去掉小数点,左补零到12位
amount_ascii = b'000000012345'
print(amount_ascii)  # b'000000012345'

3.2.2 BCD编码

BCD(Binary-Coded Decimal)就聪明多了。它用4位二进制表示一个十进制数字。所以一个字节可以存两个数字。

比如数字 12,BCD编码就是 0x12(高4位是1,低4位是2)。

数字 123 呢?需要两个字节:0x12 0x3F?不对,标准BCD是半字节对齐的。实际编码是 0x01 0x23(前面补0)。

金融协议中的BCD:很多老牌金融系统(尤其是IBM大型机体系)偏爱BCD。SWIFT MT报文中大量使用BCD编码的金额和日期。我当年第一次解析SWIFT报文时,看到一堆 0x12 0x34 还以为是加密数据,后来才反应过来是BCD。

Python实现BCD编码/解码:

def bcd_to_int(bcd_bytes):
    """BCD字节转整数"""
    result = 0
    for byte in bcd_bytes:
        high = (byte >> 4) & 0x0F
        low = byte & 0x0F
        result = result * 100 + (high * 10 + low)
    return result

def int_to_bcd(value, num_bytes=2):
    """整数转BCD字节"""
    result = bytearray()
    for i in range(num_bytes - 1, -1, -1):
        digit_pair = (value // (10 ** (i * 2))) % 100
        high = digit_pair // 10
        low = digit_pair % 10
        result.append((high << 4) | low)
    return bytes(result)

# 测试
bcd_data = b'\x12\x34'
print(bcd_to_int(bcd_data))  # 1234

value = 5678
print(int_to_bcd(value, 2).hex())  # 5678

注意:BCD编码有个变种叫「Packed BCD」,最后一个字节的低4位可能用来存符号(0xC表示正,0xD表示负)。比如 0x12 0x3C 表示 +123。这个在金融系统中很常见,千万别忽略符号位。

3.3 二进制与十六进制表示法

搞金融协议解析,你天天要和十六进制打交道。为什么?因为二进制太长了,写起来费劲。十六进制是二进制的「缩写」,一个十六进制位对应4个二进制位。

举个例子:

二进制: 1101 1110 1010 1101
十六进制: D    E    A    D

你看,16个二进制位,用十六进制4个字符就搞定了。而且转换非常直观——我一般直接在脑子里分组换算。

3.3.1 为什么金融协议偏爱十六进制?

原因很简单:位操作。金融报文里大量使用位图(Bitmap)来表示字段是否存在。比如ISO 8583的主位图,64位,用8个字节表示。每个位代表一个字段。

你想想看,如果用十进制看这8个字节,完全看不出哪个位是1。但用十六进制,一眼就能看出哪些字段被激活了。

# 假设收到一个8字节的位图
bitmap = b'\x80\x00\x00\x00\x00\x00\x00\x01'
# 十六进制:80 00 00 00 00 00 00 01
# 二进制: 1000 0000 ... 0000 0001
# 第1位和第64位被置1

我在调试ISO 8583报文时,习惯先把位图转成十六进制字符串,然后对照协议文档逐位检查。这个方法帮我快速定位过无数次字段缺失的问题。

3.3.2 Python中的进制转换

Python提供了非常方便的进制转换函数:

# 十进制转其他进制
value = 255
print(bin(value))   # 0b11111111
print(hex(value))   # 0xff
print(oct(value))   # 0o377

# 其他进制转十进制
print(int('0xff', 16))   # 255
print(int('11111111', 2)) # 255

# 字节转十六进制字符串
data = b'\x12\x34\xAB\xCD'
print(data.hex())              # 1234abcd
print(data.hex(' '))           # 12 34 ab cd
print(data.hex(':', 2))        # 12:34:abcd

我的调试习惯:每次收到原始报文,第一件事就是打印十六进制转储。用 binascii.hexlify() 或者 bytes.hex() 都行。然后对照协议文档,逐字节核对。这个方法虽然原始,但最可靠。

3.4 本章知识体系

说了这么多,咱们用一张图把核心逻辑串起来:

字节序与编码基础 - 知识体系 字节序 (Endianness) 数字编码 (Encoding) 表示法 (Representation) 大端序 (Big-Endian) 小端序 (Little-Endian) 网络字节序 (大端) ASCII 编码 BCD 编码 (Packed/Unpacked) 符号位处理 (0xC/0xD) 二进制 (Binary) 十六进制 (Hexadecimal) 位图 (Bitmap) 解析 金融协议解析核心原则 先确定字节序 → 再识别编码 → 最后用十六进制验证 实践建议 使用 struct 模块显式指定字节序 | 用 hex() 调试原始报文 | 注意 BCD 符号位

这张图把今天的内容串起来了。你从顶部开始,先搞清楚字节序,再掌握编码方式,最后用十六进制表示法去验证。三步走,基本不会出错。

好了,这一章的内容就到这里。字节序和编码是金融协议解析的「地基」,地基不稳,上层建筑再漂亮也是白搭。下一章我们会进入实战,用Python手写一个简单的报文解析器,到时候这些知识全都会用上。


公众号:蓝海资料掘金营,微信deep3321