BCD 码、BIN 码、ASCII 码
软硬件对接常用编码
BCD 码
BCD 码(Binary Coded Decimal),也称二进码十进数,是一种用二进制表示十进制数的编码,可分为有权码和无权码。其中,常见的有权 BCD 码有 8421 码、2421 码、5421 码,无权 BCD 码有余 3 码、余 3 循环码、格雷码。其中 8421 码是最基本和最常用的,也是文章主讲的。
在 BCD 码中,最常用的就是 8421,那么什么是 8421 呢?一个字节(byte)总共 8 位(bit),每 4 位成一组的方式,分 2 组,每组表示 1 个十进制数字(0-9)。比如二进制的 0101
表示十进制的 5
,那么 0010 0010
则表示 22
。 每一位1代表的十进制数称为这一位的权,因为每位的权是固定,所以8421码也是恒权码。BCD 编码广泛用于金融系统、嵌入式系统等需要精确表示数字的场合。
特点
BCD 码能够精确计算,每个数位直接用二进制表示,不涉及浮点数或舍入误差,适用于金融、嵌入式系统(计数器、显示设备)、工业自动化(传输数字信号)等需要高度精确的场合。但是空间利用率低,一个字节(byte)通常可以表示 0~255
的数字,但 BCD 只能表示 0~99
。而且处理效率较低,计算时要比直接使用二进制编码复杂,因为需要对每个数字分别进行处理。
BCD 还分压缩(Packed BCD)与未压缩(Unpacked BCD),正常情况下我们使用的就是压缩的 BCD 码,而未压缩的 BCD,则是每个字节仅存储一个十进制数字,高 4 位用
0000
填充。例如0000 0001 0000 0001
表示十进制的11
Java 代码生成
十进制转 BCD
/**
* 十进制转 BCD
* 需要将每一位十进制数分别转换为4位二进制数,然后按 2 位一组合并
* @param dec 10进制数字
* @return BCD 码
*/
public static byte[] decimal2BCD(int dec) {
// 计算 BCD 码数组的长度,2位一组
int bcdLength = (int) Math.ceil((double) Integer.toString(dec).length() / 2);
// 创建存储 BCD 码的字节数组
byte[] bcdCode = new byte[bcdLength];
// 从右向左遍历BCD码数组
for (int i = bcdLength - 1; i >= 0; i--) {
// 取出最低位的十进制数字
byte lower = (byte) (dec % 10);
// 取出次低位的十进制数字
byte higher = (byte) ((dec / 10) % 10);
// 使用位运算将两个十进制数字合并成一个字节
bcdCode[i] = (byte) ((higher << 4) | lower);
// 将temp右移两位,准备处理下两位十进制数字
dec /= 100;
}
return bcdCode;
}
BCD 转十进制
// 16 进制字符数组
static final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
/**
* BCD 转十进制
* @param bcd 码
* @return 十进制码
*/
public static int bcd2Decimal(byte bcd) {
int high = (bcd & 0xF0) >> 4; // 高位
int low = bcd & 0x0F; // 低位
return high * 10 + low; // 组合成十进制数
}
/**
* BCD 码转十进制
* @param dataByte BCD 码
* @return 十进制数字
*/
public static int bcd2Decimal(byte[] dataByte) {
StringBuilder sb = new StringBuilder();
for (byte bcd : dataByte) {
int result = bcd2Decimal(bcd);
sb.append(result);
}
return Integer.parseInt(sb.toString());
}
/**
* 开发环境一般 bcd 解析后可能是还有小数的数据都是通过约定小数点位置在哪几位,往往需要指定小数点的位置
*
* @param dataByte bcd 码
* @param left 左移位数, 123456 -> 12345.6
* @return 准确数字
*/
public static BigDecimal bcd2Decimal(byte[] dataByte, int left) {
byte[] data = convert2LittleEndian(dataByte); // 转小端
char[] chars = new char[data.length * 2];
for (int i = 0; i < data.length; ++i) {
chars[i * 2] = HEX_CHAR[data[i] >> 4 & 15];
chars[i * 2 + 1] = HEX_CHAR[data[i] & 15];
}
String str = new String(chars).toLowerCase();
return new BigDecimal(str).movePointLeft(left);
}
/**
* 互转小端
*
* @param data 原始数据
* @return 转成小端的 bcd 码
*/
public static byte[] convert2LittleEndian(byte[] data) {
byte[] converted = new byte[data.length];
for (int i = 0; i < data.length; i++) {
converted[i] = data[data.length - i - 1];
}
return converted;
}
BIN 码
BIN 也就是二进制,逢二进一,也就是平时所说的 0101
没啥特别。基本也就是**位(Bit)**为最小单位,表示 0
和 1
,Java 中最小的数据类型是由 8 Bit 组成一个的 字节(Byte), 可以表示 0~255
。每一位代表 2 的幂次,右边的最低位(LSB,Least Significant Bit)是 2 的 0 次幂,左边的位数依次是 2 的 1 次幂、2 的 2 次幂,依此类推。
使用 BIN 场景一般都是与硬件对接较多,在计算机系统里面都很常见,能够非常高效地表示数据。比如,8 位二进制可以表示 256 个值。二进制很适合逻辑运算。但是普通人类基本无法阅读,更别谈操作了。
进制转换
二进制到十进制:通过计算每个位的权重(2 的幂次)求和。 例如,二进制 1011 = 1 * 2^3 + 0 * 2^2 + 1 * 2^1 + 1 * 2^0 = 11(十进制)。
/**
*
* 十进制转二进制
* @param dec 十进制
* @return 二进制
*/
public static byte[] decimal2byte(int dec) {
ByteBuffer byteBuffer = ByteBuffer.allocate(4); //这里的4是与设备下发要求的长度一致
byteBuffer.order(ByteOrder.LITTLE_ENDIAN); //这里是小端模式
byteBuffer.putInt(dec);
byte[] bytes =byteBuffer.array();
return bytes;
}
/**
* 二进制转十六进制
*
* @param bytes
* @return
*/
public static int byte2Hex(byte[] bytes) {
int result = 0;
for (int i = bytes.length - 1; i >= 0; i--) {
result = (result << 8) | (bytes[i] & 0xFF);
}
return result;
}
二进制到十六进制:每 4 位二进制数对应一个十六进制数。 例如,二进制 10110111 分为两组:1011 和 0111,分别对应十六进制的 B7。大多数使用 Integer
类提供的工具就可直接转换。
ASCII
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是最早的字符编码标准之一,用于在计算机中表示文本字符。它为数字、字母、标点符号以及控制字符分配了唯一的整数编码。ASCII 编码定义了 128 个字符的映射,用于支持信息在计算机、通信设备等系统之间的交换。标准 ASCII 使用 7 位二进制数来表示每个字符,因此可以表示 2^7 = 128 个字符。编码范围从 0 到 127。
扩展 ASCII
标准 ASCII 仅支持 128 个字符,无法表示世界上大部分语言的字符。仅适用于现代英语的字符和少量符号,不适用于包括中文、日文等在内的其他语言。为了适应更多字符的表示需求,后来推出了扩展 ASCII,它使用 8 位(1 字节)来表示字符,能够支持 256 个字符(2^8 = 256)。扩展 ASCII 在 0–127 位置与标准 ASCII 保持一致,但增加了 128–255 的字符,这些字符可以用来表示其他符号、外语字符、图形符号等。
随着多语言和多字符的需求增加,ASCII 的局限性促使人们开发了更多的字符编码标准,如 Unicode 和 UTF-8,这些编码可以表示全球几乎所有的字符。
- Unicode:Unicode 是一种字符集标准,支持几乎所有书写系统。Unicode 的前 128 个字符与 ASCII 完全兼容,这意味着任何 ASCII 文件都是合法的 Unicode 文件。
- UTF-8:UTF-8 是 Unicode 的一种编码方式,使用 1 到 4 个字节编码字符。它在编码 0–127 范围的字符时与 ASCII 完全一致,因此 UTF-8 文件也与 ASCII 文件兼容。
/**
*简单么?
* @param b 字节
* @return 字符
*/
public static char byte2ASCII(byte b) {
return (char) b;
}
/**
* 将字节数组转换为十六进制字符串
* @param bytes 二进制数组
* @return 16进制字符串
*/
public static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X", b));
}
return sb.toString();
}
大小端
大小端(Endianness) 是指多字节数据(如 int, float)在计算机内存中存储顺序的一种约定。它定义了在存储或传输数据时,数据的字节顺序应该是从最低有效字节到最高有效字节,还是相反。当一个多字节的数据被存储到内存时,由于一个字节(8 位)是最小的存储单元,不同的处理器或计算系统可能会有不同的约定来决定如何排列这些字节。这种约定有两种主要方式:大端序(Big Endian)和小端序(Little Endian)。大小端主要影响多字节数据(如整数、浮点数)在内存中的存储顺序,不同系统之间传递数据时需要特别注意字节序的一致性
大端序(Big Endian)
在大端序中,最高有效字节(Most Significant Byte,MSB)存储在内存的最低地址,而最低有效字节(Least Significant Byte,LSB)存储在内存的最高地址。
大端序更合适人类的阅读习惯:从左到右依次排列,高位在前,低位在后
一个 32 位(4 字节)的整数 0x12345678,在大端序中将按以下顺序存储到内存中:
地址: | 0x00 | 0x01 | 0x02 | 0x03 |
值: | 12 | 34 | 56 | 78 |
小端序(Little Endian)
小端序中,最低有效字节(LSB)存储在内存的最低地址,而最高有效字节(MSB)存储在内存的最高地址。
小端序更适合处理器直接操作:低位先存储在低地址(在内存的低地址处存储),这与多数处理器逐字节处理数据时更加高效(符合计算机处理器的处理方式)。
对于相同的整数 0x12345678,在小端序中将按以下顺序存储到内存中:
地址: | 0x00 | 0x01 | 0x02 | 0x03 |
值: | 78 | 56 | 34 | 12 |
在小端序中,最低位的字节 0x78 存储在最小地址处,依次存储到 0x12。
大小端转换
Java 默认使用大端序,如果要在 Java 中处理小端序数据,可以手动调整字节顺序。
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class EndiannessExample {
public static void main(String[] args) {
int value = 0x12345678;
// 大端序(默认)
ByteBuffer bufferBigEndian = ByteBuffer.allocate(4);
bufferBigEndian.order(ByteOrder.BIG_ENDIAN);
bufferBigEndian.putInt(value);
System.out.println("大端序: " + bytesToHex(bufferBigEndian.array()));
// 小端序
ByteBuffer bufferLittleEndian = ByteBuffer.allocate(4);
bufferLittleEndian.order(ByteOrder.LITTLE_ENDIAN);
bufferLittleEndian.putInt(value);
System.out.println("小端序: " + bytesToHex(bufferLittleEndian.array()));
}
// 将字节数组转换为十六进制字符串
public static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X ", b));
}
return sb.toString();
}
}