数电笔记-数制和编码
Posted _Chance_Zhang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数电笔记-数制和编码相关的知识,希望对你有一定的参考价值。
数制和编码
Since the final exam is upcoming, efforts should be paid to the review of this Application and Design of Digital Logic (ADDL) course. There have been several topics on number system and encoding systems that are of great detail. This paper aims to give a brief introduction to those two systems. Firstly in the encoding system, binary, octal, decimal, hexadecimal systems will be introduced, with their presentation way and their transformation methods. Especially, there will also be topics on the binary system\'s arithmetic, covering addiction, 1st compliment, 2nd compliment and related. For the encoding system of numbers, the most commonly used ones will be included. The following will be an introduction to several character encoding systems. However, this paper will not cover digital image encoding, digital video encoding, or the encoding systems in digital communication.
1 数制
1.1 数制
常用的数制包括有十进制, 二进制, 八进制以及十六进制, 对应情况如下.
Decimal | Binary | Octal | Hexadecimal |
---|---|---|---|
00 | 0000 | 00 | 0 |
01 | 0001 | 01 | 1 |
02 | 0010 | 02 | 2 |
03 | 0011 | 03 | 3 |
04 | 0100 | 04 | 4 |
05 | 0101 | 05 | 5 |
06 | 0110 | 06 | 6 |
07 | 0111 | 07 | 7 |
08 | 1000 | 10 | 8 |
09 | 1001 | 11 | 9 |
10 | 1010 | 12 | A |
11 | 1011 | 13 | B |
12 | 1100 | 14 | C |
13 | 1101 | 15 | D |
14 | 1110 | 16 | E |
15 | 1111 | 17 | F |
编程中, 为了表明使用的数制, 一般给数字加前缀.
Decimal | Binary | Octal | Hexadecimal | |
---|---|---|---|---|
Verilog | 8\'d65 |
8\'b0100_0001 |
8\'o0101 |
8\'h41 |
C | 65 |
0b1000001 |
0101 |
0x41 |
Python | 65 |
0b1000001 |
0o101 |
0x41 |
1.2 二进制的MSB和LSB
二进制是计算机最常用的数制, 因为它可以允许以\\(0\\)和\\(1\\)表示出所有数字.
当一个数字用二进制表示时, 我们称其
- 最左端(权最大)的那一位为Most Significant Bit(MSB),
- 最右端(权最小)的那一位为Least Significant Bit(LSB).
1.3 数制之间的相互转换
这里对数制转换的介绍顺序为, N -> 10, 10 ->2, 2-> N, 10->N.
1.3.1 N -> 10
\\(N\\)进制转为十进制, 使用公式
即可.
1.3.2 10 -> 2
十进制转换为二进制, 十进制数的整数部分和小数部分的转换方法是不同的.
- 整数部分: 通过将十进制数不断除以\\(2\\)获得每一次的余数(\\(0\\) or \\(1\\)), 最后得到的余数为整数的最高位.
- 小数部分: 通过将十进制数不断乘以\\(2\\)获得每一次的整数(0 or 1), 最后得到的整数为小数的最高位.
例子: \\(18.8125\\)
对整数部分处理, 获得的余数序列为\\(01001\\), 故整数部分为\\(10010\\);
对小数部分处理, 获得的整数序列为\\(1101\\), 故小数部分为\\(1101\\);
所以\\(18.8125\\)的二进制表示为\\(10010.1101\\).
注: 整数部分高位是指左边的位; 小数部分的高位指的右边的位.
1.3.3 2 -> N
这里的\\(N\\)进制仅仅指\\(2^i\\)进制, 因为数字系统中最常用的还是\\(2^i\\)进制.
- 二进制转化为八进制: 将二进制数从低位开始, 每三位分为一组, 转变为八进制
- 二进制转为十六进制: 将二进制数从低位开始, 每四位分为一组, 转为十六进制
1.3.4 10 -> N
十进制转换为\\(N\\)进制, 可以先十进制转为\\(2\\)进制, 再\\(2\\)转\\(N\\).
1.3.5 编程中的实现
这里不进行Verilog数制转换的讨论.
// C
// src: https://blog.csdn.net/weixin_44184990/article/details/105019311
// 使用栈进行进制转化,N是非负十进制整数,B是要转化的进制数; 用char原因: 十六进制存在ABC等字符
void conversionBase(int N, int B)
{
if(N == 0 || B < 0)
{
return;
}
char stack[128];//用数组来模拟栈
int top = 0;
if (B <= 10)//比如二进制,八进制的做法
{
while (N)
{
stack[top++] = (N % B) + \'0\';//\'0\',可将int类型转化为字符类型
N /= B;
}
}
else
{
while (N)
{
if (N % B > 9)
{
stack[top++] = (N % B) - 10 + \'A\';//\'A\',可将int类型转换为字符类型
}
else
{
stack[top++] = (N % B) + \'0\';
}
N /= B;
}
}
//从栈顶开始逐个输出字符
for (int i = top - 1 ; i >= 0; i--) {
printf("%c",stack[i]);
}
}
# Python
# number = 233,
int (0xe9) # 233
hex (233) # 0xe9
oct (0xe9) # 0o351
bin (0o351) # 0b11101001
1.4 二进制的算术运算
1.4.1 加法运算
二进制的加法运算类似于十进制的, 逢\\(2\\)进\\(1\\)即可.
1.4.2 乘法与除法运算
二进制的乘法运算是不断将被乘数(或\\(0\\))左移\\(1\\)位然后再相加得到.
二进制的除法运算是不断将被除数(或\\(0\\))右移\\(1\\)位然后再相减得到.
相比于十进制, 区别就在于, 十进制的乘除, 在二进制下, 形式为左移和右移.
1.4.3 固定位数的加法运算
数字逻辑一般限定了数字的位数, 位数固定后的加法需要考虑溢出的情况, 我们用进位输出(carry-out)位来表示. 只有作加法的两个数字发生了溢出, 我们才令carry-out位为\\(1\\).
如果固定位数为\\(1\\), 则我们称其为半加器. 一个半加器只考虑把两个数字加起来, 输入为加数\\(a\\)和\\(b\\), 输出为它们的和\\(s\\), 以及进位输出\\(carry-out\\).
a | b | co | s |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 |
1 | 0 | 0 | 1 |
1 | 1 | 1 | 0 |
全加器相比于半加器, 考虑的是, 作为加法计算中的一个单元.
于是它的输入中, 还有来自上一位的carry-out, 为了分别, 把上一位的carry-out, 在这一位称为carry-in, 中文叫做进位输入.
ci | a | b | co | s |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 0 | 1 | 0 | 1 |
0 | 1 | 0 | 0 | 1 |
0 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 0 | 1 |
1 | 0 | 1 | 1 | 0 |
1 | 1 | 0 | 1 | 0 |
1 | 1 | 1 | 1 | 1 |
1.4.4 反码与补码运算
提出补码是为了将减法运算转换为加法运算, 以下为相关的定义:
原码: 在数字前加一位符号位, 其中\\(0\\)代表为正, \\(1\\)代表为负.
反码, 1st complement: 原码为正时, 其反码为自身; 原码为负时, 其反码为符号位外的每一位取反.
补码, 2nd complement: 原码为正时, 其补码为自身; 原码为负时, 其补码为反码加一.
补码通过将负数纳入表示范围, 可以通过加一个数的负数来代替减法.
原码表示的数字要进行加减法, 首先转为补码表示下的加法, 然后直接二进制相加, 得到的结果就是补码表示的和.
注意补码表示下, 同符号整数的和如果超过位宽, 则会发生溢出.
手算中, 这种情况表现为, 负数加负数为正数, 正数加正数为正数, 但正数加负数绝对不会溢出;
用数字逻辑进行判断, 则看符号位的carry-in和carry-out, 如果它俩相同, 则不发生溢出, 如果它俩不同, 则溢出.
这点可以对照Tab. 全加器的真值表, a和b代表原来两个数的符号, 当
ci != co
时, 出现正加正为负, 负加负为正的情况.
1.5 数据在存储/ 传输中的大小端模式
1.5.1 大小端模式概念
Ref: https://www.cnblogs.com/lingyejun/p/8312838.html
可以把一个二进制数字看作数据, 数据在存储和传输中的基本单位为字节.
一个字节包含\\(8\\)个位, 如果传输的数字用8位以内的二进制数字就可以表示出来, 则不需要考虑大小端. 而如果这个数字需要用超过\\(8\\)个位的二进制数字表示, 则需要考虑大小端.
比如我们的数字是\\(0x12345678\\), 要存储/ 传输这个数字, 我们需要\\(4\\)个字节(, \\(16\\)进制每个数用\\(4\\)位二进制数字表示, 一个字节有\\(8\\)位). 其中\\(0x12\\)是高位字节, \\(0x78\\)是低位字节.
- 大端模式 Big-Endian
数字的高位字节放在内存的高位地址端, 数字的低位字节放在内存的低位地址端.
- 小端模式 Little-Endian
数字的高位字节放在内存的低位地址端, 数字的低位字节放在内存的高位地址端.
注意大小端强调的是字节在内存中的顺序, 而MSB, LSB强调的是一个二进制数字某位的权值.
1.5.2 两种模式对比
大端模式比较符合人的习惯, 且强制变量类型转换(int
转char
)时, 可以直接取低字节.
若是补码形式, 小端模式将符号位放在低字节, 方便查看数字的正负.
1.5.3 不同CPU架构的大小端模式
不同的CPU架构下数字的存储大小端模式是不同的, 在做移植, 或者通信的时候, 需要考虑大小端的问题.
CPU架构 | 模式 |
---|---|
PowerPC | Big-Endian |
IBM | Big-Endian |
Intel x86 | Little-Endian |
ARM | 默认Little-Endian |
TCP/IP网络字节顺序 | Big-Endian |
1.5.4 C语言对大小端进行检验
ref: https://zhuanlan.zhihu.com/p/144718837
在C语言中, 令一字节(byte)为8位(bit), 则从左至右地给这\\(8\\)位编号为\\(7\\sim0\\), 则MSB即为第7位, LSB即为第0位.
1.5.4.1 联合体union的方式
C语言中, union数据所占内存空间为其最大成员所占的空间. 对union内部成员的存取都是相对于基地址偏移量为0开始的. 这里定义两种类型进行读取, 来判断大小端存储方式.
// src: https://zhuanlan.zhihu.com/p/144718837
#include <stdio.h>
int main()
{
union
{
int a; // 4 bytes
char b; // 1 byte
} data;
data.a = 1; //占4 bytes, 十六进制可表示为 0x 00 00 00 01
if(1 == data.b)
{
printf("Little_Endian\\n"); // 如果走该case, 说明a的低字节存到了联合体的低位, 即为小端.
}
else
{
printf("Big_Endian\\n");
}
return 0;
}
1.5.4.2 单字符变量char的方式
// src: https://zhuanlan.zhihu.com/p/144718837
#include <stdio.h>
int main()
{
int a = 1; // 占4 bytes, 十六进制可表示为 0x 00 00 00 01
// b相当于取了a的低地址部分
char *b = (char *)&a; // 占1 byte
if (1 == *b)
{
printf("Little_Endian!\\n"); // 如果走该case, 说明a的低字节取到了b, 属于小端模式
}
else
{
printf("Big_Endian!\\n");
}
return 0;
}
1.6 浮点数的表达与计算
[这里先留坑, 复习时间不够了]
2 编码
2.1 数字的编码
编码就是用\\(0\\)和\\(1\\)来表示各种各样的东西, 这里先从对数字的编码开始.
与之前的二进制更多的计算用途不同, 这里侧重点在于方便表示.
2.1.1 十进制数字的编码
这里的编码需要考虑的东西不多, 主要有三种, 包括
-
恒权: 每一位的权重固定;
-
自反: \\(0\\)位和\\(9\\)位, \\(1\\)位和\\(8\\)位, \\(2\\)位和\\(7\\)位...的编码为, 逐位相互取反.
十进制 | BCD/ 8421码 | 5421码 | 2421码 | 3-excess码 |
---|---|---|---|---|
0 | 0000 | 0000 | 0000 | 0011 |
1 | 0001 | 0010 | 0001 | 0100 |
2 | 0010 | 0010 | 0010 | 0101 |
3 | 0011 | 0011 | 0011 | 0110 |
4 | 0100 | 0100 | 0100 | 0111 |
5 | 0101 | 1000 | 1011 | 1000 |
6 | 0110 | 1001 | 1100 | 1001 |
7 | 0111 | 1010 | 1101 | 1010 |
8 | 1000 | 1011 | 1110 | 1011 |
9 | 1001 | 1100 | 1111 | 1100 |
BCD/ 8421码是恒权码.
5421码是恒权码, 能够用高权重位表示时用高权重位表示.
2421码属于自反码, 这点可以用于记忆编码方式. 所以, "5 用 1011 表示, 而不是 0101".
3-excess码是给BCD/ 8421码加0011得到的, 这样做加法时, 十进制表示时有进位, 码上也会有进位, 得到的是8421码表示和; 当没有进位时, 要把和减去6. 余三码也属于自反码.
2.1.2 格雷码
- 循环: \\(0\\)位和\\(2^{n-1}\\)位相邻.
- 反射: 若以最高位\\(0\\)和\\(1\\)的交界为轴, 低位的代码是轴对称的.
格雷码不属于十进制的编码, 它具有循环和反射的特性. 但格雷码最大的特点在于, 连续的码字之间只有一个位不同, 这使得它在模数转换, 信号传输等场景中, 具有可靠性. 且只有一位的变化, 能够避免过渡噪声, 且避免产生尖峰电流.
一位 | 二位 | 三位 | 四位 |
---|---|---|---|
0 | 00 | 000 | 0000 |
1 | 01 | 001 | 0001 |
11 | 011 | 0011 | |
10 | 010 | 0010 | |
110 | 0110 | ||
111 | 0111 | ||
101 | 0101 | ||
111 | 0111 | ||
1111 | |||
... |
- BCD转格雷码
最高位不变, 从最高位开始从左到右每两位取异或.
- 格雷码转BCD
最高位不变, 从最高位开始从左到右解异或.
异或: 相同取0, 不同取1.
// CPP, Gray Code 和 BCS Code 的相互转换
// src: https://blog.csdn.net/xiayufeng520/article/details/116752570
#include <iostream>
#include <math.h>
int bcd2gray(int bcd)
{
return bcd ^ (bcd >> 1);
}
int gray2bcd(int gray)
{
int bcd = 0x0;
int size = log2(gray);
bcd |= (1 << size);
for ( int i = size; i > 0; i--)
{
bcd |= (((bcd >> i) & 0x01) ^ ((gray >> (i - 1)) & 0x01)) << (i-1);
}
return bcd;
}
int main()
{
int gray_code = 0x16;
int bcd_code = 0x16;
std::cout << bcd2gray(bcd_code) << std::endl;
std::cout << gray2bcd(gray_code) << std::endl;
return 0x0;
}
2.2 字符编码
Ref: https://www.runoob.com/w3cnote/charset-encoding.html (这里就secondary citation吧)
2.2.1 字符集和字符编码
- 字符集Charset
一个系统所支持的所有抽象字符的集合.
- 字符编码Character Encoding
一套将字符逐个对应数字码的规则.
2.2.2 常见字符集和常用编码
- ASCII字符集和编码
American Standard Code for Information Interchange, 美国信息交换标准代码.
ASCII字符集主要包括控制字符(回车键, 退格, 换行键等); 可显示字符(英文大小写字符, 阿拉伯数字和西文符号)
ASCII编码使用\\(7\\)位表示一个字符, 共\\(128\\)个字符. 为了表示更多的欧洲常用字符, 扩展至\\(8\\)位, \\(127\\)位后的符号合称为EASCII. 此时共\\(256\\)个字符.
2.2.2.1 GBXXX字符集和编码
首先出现GB2312编码, 笼括了大部分常用汉字. 微软扩充其为GBK编码. 后来国内进一步扩充至GB 18030-2005.
2.2.2.2 Big5字符集和编码
使用繁体中文(正体中文)社区中最常用的电脑汉字字符集标准, 也是编码方式.
2.2.2.3 Unicode字符集和UTF-X编码
Unicode是一个囊括世界所有字符的字符集. 对于Unicode, 有三种编码方案, 分别为UTF-32, UTF-16和UTF-8.
-
UTF-32对每个字符使用\\(4\\)字节进行编码.
-
UTF-16对每个字符使用\\(2\\)字节进行编码, 囊括了UTF-32前\\(16\\)位常用的字符, 后\\(16\\)位的字符使用"诡异的方式"实现.
-
UTF-8对每个字符使用变长(\\(1\\sim4\\))字节进行编码, 其中第\\(1\\)字节与ASCII兼容. 互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码. 但只使用\\(1\\)字节时,
以上是关于数电笔记-数制和编码的主要内容,如果未能解决你的问题,请参考以下文章