数值的表示与转化
撰写时间:2025-02-17
修订时间:2025-02-17
负数在内存中的表示
正数与负数的不同表示
使用i32.store指令在内存中分别存储了30及-30两个值。它们均各自占用4个字节的内存空间。
数值30在内存中相应4个字节分别为1E 00 00 00,而数值-30在内存中相应4个字节分别为E2 FF FF FF。
两相比较,除高位部分分别为0x00及0xFF不一样之外,正数30最低字节的值0x1E不等于负数-30最低字节的值0xE2。这是因为计算机系统采取了补码(two's complement)的方式来表示一个整数。
补码原理
单字节负数的补码
对于数值-30,分4步求出其补码。
第一步,取数值-30的绝对值30的二进制原码(sign-magnitude)。
第二步,求出对应负数的原码。即,将其符号位置为1,以表示负数。
则上面的二进制变为:
第三步,求出负数的反码(one's complement)。即,保留符号位,其余位取反。
则上面的二进制变为:
第四步,求出负数的补码(two's complement)。即,将反码的值加1。
则最终结果的二进制及其十六进制分别为:
因此,单字节的十进制数值-30,其十六进制的值为0xE2。
4字节负数的补码
当数值-30需要使用4字节来存储时,求其补码的方式与步骤与上节一样,但需将4个字节的内存区域纳入参与转换运算的范围。
第一步,正数的原码:
第二步,负数的原码:
第三步,负数的反码:
第四步,负数的补码:
因此,4字节的十进制数值-30,其十六进制的值为0xFFFFFFE2。
非负数的补码
0与正数也同样存在补码,但从概念上定义了:对于0与正数,它们的反码及补码均等于其原码,因此它们的补码不存在像负数一样的转换过程,直接取其原码值即可。
补码的作用
补码是计算机表示有符号整数的标准方式,主要目的是统一加减法运算,避免硬件额外区分正负数。
计算机使用补码的好处:
- 运算一致性。无论操作数是正还是负,加减法可直接使用同一套逻辑电路,简化了硬件设计。
- 消除冗余。补码解决了原码和反码中 +0 和 -0 表示不唯一的问题(补码中 0 只有 0 一种形式)。
- 兼容性。现代CPU的指令集和硬件设计均基于补码,因此所有整数(包括正数)均以补码形式存储。
负数存储的结构特点
综上,一个负数在内存中存储,其结构特点如下::
- 必须有明确、足够的位域来存储一个负数。
- 在内存中,除表示特定数值的字节(数值位域)之外,其余高位字节全部为0xFF(符号位域)。
- 在按小尾存储的系统中,其数值位域位于内存的最低地址。
负数的加载
因为负数在内存中存储的结构特点,当从内存加载特定字节,以得到其原有的带符号的数值时,应同时考虑原来存储该数值时的所有位域,及其存储数值时原来的偏移值。
正确的负数加载
下面是正确的加载方式。
上面代码,对于一个32位的负数,分别调用i32的load8_s, load16_s,及load指令,均正确地读出其原来数值-30。
load8_s的内部细节
从第一节我们知道,30及-30这两个值在32位域中如何表示。这里再次使用该节代码:
30的第8位至第31位,即高24位的值均为0;而-30的第8位至第31位,即高24位的值均为1。
现在,只看值为-30的情况:
我们准备只取出第0个字节值为0xE2的数值。其二进制值如下:
实际上,226及-30,这2个十进制数,它们的二进制数值的最低权重的字节的值都等于1110_0010。
可见,对于一个在内存中存储的位长为1字节、值为1110_0010的二进制数,即可表示一个无符号的十进制数值226,也可表示一个有符号的且为负的十进制数值-30。当读取此二进制数值时,依赖于我们准备如何解释这个二进制数值。
如果我们要将此解读为无符号数值,则直接转换为十进制数值。
如果我们要将此解读为有符号数值,则:
如果它是非负数,则直接转换为带符号的十进制数值。
如果它是负数,则按以下步骤进行:
- 将值减1。得到1110_0001。
- 符号位不变,其余位取反。得到1001_1110。
- 将符号位置0。得到0001_1110,即十进制数值30。
- 在该值前面添加
-
号并返回。
load8_s指令先取出第0个字节的值0xE2,根据其二进制数值,取出其符号位扩展为32位:
- 若最高位符号位为1,则高24位均填充1。
- 若最高位符号位为0,则高24位均填充0。
这意味着一个原为正数的32位数值,单独取出其第0个字节的数值再扩展为32位,也有可能变成一个负数。
