正数和负数的原码反码补码(负数的补码是该数的原码加一)

3. 二进制可以表示的内容

内容导视:

  • 进制转换
  • 原码、补码、反码
  • 浮点数表示法
  • 字符编码

抱歉,最近没空写文章了…内容很杂,有内容导视,可以 Ctrl + F 搜索一下关键字,有谬误的地方,尽管批评。

6.29 去武汉面试,软件测试实习生,之前从未了解过,说是可以培训两个月,于是答应了,也不知道 hr 与技术人员沟通好了没,还只是为了完成业绩,面试官究竟要问什么,有什么要求呢,祝我好运。

往返一次 100 就没了,有些心疼。

还行吧,还以为要进厂或工地…

3.1 进制转换

内容导视:

  • 二进制与十进制互转
  • 二进制与八进制互转

常用进制如下:

  • 二进制:0、1,满 2 进 1
  • 十进制:0 ~ 9,满 10 进 1
  • 八进制:0 ~ 7,满 8 进 1
  • 十六进制:0 ~ F,满 16 进 1

十六进制中,超过 9 的部分与十进制的对应关系,A:10,B:11,C:12,D:13,E:14,F:15。

码 3.1-1 加前缀表明进制?

 89:十进制
 0b100:二进制的前面加上 0b
 015 :八进制的前面加上 0
 0xFF:十六进制的前面加上 0x

3.1.1 二进制与十进制互转

二进制转十进制

将每位上的数乘以 2 的(所处位数 – 1)次方,求和。

例 1:0b100 转为十进制

 0b100 = 
     1 * 2 ^ 2
 +   0 * 2 ^ 1
 +   0 * 2 ^ 0
 =   4
 所以 0b100 对应的十进制为 4

例 2:0b10101010 转为十进制

由于 0 乘以什么都是 0,故而省去。

 0b10101010 = 
     1 * 2 ^ 7
 +   1 * 2 ^ 5
 +   1 * 2 ^ 3
 +   1 * 2 ^ 1
 = 128 + 32 + 8 + 2
 = 170
 所以 0b10101010 对应的十进制为 170

十进制转二进制

十进制数除以 2 得到商,再将商除以 2,如此反复,直到商为 0,然后将每步得到的余数倒过来,就是对应的二进制。

例 1:5 转为二进制

 5/2,商 2,余 1
 2/2,商 1,余 0
 1/2,商 0,余 1
 所以 5 对应的二进制为 0b101

例 2:147 转为二进制

 147/2,商 73,余 1
 73/2,商 36,余 1
 36/2,商 18,余 0
 18/2,商 9,余 0
 9/2,商 4,余 1
 4/2,商 2,余 0
 2/2,商 1,余 0
 1/2,商 0,余 1
 所以 147 对应的二进制为 0b10010011

十进制与八进制互转、十进制与十六进制互转,类似于十进制与二进制互转,只不过把 2 换成 8、16 而已。

例 3:0124 转十进制

 0124 = 
     1 * 8 ^ 2
 +   2 * 8 ^ 1
 +   4 * 8 ^ 0
 = 64 + 16 + 4
 = 84
 所以 0124 对应的十进制为 84

例 4:241 转十六进制

 241/16,商 15,余 1
 15/16,商 0,余 F
 所以 241 对应的十六进制为 0xF1

3.1.2 二进制与八进制互转

二进制转八进制

0b000 ~ 0b111 对应 0 ~ 7,每三位二进制数可以表示 0 ~ 7 之间的数,即一个八进制数

从右边开始,二进制数每三位一组,转成对应的八进制数,不够三位补 0,拼起来即可:

 0b000       0
 0b001       1
 0b010       2
 0b011       3
 0b100       4
 0b101       5
 0b110       6
 0b111       7
 0b001000    10
 0b001001    11
 ...
 0b111111    77

例 1:0b11101 转为八进制

 101:5
 011:3
 所以 0b11101 对应的八进制数为 035

例 2:0b101111011 转为八进制

 011:3
 111:7
 101:5
 所以 0b101111011 对应的八进制数为 0573

八进制转二进制

上述的逆过程,将八进制的每位,转成对应的一个 3 位的二进制数,拼起来即可。

例 1:0241 转为二进制数

 0241
 2:010
 4:100
 1:001
 所以 0241 对应的二进制是 0b10100001

例 2:02371 转为二进制数

 02371
 2:010
 3:011
 7:111
 1:001
 所以 02371 对应的二进制数为 0b10011111001

同理二进制与十六进制转换,由于 0b0000 ~ 0b1111 对应 0 ~ 15,每 4 位二进制数可以表示 1 个十六进制数。

0b0000        0x0
0b0001        0x1
0b0010        0x2
0b0011        0x3
0b0100        0x4
0b0101        0x5
0b0110        0x6
0b0111        0x7
0b1000        0x8
0b1001        0x9
0b1010        0xA
0b1011        0xB
0b1100        0xC
0b1101        0xD
0b1110        0xE
0b1111        0xF
0b00010000    0x10
...
0b11111111    0xFF

例 3:0b111101010 转为十六进制

1010:A
1110:E
0001:1
0b111101010 对应的十六进制为 0x1EA

例 4:0xFAD8 转为二进制

 F:1111
 A:1010
 D:1101
 8:1000
 所以 0xFAD8 对应的二进制为 0b1111101011011000

3.2 原码、补码、反码

内容导视:

  • 使用二进制表示正负数
  • 1 个字节表示 -128 ~ 127
  • 猜测为什么计算机要以补码形式存储

3.2.1 使用二进制表示正负数

采用最高位是符号位的方法来区分正负数,正数的符号位为 0、负数的符号位为 1。

原码是带符号位的二进制码。用 8 位(1 个字节)表示一个整数,3 的原码为 00000011,-3 的原码为 10000011

其实计算机存储数据时,一律储存二进制的补码形式。

对于正整数来说,原码、反码、补码相同。

对于负整数来说,反码的符号位不变,其它位取反(1、0 互换);补码是反码 + 1。

例 1:-1 的补码

 原码:10000001
 反码:符号位不变,其它位取反,11111110
 补码:反码 + 1,11111111
 所以 -1 的补码是 11111111

例 2:补码 10000010 对应的十进制整数

通过符号位可以看出是负数的补码,因为正数三码相同,不可能符号位为 1
补码:10000010
反码:补码 - 1,10000001
原码:11111110

原码 1111110 对应的十进制为 254
再加上符号位 1,原码 11111110 对应 -254
所以补码 10000010 对应的十进制整数为 -254

3.2.2 1 个字节表示 -128 ~ 127

要表示的十进制数

原码

反码

补码

+0

00000000

00000000

00000000

1

00000001

00000001

00000001

2

00000010

00000010

00000010

3

00000011

00000011

00000011

4

00000100

00000100

00000100

126

01111110

01111110

01111110

127

01111111

01111111

01111111

到 128 时,原码:10000000,错误,因为最高位是符号位,这会被误以为是负数,所以 128 超出了 1 个字节能够表示的范围。

要表示的十进制数

原码

反码

补码

-0

10000000

11111111

00000000

-1

10000001

11111110

11111111

-2

10000010

11111101

11111110

-3

10000011

11111100

11111101

-4

10000100

11111011

11111100

-126

01111110

10000001

10000010

-127

11111111

10000000

10000001

若超出 8 位,就截去前面的多余位数。如 100000000 是 9 位,截去前面一位为 00000000。

1 个字节 8 位,最多表示 28 = 256 个数,以原码的形式存储数据时,由于 0 的表示方法有两种:00000000、10000000,浪费了一种表示方法,实际表示的数的范围:[-127,127] 一共 255 个。

如果以补码的形式存储数据,0 的表示方法统一了,都是 00000000,还有补码 10000000 未被使用,用于表示 -128,实际表示的数的范围:[-128,127]。

3.2.3 猜测为什么计算机要以补码形式存储

除了以补码形式存储可以多存一个数的原因,方便减法运算转为加法运算也是一个关键的因素吧。

二进制加法:0 + 0 = 0,0 + 1 = 1,1 + 1 = 0 进 1 位。

例 1:1 – 1 = ?

1 - 1 = 1 + (-1)
1 的补码为:00000001,-1 的补码为 11111111,0 的补码 00000000
以补码形式做加法,得到的补码,再转为整数
  00000001
+ 11111111
= 00000000
00000000 是 0 的补码,所以运算结果为 0

例 2:24 – 4 = ?

 24 - 4 = 24 + (-4)
 24 的补码为:00011000,-4 的补码为 11111100
   00011000
 + 11111100
 = 00010100
 00010100 是正数的补码、反码、原码,转为十进制为 20

使用补码,运算变得更方便。

3.3 浮点数表示法

内容导视:

  • 小数对应的二进制
  • 科学计数法
  • 浮点数表示法
  • 最大值、最小值
  • 特殊值

3.3.1 小数对应的二进制

之前漏掉了小数对应的二进制,现补上。

二进制转十进制

从个位数开始向左计算,个位数乘以 2 的 0 次方,十位数乘以 2 的 1 次方,百位数乘以 2 的 2 次方…

从十分位开始向右计算,十分位乘以 2 的 -1 次方,百分位乘以 2 的 -2 次方…

然后将每个式子的结果相加。

例 1:0b101.11 转为十进制

 最高位是百位,从 1 * 2 ^ 2 开始
 最低位是百分位,到 1 * 2 ^ -2 结束
 0b101.11 = 
     1 * 2 ^ 2
 +   0 * 2 ^ 1   
 +   1 * 2 ^ 0
 +   1 * 2 ^ -1
 +   1 * 2 ^ -2
 = 4 + 1 + 0.5 + 0.25 = 5.75

例 2:0b111.01 转为十进制

 0b111.01 = 
     1 * 2 ^ 2
 +   1 * 2 ^ 1
 +   1 * 2 ^ 0
 +   0 * 2 ^ -1
 +   1 * 2 ^ -2
 = 4 + 2 + 1 + 0 + 0.25 = 7.25

可以看到小数部分都是由 0.5、0.25、0.125、0.0625… 等数组合表示,前面的系数 1 或 0,0.625 = 1 * 0.5 + 0 * 0.25 + 1 * 0.125,所以 0.625 对应的二进制为 0.101。

例 3:0100.4 转为十进制,八进制转为十进制,同上过程,将 2 换成 8。

0100.4 = 
	1 * 8 ^ 2
+ 	4 * 8 ^ -1
= 64 + 0.5 = 64.5

100 = 0000100,1.1 = 1.10000,补 0 是为了方便转换为其它进制,参考 3.1.2 二进制与八进制互转。

4 = 04 = 0b100 = 0b0100 = 0x4,1 * 80 + 4 * 8-1 = 1.5 = 01.4 = 0b1.100 = 0b1.1000 = 0x1.8

十进制转二进制

整数部分除以 2 得到商,再将商除以 2,如此反复,直到商为 0,然后将每步得到的余数倒过来,就是对应的二进制。

小数部分乘以 2 得到积,取整数部分,余下的部分再次乘以 2,重复步骤,直到余数为 0,取出的整数合并再添上前缀 0. 就是小数对应的二进制。

再将整数和小数部分对应的二进制合并。

使用二进制表示小数可能会有精度损失,有些小数穷尽二进制也无法表示。

例 1:0.75 转为二进制

0.75 * 2 = 1.5,取 1,余 0.5
0.5 * 2 = 1,取 1,余 0
所以 0.75 对应的二进制为 0b0.11

例 2:3.4 转为二进制

整数部分 3 转为二进制为 0b11
小数部分 0.4
0.4 * 2 = 0.8,取 0,余 0.8
0.8 * 2 = 1.6,取 1,余 0.6
0.6 * 2 = 1.2,取 1,余 0.2
0.2 * 2 = 0.4,取 0,余 0.4
0.4 * 2 = 0.8,取 0,余 0.8
...
所以小数部分 0.4 对应的二进制为 0b0.0110 0110 0110... 的循环
3.4 对应的二进制为 0b11.0110 0110 0110...

例 3:123.123 转为二进制

123/2,商 61,余 1
61/2,商 30,余 1
30/2,商 15,余 0
15/2,商 7,余 1
7/2,商 3,余 1
3/2,商 1,余 1
1/2,商 0,余 1
所以 123 对应的二进制为 0b1111011
0.123 * 2 = 0.246,取 0,余 0.246
0.246 * 2 = 0.492,取 0,余 0.492
0.492 * 2 = 0.984,取 0,余 0.984
0.984 * 2 = 1.968,取 1,余 0.968
0.968 * 2 = 1.936,取 1,余 0.936
0.936 * 2 = 1.872,取 1,余 0.872
0.872 * 2 = 1.744,取 1,余 0.744
0.744 * 2 = 1.488,取 1,余 0.488
0.488 * 2 = 0.976,取 0,余 0.976
...
0.123 对应的二进制为 0b0.000111110...

所以 123.123 对应的二进制为 0b1111011.000111110…

3.3.2 科学计数法

十进制表示法(E)

1.4E-45 = 1.4 * 10 ^ -45,1.4 称为尾数(Mantissa),10 为基数、底数(Base),-45 为指数(Exponent),都是十进制表示。

尾数在 [1.0,10) 之间的表示方法称为 modified normalized form,正是 Java 所使用的。

尾数在 [0.0,1.0) 之间的表示方法称为 true normalized form。

例:

  • 3.4028235E38 = 3.4028235 * 10 ^ 38
  • 999999 = 9.99999 * 10 ^ 5。
  • 0b10010101 = 0b1.0010101 * 2 ^ 7(对于二进制而言,每乘以 2 就相当于小数点往右移动一位)

Java 中,当小数超出 [-9999999,9999999] 范围时,会使用科学计数法表示

 System.out.println(-10000000.0);// -1.0E7

思考控制台输出什么结果?

 System.out.println(500e-2);

答:500e-2 = 500 * 10 ^ -2 = 500 / 10 ^ 2 = 5.0

科学计数法默认被当作 double 类型来处理,所以小数不能掉。

 System.out.println(-10000000);
 System.out.println(500E-7);

第 1 个不是小数,原样输出。第 2 个超过上述所说的范围,那使用科学计数法表示,尾数应在 [1.0,10) 之间,输出 5.0E-5

十六进制表示(P)

在十六进制表示中,2 为底数。

例 1:0xa.0p-1 转为十进制

 0xa.0 转为十进制为 10
 0xa.0p-1 = 10 * 2 ^ -1 = 10 / 2 = 5.0

例 2:0x19.0p-2 转为十进制

 0x19.0 转十进制
 0x19.0 = 
     1 * 16 ^ 1
 +   9 * 16 ^ 0
 = 16 + 9 = 25
 
 0x19.0p-2 = 25 * 2 ^ -2 = 25 / 4 = 6.25

例 3:0x0.12p2 转为十进制

 0x0.12 转十进制
 0x0.12 = 
     1 * 16 ^ -1
 +   2 * 16 ^ -2
 = 1/16 + 2/256 = 0.0703125
 
 0x0.12p2 = 0.0703125 * 2 ^ 2 = 0.28125

例 4:0b1.0010101 * 2 ^ 7 转为十进制

有两种方案,一是先求出 0b1.0010101 对应的小数,然后再乘以 2 的 7 次方。

二是将小数点向右移动 7 位,得到 0b10010101,然后在求 0b10010101 对应的整数。

 0b1.0010101 * 2 ^ 7 = 0b10010101 = 1 + 4 + 16 + 128 = 149
 0b1.0010101 = 0b1 + 0b0.0010101 = 1 + 0.125 + 0.03125 + 0.0078125 = 1.1640625
 1.1640625 * 2 ^ 7 = 1.1640625 * 128 = 149
 0b1.00101010 = 0x1.2a = 1 + 2/16 + 10/256 = 1 + 0.125 + 0.0390625 = 1.1640625
 1.1640625 * 2 ^ 7 = 149

3.3.3 浮点数表示法

并不是直接将小数对应的二进制直接存储,这样表示的数的范围太小,也无法保证精度。

如 123456789.123456 的二进制为 111010110111100110100010101.00011111100110101101,

使用 4 个字节保存此二进制,只能保存前 32 位:0b111010110111100110100010101.0 = 123456789.0,小数全部丢失。

浮点数如何表示小数?

分为四部分表示:符号、尾数、基数和指数。

例:0b1010.11101 * 2 ^ 10

符号:+

尾数:1010.11101

基数:2

指数:10。

浮点数有两种类型:单精度浮点数(float:32bit)和双精度浮点数(double:64bit)。

?3.3.3-1 浮点数存储格式?

类型

总长度

符号

阶码

尾数

float

32 bit

1

8

23

double

64 bit

1

11

52

符号

1 表示负,0 表示正。

阶码

也称指数位,因为指数有正、负,为了避免使用符号位,方便比较、排序,采用了 The Biased exponent(有偏指数)。[1]

IEEE754 规定,2 ^(e-1) – 1 的值是 0(e 是阶码部分的位数),小于这个值表示负数,大于这个值表示正数。因此,对于单精度浮点数而言,127 是 0;双精度浮点数中 1023 是 0。

假设指数为 -5,使用单精度浮点数表示时,应存储 -5 + 127 = 122 的二进制 0111 1010。

指数为 1023,使用双精度浮点数表示时,应存储 1023 + 1023 = 2046 的二进制 11111111110。

8 位可以表示的值:00000000 ~ 11111111 即 0 ~ 255,其中 00000000 和 11111111 被用作特殊情况,所以可用范围只有 1 ~ 254,用来表示负数,如果取 128 为 0,可以表示 -127 ~ 126,取 127 为 0,可以表示 -126 ~ 127,表示的数更大。

11 位可用范围:00000000001 ~ 11111111110 即 1 ~ 2046,取 1023 为 0,可以表示 -1022 ~ 1023。

尾数

一个小数既可以使用 0b1010.11101 * 2 ^ 10 表示,也可以使用 0b1.01011101 * 2 ^ 13 表示。

为了得到统一的编码,通过移位,将小数点前面的值固定为 1。IEEE754 称这种形式的浮点数为规范化浮点数(normal number)。

如 0b0.00101 = 0b1.01 * 2 ^ -3。

因为规定第 1 位永远为 1,因此可以省略不存,这样尾数部分多了 1 位,只需存 01。

因此对于规范化浮点数,尾数其实比实际的多 1 位,也就是说单精度的是 24 位,双精度是 53 位。为了作区分,IEEE754 称这种尾数为 significand。

基数

默认为 2,不参与存储。


[1] 为什么使用移码表示阶码:https://www.zhihu.com/question/24115452/answer/363412858

工具网址

  • 进制转换:https://tool.oschina.net/hexconvert

转载文章

  • https://baijiahao.baidu.com/s?id=1679268252794098534

3.3.4 一些例子

加空格只是为了更好的观察。

例 1:用 float 表示 329.301

329.301 的二进制为 0b101001001.01001101000011100101011000000100000110001001001101111… = 0b1.0100100101001101000011100101011000000100000110001001001101111… * 2 ^ 8

符号位:0

阶码:8 + 127 = 135,对应的二进制为:10000111

尾数取出 23 位:01001001 01001101 0000111(规格化表示,1 省略不存)

所以单精度浮点数 0.15625 对应的二进制内存表示是:0 10000111 01001001010011010000111

?3.3.4-1 float 内存表示对应的整数?

 // 329.301 的二进制内存表示对应的整数
 int i = Float.floatToIntBits(329.301f);
 // 329.301 的二进制内存表示
 Object s = Integer.toBinaryString(i);
 System.out.println(i);// 1134864007
 System.out.println(s);// 1000011 10100100 10100110 10000111
 
 // -1 的补码为 11111111 11111111 11111111 11111111
 System.out.println(Integer.toBinaryString(-1));
 // -1 的补码为 ffffffff
 System.out.println(Integer.toHexString(-1));
 // 加下划线不影响实际数值
 System.out.println(0b11111111_11111111_11111111_11111111);
 System.out.println(0xffffffff);

例 2:用 double 表示 -666.875

666.875 实际对应的二进制为 0b1010011010.111 = 0b1.010011010111 * 2 ^ 9

符号位:1

阶码:9 + 1023 = 1032,对应的二进制为:10000001000

尾数位:010011010111,不够 52 位就补 0。

所以双精度浮点数 -666.875 对应的二进制内存表示是:1 10000001000 0100110101110000000000000000000000000000000000000000

?3.3.4-2 double 内存表示对应的整数?

 long l = Double.doubleToLongBits(-666.875);
 Object s = Long.toBinaryString(l);
 // -4574294926501609472
 System.out.println(l);
 // 1100000010000100110101110000000000000000000000000000000000000000
 System.out.println(s);

例 3:单精度浮点数内存表示:00111101110011001100110011001101,求它对应的小数

符号位:0,正数

阶码取 8 位:01111011,转为十进制 123,123 – 127 = -4,指数为 -4

尾数取 23 位:10011001100110011001101,尾数为 1.10011001100110011001101

小数的二进制表示为 0b1.10011001100110011001101 * 2 ^ -4 = 0b0.000110011001100110011001101

0b0.000110011001100110011001101 对应的小数:0.10000000149011612…,由于 float 精度最多只能表示 8 位,所以对应的小数是 0.1。

float v = Float.intBitsToFloat(0b00111101110011001100110011001101);
System.out.println(v);// 0.1

3.3.5 浮点数精度

浮点数的精度是指浮点数的有效数字的最大位数,从左边第一个不为 0 的数字开始的个数开始算起。

System.out.println(0.1111111111111f);//0.11111111

可以发现超过了 8 位后的数字都被舍弃,float 最多只能表示 8 位有效数字。

例:0b0.01010010 11101001 01010100 10101010 = 0b1.010010 11101001 01010100 10101010 * 2 ^ -2

使用单精度浮点数表示,只能存储 23 位尾数,也就是 010010 11101001 01010100 1,丢掉了一部分,导致精度损失。

大部分文章的解释

单精度浮点数尾数有 23 位,加上默认的 1,2 ^ (23+1)= 16777216。因为 10^7 < 16777216 < 10^8,所以说单精度浮点数的有效位数是 7 位,部分可以达到 8 位。

双精度浮点数尾数有 52 位,2 ^ (52+1)= 9007199254740992,10^15 < 9007199254740992 < 10^16,所以双精度的有效位数是 15 位,部分可以达到 16 位。

说实话,这个解释不大满意,之前的二、十进制转换,求余得到二进制,勉强可以不探究原理;这里就不太好理解,难道是指 24 位全取 1,是最大值,再大就表示不了的意思?只能被迫舍去一些位?

没办法只能自己做实验了,以单精度浮点数为例。

整数部分

23 = 0b10111 = 0b1.0111 * 2 ^ 4

如果尾数部分只能存 1 位,即 0,后面的 111 被舍弃,实际存储的值为 0b1.0 * 2 ^ 4 = 0b10000 = 16,那么连一位有效数字都保证不了。

9 = 0b1001 = 0b1.001 * 2 ^ 3,如果尾数只能存 1 位,即 0,后面的 01 被舍弃,实际存储的值为 0b1000 = 8;如果尾数有 3 位,精度才不会损失。

15 = 0b1111 = 0b1.111 * 2 ^ 3,要想精确表示,应存储 111,尾数至少 3 位。

要想表示 1 ~ 9 范围内的所有整数:0001 ~ 1001,需要 4 位二进制,尾数至少需要 3 位。

表示 10 ~ 99:1010 ~ 1100011,至少需要 6 位尾数,如 1100011 = 1.100011 * 2 ^ 6,存储 100011 可以保证精度不损失。

表示 106 ~ 107:11110100001001000000 ~ 100110001001011010000000,至少需要 23 位尾数。

24 位二进制能表示的最大值:16777215,超过了此数,如 16777217,0b1000000000000000000000001 = 0b1.000000000000000000000001 * 2 ^ 24,存储 23 位 0,第 24 位的 1 被略去,精度损失。

小数部分可参考:https://blog.csdn.net/pkxpp/article/details/103059502

意思大概是小数由 0.5、0.25、0.125、0.0625、0.003125、0.015625、…等单位组合而成。

如 0.625 = 1 * 0.5 + 0 * 0.25 + 1 * 0.125,则 0.625 = 0b0.101

如果要表示的数比最小单位还小,如用 0.5、0.25、0.125、0.0675、0.03125、0.015625 组合表示不了 0.001,精度只能到十分位 0.1,有时可以到百分位。

例,六位二进制表示 0.175,最贴近的是 0b0.001011 = 0.171875。

要想保证精确到小数点后面 3 位,2 ^ x < 10-3,求得 x 的最大值为 -10,最小单位 0.0009765625,用十位二进制表示 0b0.0010110100 = 0.17578125。

23 位尾数,能够表示的最小单位 2 ^ -24 = 0.0000000590444775 < 10-7,可以保证精确到小数点后 7 位,部分小数能够精确到 8 位。

小数部分的解释,我不大满意,因为有负指数的存在。还另外也没能解释整数与小数共存时的情况,这已经超出了我的能力范围。

0.1 + 0.2 = ?

使用单精度浮点数表示;

0.1 = 0b0.0001 10011001 10011001 1001100 1100110011001100110011001101…

类似四舍五入,第 23 位是否进位,看后面的位数是否大于 10000000 00000000 00000000 0000010 00000000…剩下都是 0,设此二进制为 b。

这是小数部分,可以无限往后填充 0,按位比就行。

例,比较 11 与 b,第 1 位相等,都是 1;第 2 位的 1 大于 0,所以 11 更大。

如 0b1.11101010 10101010 1010111 1011,1011 > b,所以第 23 位进位 + 1,应存储 11101010 10101010 1011000,多余的 1011 被舍弃。

如 0b1.10100101 10101111 1111010 01,01 < b,应存储 10100101 10101111 1111010。

尾数存储 23 位,实际值为 0b0.0001 10011001 10011001 1001101 = 0.10000000149011612

0.2 = 0b0.001 10011001 10011001 1001100 1100110011001100110011001101…

实际值 0b0.001 10011001 10011001 1001101 = 0.20000000298023224

所以 0.1 + 0.2 = 0.10000000149011612 + 0.20000000298023224 = 0.30000000447034836

得到的结果大于 0.3,不过单精度浮点数由于精度不够,显示不出来。

System.out.println(0.1f + 0.2f);// 0.3
System.out.println(0.1 + 0.2);// 0.30000000000000004

3.3.6 最大值、最小值

单精度浮点数最大值

8 位可以表示的值:00000000 ~ 11111111 即 0 ~ 255,其中 00000000 和 11111111 被用作特殊情况,所以可用范围只有 1 ~ 254,用来表示负数,取 127 为 0,可以表示 -126 ~ 127。

单精度最大指数为 127,最小指数 -126。

双精度最大指数为 1023,最小指数 -1022。

由于阶码 0b11111111 被用作特殊情况,最大指数应为 0b11111110 – 127 = 254 – 127 = 127。

最大值在内存中的表示:0 11111110 11111111111111111111111

1/2 + 1/4 = 1 – 1/4

1/2 + 1/4 + 1/8 = 1 – 1/8

1/2 + 1/4 + … + 1/223 = 1 – 1/223

单精度浮点数最大值为 0b1.11111111 11111111 1111111 * 2 ^ 127 = 2 ^ 127 + 2 ^ 126 + … + 2 ^ 104 = 2 ^ 127 * [1 + 1/2 + 1/4 + … + 1/2^23] = 2^127 * [1 + 1 – 1/2^23] = 2^128 – 2^104 = 3.4028235E38

0b1.11111111 11111111 1111111 * 2 ^ 127 = 0x1.fffffe * 2 ^ 127。

单精度浮点数最小正值

使用规范化浮点数时,尾数默认省略了前导数 1,导致 0 无法表示。

即使尾数全部取 0,0b1.0000… * 2 ^ n = 1 * 2 ^ n,也无法精确表示 0。

所以规定了另一种浮点数:

当指数位全是 0 时,尾数部分的前导数为 0,同时指数部分的偏移值比规范形式的偏移值小 1。如单精度浮点数取 126 为 0。

最小指数 0b00000000 – 126 = -126。

最小正值在内存中的表示:0 00000000 00000000000000000000001

单精度浮点数最小正值为 0b0.0000 0000 0000 0000 0000 001 * 2 ^ -126 = 2 ^ -23 * 2 ^ -126 = 2 ^ -149 = 1.4e-45

0b0.0000 0000 0000 0000 0000 001 * 2 ^ -126 = 0x0.000002 * 2 ^ -126

双精度浮点数最大值

内存表示:0 11111111110 1111111111111111111111111111111111111111111111111111

指数的最大值为 0b11111111110 – 1023 = 2046 – 1023 = 1023

这里使用二进制表示太长,转为十六进制表示

最大值 0x1.fffffffffffff * 2 ^ 1023 = 2 ^ 1023 * [1 + 1 – 1/2^52] = 2 ^ 1024 – 2 ^ 971 = 1.7976931348623157e308

双精度浮点数最小正值

内存表示:0 00000000000 0000000000000000000000000000000000000000000000000001

指数的最小值为 0b00000000000 – 1022 = -1022

最小正值 0x0.0000000000001 * 2 ^ -1022 = 2 ^ -52 * 2 ^ -1022 = 2 ^ -1074 = 4.9e-324

3.3.7 特殊值

为了表示 -1.0 / 0.0、1.0 / 0.0、0.0 / 0.0 等特殊数值,定义了三种类型 -Infinity(负无穷大)、Infinity(正无穷大)、NaN(非数字)。

无穷大

当指数位全为 1,尾数位全为 0,表示为无穷大,当一个数超出了浮点数的表示范围,就可以使用 Infinity 表示。符号为 0,是正无穷大,符号为 1,负无穷大。

单精度浮点数 Infinity 内存表示 0 11111111 0000000000000

双精度浮点数 -Infinity 内存表示 1 11111111111 0000000000000000000000000000000000000000000000000000

0b1111 1111 1111 0000000000000000000000000000000000000000000000000000 = 0xfff00…

// 0111 1111 1111 0000000...,是正无穷大
double d1 = Double.longBitsToDouble(0x7FF0000000000000L);
// 1111 1111 1000 00000...,是负无穷大
float f1 = Float.intBitsToFloat(0xFF800000);

非数字

符号位任意,指数位全为 1,尾数位不全为 0(至少有 1 位是 1),表示不是数,比如用 NaN 表示 -1 的平方根。

特性:

  • 不等于自身,可以利用此特性判断一个数是否是 NaN
  • NaN 参与运算,最终结果还是 NaN

3.4 字符编码

内容导视:

  • 字符集与字符编码
  • ASCII 字符集
  • GB2312 字符集
  • Unicode 字符集

计算机是以二进制的形式来存储数据的,我们在屏幕上看到的文字,在存储之前都被转换成了二进制(编码),在显示时也要根据二进制找到对应的字符。(解码)

字符集定义了文字和二进制的对应关系,为字符分配了唯一的编码。

如 ASCII 字符集定义的 'a' 的编码是 01100001,如果把它当作整数的补码,则为 97。

3.4.1 字符集与字符编码

字符集规定了某个文字对应的二进制数字存放方式(编码)和某串二进制数值代表了哪个文字(解码)的转换关系。

字符集只是一个规则集合的名字,对应到真实生活中,字符集就是对某种语言的称呼。例如:英语,汉语。

对于一个字符集来说,要正确编码转码一个字符需要三个关键元素:字库表(character repertoire)、编码字符集(coded character set)、字符编码(character encoding form)。

  • 字库表中装着所有字符
  • 编码字符集:即用一个序号(code point)来表示一个字符在字库中的位置,不同进制都可以,如 'a' 在 ASCII 字库表中的位置为 0x61。
  • 字符编码:编码字符集和实际存储二进制之间的转换关系。一般来说直接将 code point 的值转为二进制直接存储。例如在 ASCII 字库中 'A' 的序号为 65,转为二进制 01000001,直接存储。

3.4.2 ASCII 字符集

American Standard Code for Information Interchange:美国信息交换标准代码

ASCII 字符集是由美国人发明的,没有考虑欧洲那些扩展的拉丁字母,也没有考虑韩语和日语、汉字。

ASCII 的标准版本于 1967 年第一次发布,最后一次更新则是在 1986 年,迄今为止共收录了 128 个字符,包含了基本的拉丁字母(英文字母)、阿拉伯数字(1234567890)、标点符号(,.! 等)、特殊符号(@#$%^& 等)以及一些具有控制功能的字符(往往不会显示出来)。1 个字节表示这些符号绰绰有余。

在 ASCII 编码中,大写字母、小写字母和阿拉伯数字都是连续分布的(见下表),这给程序设计带来了很大的方便。例如要判断一个字符是否是大写字母,就可以判断该字符的 ASCII 编码值是否在 65~90 的范围内。

?3.4.2-1 ASCII 字库表?

二进制

十进制

十六进制

字符/缩写

解释

00000000

0

00

NUL (NULL)

空字符

00000001

1

01

SOH (Start Of Headling)

标题开始

00000010

2

02

STX (Start Of Text)

正文开始

00000011

3

03

ETX (End Of Text)

正文结束

00000100

4

04

EOT (End Of Transmission)

传输结束

00000101

5

05

ENQ (Enquiry)

请求

00000110

6

06

ACK (Acknowledge)

回应/响应/收到通知

00000111

7

07

BEL (Bell)

响铃

00001000

8

08

BS (Backspace)

退格

00001001

9

09

HT (Horizontal Tab)

水平制表符

00001010

10

0A

LF/NL(Line Feed/New Line)

换行键

00001011

11

0B

VT (Vertical Tab)

垂直制表符

00001100

12

0C

FF/NP (Form Feed/New Page)

换页键

00001101

13

0D

CR (Carriage Return)

回车键

00001110

14

0E

SO (Shift Out)

不用切换

00001111

15

0F

SI (Shift In)

启用切换

00010000

16

10

DLE (Data Link Escape)

数据链路转义

00010001

17

11

DC1/XON (Device Control 1/Transmission On)

设备控制1/传输开始

00010010

18

12

DC2 (Device Control 2)

设备控制2

00010011

19

13

DC3/XOFF (Device Control 3/Transmission Off)

设备控制3/传输中断

00010100

20

14

DC4 (Device Control 4)

设备控制4

00010101

21

15

NAK (Negative Acknowledge)

无响应/非正常响应/拒绝接收

00010110

22

16

SYN (Synchronous Idle)

同步空闲

00010111

23

17

ETB (End of Transmission Block)

传输块结束/块传输终止

00011000

24

18

CAN (Cancel)

取消

00011001

25

19

EM (End of Medium)

已到介质末端/介质存储已满/介质中断

00011010

26

1A

SUB (Substitute)

替补/替换

00011011

27

1B

ESC (Escape)

逃离/取消

00011100

28

1C

FS (File Separator)

文件分割符

00011101

29

1D

GS (Group Separator)

组分隔符/分组符

00011110

30

1E

RS (Record Separator)

记录分离符

00011111

31

1F

US (Unit Separator)

单元分隔符

00100000

32

20

(Space)

空格

00100001

33

21

!

00100010

34

22

"

00100011

35

23

#

00100100

36

24

$

00100101

37

25

%

00100110

38

26

&

00100111

39

27

'

00101000

40

28

(

00101001

41

29

)

00101010

42

2A

*

00101011

43

2B

+

00101100

44

2C

,

00101101

45

2D

00101110

46

2E

.

00101111

47

2F

/

00110000

48

30

0

00110001

49

31

1

00110010

50

32

2

00110011

51

33

3

00110100

52

34

4

00110101

53

35

5

00110110

54

36

6

00110111

55

37

7

00111000

56

38

8

00111001

57

39

9

00111010

58

3A

:

00111011

59

3B

;

00111100

60

3C

<

00111101

61

3D

=

00111110

62

3E

>

00111111

63

3F

?

01000000

64

40

@

01000001

65

41

A

01000010

66

42

B

01000011

67

43

C

01000100

68

44

D

01000101

69

45

E

01000110

70

46

F

01000111

71

47

G

01001000

72

48

H

01001001

73

49

I

01001010

74

4A

J

01001011

75

4B

K

01001100

76

4C

L

01001101

77

4D

M

01001110

78

4E

N

01001111

79

4F

O

01010000

80

50

P

01010001

81

51

Q

01010010

82

52

R

01010011

83

53

S

01010100

84

54

T

01010101

85

55

U

01010110

86

56

V

01010111

87

57

W

01011000

88

58

X

01011001

89

59

Y

01011010

90

5A

Z

01011011

91

5B

[

01011100

92

5C

\

01011101

93

5D

]

01011110

94

5E

^

01011111

95

5F

_

01100000

96

60

`

01100001

97

61

a

01100010

98

62

b

01100011

99

63

c

01100100

100

64

d

01100101

101

65

e

01100110

102

66

f

01100111

103

67

g

01101000

104

68

h

01101001

105

69

i

01101010

106

6A

j

01101011

107

6B

k

01101100

108

6C

l

01101101

109

6D

m

01101110

110

6E

n

01101111

111

6F

o

01110000

112

70

p

01110001

113

71

q

01110010

114

72

r

01110011

115

73

s

01110100

116

74

t

01110101

117

75

u

01110110

118

76

v

01110111

119

77

w

01111000

120

78

x

01111001

121

79

y

01111010

122

7A

z

01111011

123

7B

{

01111100

124

7C

|

01111101

125

7D

}

01111110

126

7E

~

01111111

127

7F

DEL (Delete)

删除

如 'a' 存储时以 97 对应的二进制 01100001 存储。

后来欧洲人发明了 ISO8859-1。ISO8859-1 包含了 ASCII 中的所有字符(兼容),还加入了一些西欧字符。

兼容:新字符集包括原有字符集中的所有字符,且这些字符的编码在原有字符集中也是如此,如 ‘a’ 在 ASCII 的编码是 01100001,在 GBK 的编码也是 01100001,就称新字符集兼容原有字符集。

3.4.3 GB2312 字符集

ASCII 字符集中没有包含中文,国人规定了 GB2312 字符集,使用 2 个字节表示一个汉字(因为汉字太多,1 个字节最多表示 256 个字符),英文还是 1 个字节,兼容 ASCII 码。

那么存储时好存,找到字符对应的字符编码(序号转为二进制)存储即可,但对于非定长编码,读取时怎么知道是按 1 个字节读还是 2 个字节读。

101110011111101001100001,凡是汉字,对应字符编码都以 1 开头,所以读取 2 个字节,1011100111111010 对应汉字 '国',以 0 开头,读取 1 个字节,01100001 对应 'a'。

GB2312 编码范围:0xA1A1 ~ 0xFEFE,其中汉字编码范围:0xB0A1 ~ 0xF7FE。

分为 94 个区,每个区有 94 个数,GB2312 编码表:https://www.qqxiuzi.cn/zh/hanzi-gb2312-bianma.php

通过序号查字符

0xA1A2,前两位是区号,后两位是区中的第几个数。

A1A2 代表 01 区的第 2 个数。(可以看出规律:01 + A0 = A1,02 + A0 = A2)

?3.4.3-1 01 区中的字符?

正数和负数的原码反码补码(负数的补码是该数的原码加一)

通过字符查序号

以 '包' 为例,它在 16 区的第 92 个数,那么对应的十六进制的序号是多少?把它们转成 16 进制分别与 A0 相加就可以了。

 16 转成十六进制是 0x10
 92 转成十六进制是 0x5C
 
    10
 + A0 
 = B0
 
    5C
 + A0
 = FC

即 '包' 对应的序号是 0xB0FC,转成二进制是 1011000011111100。

?3.4.3-2 16 区中的字符?

正数和负数的原码反码补码(负数的补码是该数的原码加一)

可以查看 GB2312 中文简体字库表 直观一点:https://blog.csdn.net/panqihe/article/details/1661808

?3.4.3-3 字符编码集?

正数和负数的原码反码补码(负数的补码是该数的原码加一)

'、' 对应的是 A1A0+2,即 A1A2,比较一目了然。

GBK 和 GB18030是后来的扩展编码,兼容 GK2312。

3.4.4 乱码的产生

不同国家有不同的编码方式,同一串二进制经不同的规则解码得到的结果很可能不一样,或者干脆显示一大堆的 ?。

如下面的例子:

使用 UTF-8 编码输入 “天下”,将文件转成 GBK 编码,会显示什么?

工具:字符集编码转换,https://www.qqxiuzi.cn/bianma/zifuji.php

使用 UTF-8 编码(此编码中一个汉字占 3 个字节)储存 “天下” 得到的二进制是 11100101 10100100 10101001 11100100 10111000 10001011

使用 GBK 解码,1 开头的读 2 个字节,11100101 10100100 转成十六进制是 0xE5A4,即 69 区的第 4 个数:“澶”,再读 2 个字节转 0xA9E4是 “╀”,(因为 GBK 兼容 GB2312,所有前两字可以查 GB2312 表)

最后两个字节转成的十六进制 0xB88B 不属于 GB2312 的范围,查下 GBK编码表,https://blog.csdn.net/xiatiancc/article/details/101691474,得 “笅”。

 E5 - A0 = 0x45 = 69
 A4 - A0 = 0x4 = 4
正数和负数的原码反码补码(负数的补码是该数的原码加一)

正数和负数的原码反码补码(负数的补码是该数的原码加一)

UTF-8 编码转为 GBK,“天下” -> “澶╀笅”。

所以要显示正确的结果,编码、解码需要使用同一种字符编码。

事实真的是这样吗?

创建一个 a.txt 文件,使用 UTF-8 编码,输入 “天下”。

正数和负数的原码反码补码(负数的补码是该数的原码加一)

右键此文件/属性:

正数和负数的原码反码补码(负数的补码是该数的原码加一)

两个汉字一共占用 6 个字节,说明 UTF-8 一个汉字占 3 个字节。

右键使用 Notepad++ 打开:

正数和负数的原码反码补码(负数的补码是该数的原码加一)

使用 GBK 编码读取这段二进制:

正数和负数的原码反码补码(负数的补码是该数的原码加一)

正数和负数的原码反码补码(负数的补码是该数的原码加一)

GBK 编码中,3 个汉字 6 个字节,一个汉字占 2 个字节。

3.4.5 BOM

BOM:byte order mark(标记字节顺序),因为网络传输中分为两种,大端(Big Endian)和小端(Little Endian)。[1]

在文件开头添加 BOM 标记(0xFEFF:大端、0xFFFE:小端)表明所使用的字节顺序,为 UTF-16、UTF-32 准备的。

UTF-8 不需要 BOM 表明字节顺序,但可以用 BOM 来表示编码方式,Windows 就是采用 BOM 来标记文本文件的编码方式的。

微软在 UTF-8 中使用 BOM 是因为这样可以把 UTF-8 和 ASCII 等编码区分开来,但这样的文件在 Windows 之外的操作系统里会带来问题。

?图 3.4.5-1 IDEA 中应选择不带 BOM 的 UTF-8?

正数和负数的原码反码补码(负数的补码是该数的原码加一)

不含 BOM 的 UTF-8 才是标准形式,即文件开头没有 0xEFBBBF = 0b1110 1111 10 111011 10 111111。

0b1111 111011 111111 = 0xFEFF。[2]

[1] 详见 2.3.5 解析的第 6 条注释。存储器以字节为单位存储,字节序是对于超过 1 个字节的数据而言的,如存储 0x00009A6C,先存储低字节 0x6C(小端: 0x6C9A0000),还是先存储高字节 0x00(大端:0x00009A6C)。

字节序是指编码单元内部字节与字节之间的顺序,而不是位之间的顺序,如 11011100 10110001 以大端存储:11011100 10110001 = 0xDCB1,小端存储:10110001 11011100 = 0xB1DC。

UTF-16、UTF-32 分别以 2、4 个字节为编码单元存储,是需要区分大小端的。如以 UTF-16LE 编码存储字符 “” ,对应编码是 0xD800DF00,编码单元之间的顺序确定,即 0xD800 与 0xDF00 这两个编码单元顺序确定,但编码单元内部,字节与字节之间的顺序不确定,即 0xD800 内究竟是 00 在前还是 D8 在前,没有规定;以小端存储应是低位在前,存储 0x00D8,所以 “” 的 UTF-16LE 编码为 0x00D800DF。

UTF-8 以 1 个字节为编码单元存储,不存在编码单元内的字节谁先谁后的问题,就 1 个也没法排序。

[2] 详见 3.4.9 UTF-8 的编码规则。

3.4.6 Unicode 字符集

Unicode 是为了解决传统的字符编码方案的局限而产生的,它为所有语言中的每个字符设定了统一并且唯一的序号(code point),以供全球人使用,序号也被称为代码点。

UCS-2 和 UCS-4

这个序号一般写成十六进制,在前面加上 U+。例如:“马” 的 Unicode 序号是 U+9A6C,这个无所谓,写成 0x 也行。(Unicode 码兼容 ASCII 码)

文字和序号之间的对应关系就是 UCS-2(Universal Character Set coded in 2 octets),UCS-2 使用 2 个字节表示序号,取值范围为 U+0000 ~ U+FFFF。

为了能表示更多的文字,人们又提出了 UCS-4,即用 4 个字节表示序号。它的范围为 U+00000000 ~ U+7FFFFFFF,其中 U+00000000 ~ U+0000FFFF 与 UCS-2 一样。

Unicode 本身只规定了每个字符与序号的对应关系,并没有规定这个序号在计算机中如何存储,你当然可以直接将序号转成二进制存储,但是对于 UCS-4 而言,序号直接转成二进制,每个字符需要 4 个字节,使用 ASCII 编码的地区国家,存储体积是原来的 4 倍,这是不太容易接受的,为此诞生了 UTF-8 可变长编码,英文字符还是只占一个字节。

规定存储方式的称为 UTF(Unicode Transformation Format),其中应用较多的就是 UTF-16 和 UTF-8 了。

3.4.7 UTF-32

直接存储序号转成的二进制,每个字符占用 4 个字节(包括英文字符)。比如马在 Unicode 中的序号为:U+9A6C,对应的二进制:00000000 00000000 10011010 01101100。

UTF-32 包括 UTF-32、UTF-32BE(Big Endian:大端),UTF-32LE(Little Endian:小端)。

?表 3.4.7-1 使用十六进制编辑器打开文件?

使用不同方式存储

存储 “马”

UTF-32(Big Endian)

00 00 FE FF 00 00 9A 6C

UTF-32(Little Endian)

FF FE 00 00 6C 9A 00 00

UTF-32(不带 BOM)

00 00 9A 6C

没有提供 BOM,默认以大端解码。

工具网址

  • 记得去掉空格,Unicode 编码转换:https://www.qqxiuzi.cn/bianma/Unicode-UTF.php
  • 汉字 Unicode 编码范围:https://www.qqxiuzi.cn/zh/hanzi-unicode-bianma.php

3.4.8 UTF-16

UTF-16 使用变长字节表示:

  • 序号在 U+0000 ~ U+D7FF 的字符,使用 2 个字节表示,直接将序号转成二进制。
  • 序号在 U+10000 ~ U+10FFFF 的字符,使用 4 个字节表示,编码规则如下。

0xD800 ~ 0xDFFF:0b11011 00000 000000 ~ 0b11011 11111 111111 是空,没有对应字符,如果编码以 11011 开头,那么必是读取 4 个字节,紧跟着的 5 位,如 00011,再加 1,得到序号的二进制的前缀 100。

?表 3.4.8-1 UTF-16 编码与序号的对应关系?

序号

序号对应二进制

编码

0x0000 ~ 0xD7FF、0xE000 ~ 0xFFFF

xxxxxxxx xxxxxxxx

xxxxxxxx xxxxxxxx

0x10000 ~ 0x1FFFF

1xxxxxx xxxxxxxxxx

11011 00000xxxxxx 110111xxxxxxxxxx

0x20000 ~ 0x2FFFF

10xxxxxx xxxxxxxxxx

11011 00001xxxxxx 110111xxxxxxxxxx

0x30000 ~ 0x3FFFF

11xxxxxx xxxxxxxxxx

11011 00010xxxxxx 110111xxxxxxxxxx

0x40000 ~ 0x4FFFF

100xxxxxx xxxxxxxxxx

11011 00011xxxxxx 110111xxxxxxxxxx

0x50000 ~ 0x5FFFF

101xxxxxx xxxxxxxxxx

11011 00100xxxxxx 110111xxxxxxxxxx

0x60000 ~ 0x6FFFF

110xxxxxx xxxxxxxxxx

11011 00101xxxxxx 110111xxxxxxxxxx

0x70000 ~ 0x7FFFF

111xxxxxx xxxxxxxxxx

11011 00110xxxxxx 110111xxxxxxxxxx

0x80000 ~ 0x8FFFF

1000xxxxxx xxxxxxxxxx

11011 00111xxxxxx 110111xxxxxxxxxx

0x90000 ~ 0x9FFFF

1001xxxxxx xxxxxxxxxx

11011 01000xxxxxx 110111xxxxxxxxxx

0xA0000 ~ 0xAFFFF

1010xxxxxx xxxxxxxxxx

11011 01001xxxxxx 110111xxxxxxxxxx

0xB0000 ~ 0xBFFFF

1011xxxxxx xxxxxxxxxx

11011 01010xxxxxx 110111xxxxxxxxxx

0xC0000 ~ 0xCFFFF

1100xxxxxx xxxxxxxxxx

11011 01011xxxxxx 110111xxxxxxxxxx

0xD0000 ~ 0xDFFFF

1101xxxxxx xxxxxxxxxx

11011 01100xxxxxx 110111xxxxxxxxxx

0xE0000 ~ 0xEFFFF

1110xxxxxx xxxxxxxxxx

11011 01101xxxxxx 110111xxxxxxxxxx

0xF0000 ~ 0xFFFFF

1111xxxxxx xxxxxxxxxx

11011 01110xxxxxx 110111xxxxxxxxxx

0x100000 ~ 0x10FFFF

10000xxxxxx xxxxxxxxxx

11011 01111xxxxxx 110111xxxxxxxxxx

?表 3.4.8-2 0x0000 ~ 0xFFFF 之内范围的字符存储?

使用不同方式存储

存储 “马”

UTF-16(Big Endian)

FE FF 9A 6C

UTF-16(Little Endian)

FF FE 6C 9A

UTF-16(不带 BOM)

9A 6C

的序号 U+10300 的二进制:1000000 1100000000,对应编码在表中第 2 项,将 000000 1100000000 填入 11011 00000xxxxxx 110111xxxxxxxxxx 得到编码:11011 00000000000 1101111100000000 = 0xD800DF00。

?表 3.4.8-3 0x10000 ~ 0x1FFFF 之内范围的字符存储?

使用不同方式存储

存储 “”

UTF-16(Big Endian)

FE FF D8 00 DF 00

UTF-16(Little Endian)

FF FE 00 D8 00 DF

UTF-16(不带 BOM)

D8 00 DF 00

例 1:UTF-16 编码 0101010101001010 对应的字符。

0b0101010101001010,读取 2 个字节,不需要做特殊处理,转成十六进制 0x554A,直接在 Unicode 字库表 中找到 ‘啊’。

正数和负数的原码反码补码(负数的补码是该数的原码加一)

例 2:UTF-16 编码 11011000001111001101111000110011 对应的字符。

编码的第 6 ~ 10 位需要加 1 得到序号的二进制前缀,如 0b01111 + 1 = 0b10000。

11011 开头,应读取 4 个字节,11011 00000111100 1101111000110011。

紧接着读取 5 位:00000,得到序号的二进制前缀:0b00000 + 0b1 = 0b1。

读取 6 位,得到 111100;

剩余位数:1101111000110011,去掉 110111,得到 1000110011;

将这三部分拼接得到:0b1 111100 1000110011 = 0x1F233,然后根据此序号在字库中查询到 “”。

例 3:“” 在 Unicode 的序号是 0x1F234,求它的 UTF-16LE 编码对应的十六进制。

序号的二进制前缀需要 – 1,得到编码的第 6 ~ 10 位,如 0b1100 – 1 = 0b01011。(不足 5 位前面补 0)

0x1F234 = 0b1 1111001000110100,这种 17 位的没法用 2 个字节存储,取出二进制的后 16 位:111100 1000110100,前缀为 1。

编码的开头为 11011;

紧接着的 5 位等于前缀 – 1 = 00000;

接着 6 位是取出的二进制的前 6 位:111100;

接着 6 位是固定的:110111;

最后 10 位是取出的二进制的最后 10 位:1000110100;

将其拼接得到:0b11011 00000 111100 110111 1000110100 = 0xD83C DE34。

使用小端表示,每两个字节交换顺序,再加上 BOM 表明字节顺序:0xFFFE 3CD8 34DE。

正数和负数的原码反码补码(负数的补码是该数的原码加一)

工具网址

  • UTF-16 工具转换:https://www.qqxiuzi.cn/bianma/utf-16.htm

?图 3.4.8-1 添加前缀 FFFE 表明字节顺序?

正数和负数的原码反码补码(负数的补码是该数的原码加一)

3.4.9 UTF-8

UTF-8 使用变长字节表示:

  • 序号在 U+00 ~ U+7F 的字符,编码使用 1 个字节表示,直接将序号转成二进制。
  • 序号在 U+80 ~ U+7FF 的字符,使用 2 个字节表示。
  • 序号在 U+800 ~ U+FFFF 的字符,使用 3 个字节表示。
  • 序号在 U+10000 ~ U+10FFFF 的字符,使用 4 个字节表示。

?表 3.4.9-1 UTF-8 编码与序号的对应关系?

序号

编码

0x00 ~ 0x7F(0 ~ 127)

0xxxxxxx

0x80 ~ 0x7FF(128 ~ 2047)

110xxxxx 10xxxxxx

0x800 ~ 0xFFFF(2048 ~ 65535)

1110xxxx 10xxxxxx 10xxxxxx

0x10000 ~ 0x10FFFF(65536 ~ 1114111)

11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

BOM:0xFEFF = 0b1111 111011 111111,填充到 1110xxxx 10xxxxxx 10xxxxxx 中,得到编码:11101111 10111011 10111111 = 0xEFBBBF。

例 1:“倩” 在 Unicode 的序号为 0x5029,求它的 UTF-8 编码对应的十六进制。

0x5029 处于 0x800 ~ 0xFFFF 范围内,对应编码 1110xxxx 10xxxxxx 10xxxxxx。

0x5029 = 0b0101000000101001,补足 16 位,不够前面补 0,截成三段,长度分别为 4、6、6 位:0101 000000 101001,然后填充到对应编码,11100101 10000000 10101001 = 0xE580A9。

带 BOM 的 UTF-8:0xEF BB BF E5 80 A9。

例 2:UTF-8 编码 111001101001011110100000,求它对应的字符。

观察前 4 位:1110,应读取 3 个字节,11100110 10010111 10100000,去掉第 1 个字节的 1110、第 2 个字节的 10、第三个字节的 10,得到 0110 010111 100000 = 0x65E0,在 Unicode 字库的 0 号平面查找到 0x65E0 对应的字符 “无”。

由于 UTF-8 的处理单元为一个字节(也就是一次处理一个字节),所以处理器在处理的时候就不需要考虑这一个字节的存储是在高位还是在低位,直接拿到这个字节进行处理就行了,因为大小端是针对大于一个字节的数的存储问题而言的。

本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 1553299181@qq.com 举报,一经查实,本站将立刻删除。
如若转载,请注明出处:https://www.pop371.cn/2827.html