前置知识: 入门指南

数的表示与编码

00:00
10 min Beginner 2026/6/14

进制转换、原码反码补码、IEEE 754浮点数、ASCII与Unicode编码。

1. 进制与转换

计算机内部使用二进制,但人类习惯十进制。不同进制是同一数值的不同表示方式。

1.1 常用进制

进制基数数字符号前缀示例
二进制20, 10b0b1010
八进制80~70012
十进制100~910
十六进制1609, AF0x0xA

同一个数在不同进制下的表示:0b1010 = 012 = 10 = 0xA

1.2 任意进制 → 十进制

方法:按权展开求和。每一位的权值 = 数字 × 基数^位序号(从右往左,从0开始)。

二进制 1011 → 十进制:
  1×2³ + 0×2² + 1×2¹ + 1×2⁰
= 8 + 0 + 2 + 1
= 11

八进制 17 → 十进制:
  1×8¹ + 7×8⁰
= 8 + 7
= 15

十六进制 0xFF → 十进制:
  15×16¹ + 15×16⁰
= 240 + 15
= 255

1.3 十进制 → 任意进制

方法:除基取余,逆序排列

十进制 25 → 二进制:
  25 ÷ 2 = 12 ... 余1  ← 最低位
  12 ÷ 2 = 6  ... 余0
   6 ÷ 2 = 3  ... 余0
   3 ÷ 2 = 1  ... 余1
   1 ÷ 2 = 0  ... 余1  ← 最高位
  结果: 11001

十进制 255 → 十六进制:
  255 ÷ 16 = 15 ... 余15 (F)  ← 最低位
   15 ÷ 16 = 0  ... 余15 (F)  ← 最高位
  结果: FF

1.4 二进制 ↔ 八进制

方法:每3位二进制对应1位八进制(因为 2³ = 8)。

二进制 011 010 111 → 八进制
  011 = 3
  010 = 2
  111 = 7
  结果: 327

八进制 527 → 二进制
  5 = 101
  2 = 010
  7 = 111
  结果: 101 010 111

1.5 二进制 ↔ 十六进制

方法:每4位二进制对应1位十六进制(因为 2⁴ = 16)。

二进制 1010 1111 → 十六进制
  1010 = A
  1111 = F
  结果: AF

十六进制 0xC3 → 二进制
  C = 1100
  3 = 0011
  结果: 1100 0011

1.6 代码实现进制转换

#include <stdio.h>
#include <string.h>

// 十进制整数转二进制字符串
void dec_to_bin(int n, char* buf, int size) {
    if (n == 0) {
        strcpy(buf, "0");
        return;
    }
    int i = 0;
    int is_negative = 0;
    if (n < 0) { is_negative = 1; n = -n; }
    while (n > 0 && i < size - 1) {
        buf[i++] = (n % 2) + '0';
        n /= 2;
    }
    // 反转
    for (int j = 0; j < i / 2; j++) {
        char tmp = buf[j];
        buf[j] = buf[i - 1 - j];
        buf[i - 1 - j] = tmp;
    }
    buf[i] = '\0';
}

int main() {
    char buf[33];
    dec_to_bin(25, buf, sizeof(buf));
    printf("25 的二进制: %s\n", buf);  // 11001
    return 0;
}

2. 原码、反码与补码

计算机需要表示正数和负数。8位二进制中,最高位作为符号位:0表示正数,1表示负数。

2.1 原码

原码是最直观的表示法:最高位为符号位,其余位为数值的绝对值。

+5 的原码: 0 0000101
-5 的原码: 1 0000101
+0 的原码: 0 0000000
-0 的原码: 1 0000000  ← 存在+0和-0两种表示

8位原码范围:-127 ~ +127(共255个数,因为0有两个表示)

2.2 反码

  • 正数的反码与原码相同
  • 负数的反码:符号位不变,其余位按位取反
+5 的反码: 0 0000101  (与原码相同)
-5 的反码: 1 1111010  (原码数值位取反)
+0 的反码: 0 0000000
-0 的反码: 1 1111111  ← 仍然有两个0

8位反码范围:-127 ~ +127

2.3 补码

  • 正数的补码与原码相同
  • 负数的补码 = 反码 + 1
+5 的补码: 0 0000101  (与原码相同)
-5 的补码: 1 1111011  (反码 11111010 + 1)
+0 的补码: 0 0000000
-0 的补码: 1 00000000 → 溢出后为 00000000 ← 只有一个0!

8位补码范围:-128 ~ +127(共256个数)

2.4 为什么用补码?

补码解决了三个关键问题:

① 统一了0的表示

原码: +0 = 00000000, -0 = 10000000  → 两个0
补码: +0 = 00000000, -0 = 00000000  → 只有一个0

② 加减法统一

补码让减法可以转化为加法运算,CPU不需要单独的减法器:

5 - 3 = 5 + (-3)

  00000101   (+5的补码)
+ 11111101   (-3的补码)
-----------
  00000010   (+2的补码)  ← 结果正确!
  (最高位进位自然丢弃)

③ 扩大了表示范围

8位补码可以表示 -128,这是原码和反码做不到的:

-128 的补码: 10000000
  这个编码在原码和反码中没有对应值
  在补码中直接定义为 -128

2.5 补码的快速求法

负数补码的两种求法:

方法1: 原码 → 反码 → 反码+1 → 补码
  -6 原码: 10000110
  -6 反码: 11111001
  -6 补码: 11111010

方法2: 从右往左找到第一个1,该1及其右边的0保持不变,左边各位取反
  -6 原码: 1 0000110
              ↑ 第一个1
  保持:          10
  取反:  1 1111010
  结果:  11111010  ← 与方法1一致

2.6 补码运算与溢出

#include <stdio.h>
#include <limits.h>

int main() {
    // 补码溢出示例
    signed char a = 127;   // 最大正值
    signed char b = 1;
    signed char c = a + b; // 溢出!

    printf("127 + 1 = %d\n", c);  // 输出: -128(发生了溢出)

    // 判断溢出:两个正数相加得到负数,或两个负数相加得到正数
    signed char x = -128;
    signed char y = -1;
    signed char z = x + y;  // 溢出!

    printf("-128 + (-1) = %d\n", z);  // 输出: 127(发生了溢出)

    return 0;
}

2.7 8位各表示法范围汇总

表示法范围0的个数特点
原码-127 ~ +1272个直观,但运算复杂
反码-127 ~ +1272个原码到补码的过渡
补码-128 ~ +1271个现代计算机标准表示

3. 浮点数表示(IEEE 754)

计算机用浮点数表示小数,遵循 IEEE 754 标准。

3.1 科学记数法回顾

十进制: 123.456 = 1.23456 × 10²
二进制: 101.101 = 1.01101 × 2²

3.2 IEEE 754 单精度(32位)

| 1位符号 | 8位指数 | 23位尾数 |
|   S     |   E     |    M     |

值 = (-1)^S × 1.M × 2^(E-127)
部分位数说明
符号位 S10=正数,1=负数
指数 E8偏移量127,实际指数 = E - 127
尾数 M23隐含前导1,实际尾数 = 1.M

3.3 IEEE 754 双精度(64位)

| 1位符号 | 11位指数 | 52位尾数 |
|   S     |    E     |    M     |

值 = (-1)^S × 1.M × 2^(E-1023)

3.4 浮点数转换示例

将 -6.5 转换为 IEEE 754 单精度浮点数:

第1步: 确定符号位
  -6.5 为负数 → S = 1

第2步: 转换为二进制
  6 = 110
  0.5 = 0.1
  6.5 = 110.1

第3步: 规格化
  110.1 = 1.101 × 2²

第4步: 提取各字段
  S = 1
  M = 101 (后面补0至23位: 10100000000000000000000)
  E = 2 + 127 = 129 = 10000001

第5步: 组合
  1 10000001 10100000000000000000000
  = 0xC0D00000

3.5 浮点数的特殊值

指数 E尾数 M含义说明
全0全0±0正零或负零
全0非全0非正规数非常接近0的数
全1全0±∞正无穷或负无穷
全1非全0NaN不是一个数(0/0等)

3.6 浮点数精度问题

#include <stdio.h>

int main() {
    float a = 0.1f;
    float b = 0.2f;
    float c = a + b;

    // 0.1 + 0.2 不等于 0.3!
    printf("0.1 + 0.2 = %.20f\n", c);      // 0.30000001192092895508
    printf("0.3     = %.20f\n", 0.3f);      // 0.30000001192092895508

    // 正确的比较方式:使用误差范围
    float epsilon = 1e-6f;
    if (c - 0.3f < epsilon && 0.3f - c < epsilon) {
        printf("近似相等\n");
    }

    // 绝对不要用 == 比较浮点数
    if (c == 0.3f) {
        printf("相等\n");    // 可能不会执行
    }

    return 0;
}

3.7 精度与范围对比

类型位数有效数字范围
float32~7位±1.18×10⁻³⁸ ~ ±3.4×10³⁸
double64~15位±2.23×10⁻³⁰⁸ ~ ±1.80×10³⁰⁸

实际开发中,优先使用 double 以获得更高精度。仅在内存受限(如嵌入式、GPU计算)时使用 float

4. ASCII 码

ASCII(American Standard Code for Information Interchange)是最基础的字符编码标准,用7位二进制表示128个字符。

4.1 ASCII 码表(0~127)

控制字符(0~31):

范围含义常见字符
0NUL(空字符)字符串结束标志 ‘\0’
7BEL(响铃)
8BS(退格)‘\b’
9HT(水平制表)‘\t’
10LF(换行)‘\n’
13CR(回车)‘\r’
27ESC(转义)
32SP(空格)’ ’

可打印字符(32~127):

字符类别范围关键记忆点
空格32第一个可打印字符
数字 0~948~57’0’ = 48
大写 A~Z65~90’A’ = 65
小写 a~z97~122’a’ = 97
DEL127最后一个ASCII字符

4.2 必须记住的 ASCII 值

'0' = 48    'A' = 65    'a' = 97
'1' = 49    'B' = 66    'b' = 98
 ...          ...          ...
'9' = 57    'Z' = 90    'z' = 122

关键规律:

  • 数字、大写字母、小写字母的编码各自连续
  • 大写字母 + 32 = 对应小写字母('A' + 32 = 'a'
  • 数字字符 - ‘0’ = 数值('7' - '0' = 7
#include <stdio.h>

int main() {
    // 大小写转换
    char upper = 'G';
    char lower = upper + 32;   // 或 upper + ('a' - 'A')
    printf("%c -> %c\n", upper, lower);  // G -> g

    // 字符转数字
    char digit = '7';
    int value = digit - '0';
    printf("'%c' = %d\n", digit, value);  // '7' = 7

    // 判断字符类型
    char ch = '5';
    if (ch >= '0' && ch <= '9')
        printf("数字\n");
    else if (ch >= 'A' && ch <= 'Z')
        printf("大写字母\n");
    else if (ch >= 'a' && ch <= 'z')
        printf("小写字母\n");

    return 0;
}

4.3 扩展 ASCII(128~255)

标准ASCII只有128个字符,无法表示中文等非英文字符。扩展ASCII(128~255)在不同编码页中有不同含义,这导致了乱码问题,也是Unicode诞生的背景。

5. Unicode 与 UTF-8

5.1 Unicode 概述

Unicode 为世界上几乎所有文字系统中的每个字符分配了唯一的码点(Code Point)

码点范围: U+0000 ~ U+10FFFF(共1,114,112个码点)
表示方式: U+ 后跟4~6位十六进制数

示例:
  'A'   → U+0041
  '中'  → U+4E2D
  ''  → U+1F600

5.2 Unicode 平面

Unicode 码点空间被划分为17个平面,每个平面包含 65,536 个码点:

平面范围内容
第0平面(BMP)U+0000 ~ U+FFFF基本多文种平面,最常用
第1平面U+10000 ~ U+1FFFF补充多文种平面
第2平面U+20000 ~ U+2FFFFCJK统一表意文字扩展
第3~13平面保留或分配
第14平面U+E0000 ~ U+EFFFF补充特殊用途平面
第15~16平面U+F0000 ~ U+10FFFF私用区

日常使用的汉字大多在 BMP(U+4E00~U+9FFF)和第2平面中。

5.3 UTF-8 编码规则

UTF-8 是 Unicode 的一种变长编码实现,用1~4个字节表示一个码点:

码点范围字节数编码格式
U+0000 ~ U+007F10xxxxxxx
U+0080 ~ U+07FF2110xxxxx 10xxxxxx
U+0800 ~ U+FFFF31110xxxx 10xxxxxx 10xxxxxx
U+10000 ~ U+10FFFF411110xxx 10xxxxxx 10xxxxxx 10xxxxxx

5.4 码点 → UTF-8 转换示例

例1:‘A’ = U+0041 → 1字节

U+0041 = 1000001
范围 U+0000~U+007F → 1字节格式: 0xxxxxxx
填入: 01000001
结果: 0x41(与ASCII完全兼容!)

例2:‘中’ = U+4E2D → 3字节

U+4E2D = 0100 1110 0010 1101
范围 U+0800~U+FFFF → 3字节格式: 1110xxxx 10xxxxxx 10xxxxxx

将 0100111000101101 填入 x 的位置:
  1110 0100   10 111000   10 101101
  = E4        = B8        = AD

结果: E4 B8 AD(3个字节)

例3:” = U+1F600 → 4字节

U+1F600 = 0001 1111 0110 0000 0000
范围 U+10000~U+10FFFF → 4字节格式: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

将 000011111011000000000 填入 x 的位置(共21位):
  11110 000   10 011111   10 011000   10 000000
  = F0        = 9F        = 98        = 80

结果: F0 9F 98 80(4个字节)

5.5 UTF-8 的设计优势

优势说明
ASCII 兼容U+0000~U+007F 的 UTF-8 编码与 ASCII 完全相同
自同步从任意字节可以判断是首字节还是后续字节
无字节序问题不需要 BOM(字节序标记)
容错性好损坏一个字节影响一个字符,不影响后续
空间英文1字,中文3字,比UTF-32节省空间

5.6 BOM(字节序标记)

UTF-16 和 UTF-32 存在问题,用 BOM 标识

FE FF → 大端序(Big Endian,高位在前)
FF FE → 小端序(Little Endian,低位在前)

UTF-8 的 BOM: EF BB BF(可选,不推荐添加)
#include <stdio.h>

// 检测文件BOM
void check_bom(const unsigned char* data, int len) {
    if (len >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF)
        printf("UTF-8 BOM\n");
    else if (len >= 2 && data[0] == 0xFE && data[1] == 0xFF)
        printf("UTF-16 Big Endian BOM\n");
    else if (len >= 2 && data[0] == 0xFF && data[1] == 0xFE)
        printf("UTF-16 Little Endian BOM\n");
    else
        printf("无BOM\n");
}

5.7 编码方案对比

编码字节适用场景
UTF-81~4兼容ASCII,无字节序中文占3字Web、文件存储
UTF-162或4中文2字字节序,英文2字Windows内部、Java
UTF-32固定4定长,随机访问空间浪费大内存中处理

6. 小结

主题心要
进制转换按权展开求和(→十进制),除基取余(十进制→)
原码反码补码补码统一0的表示统一加减法、扩大表示范围
IEEE 754符号位+指数+尾数,注意问题和特殊
ASCII’0’=48, ‘A’=65, ‘a’=97,大小写差32
Unicode/UTF-8唯一标识字符,UTF-8变长编码兼容ASCII

理解数的表示编码是理解程序行为的基础——为什么 0.1 + 0.2 != 0.3、为什么中文占3个字节、为什么整数溢出会变成负数,这些问题的答案都在本章中。

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式