浮点数的支持

我们已经在PA3中把仙剑奇侠传运行起来了, 但却不能战斗, 这是因为还有一些浮点数相关的工作需要处理. 现在到了处理的时候了. 要在NEMU中实现浮点指令也不是不可能的事情. 但实现浮点指令需要涉及x87架构的很多细节, 根据KISS法则, 我们选择了一种更简单的方式: 我们通过整数来模拟实数的运算, 这样的方法叫binary scaling.

我们先来说明如何用一个32位整数来表示一个实数. 为了方便叙述, 我们称用binary scaling方法表示的实数的类型为FLOAT. 我们约定最高位为符号位, 接下来的15位表示整数部分, 低16位表示小数部分, 即约定小数点在第15和第16位之间(从第0位开始). 从这个约定可以看到, FLOAT类型其实是实数的一种定点表示.

31  30                  16                    0
+----+-------------------+--------------------+
|sign|      integer      |      fraction      |
+----+-------------------+--------------------+

这样, 对于一个实数a, 它的FLOAT类型表示A = a * 2^16(截断结果的小数部分). 例如实数1.25.6FLOAT类型来近似表示, 就是

1.2 * 2^16 = 78643 = 0x13333
+----+-------------------+--------------------+
| 0  |         1         |        3333        |
+----+-------------------+--------------------+


5.6 * 2^16 = 367001 = 0x59999
+----+-------------------+--------------------+
| 0  |         5         |        9999        |
+----+-------------------+--------------------+

而实际上, 这两个FLOAT类型数据表示的数是:

0x13333 / 2^16 = 1.19999695
0x59999 / 2^16 = 5.59999084

对于负实数, 我们用相应正数的相反数来表示, 例如-1.2FLOAT类型表示为:

-(1.2 * 2^16) = -0x13333 = 0xfffecccd

比较FLOAT和float

FLOATfloat类型的数据都是32位, 它们都可以表示2^32个不同的数. 但由于表示方法不一样, FLOATfloat能表示的数集是不一样的. 思考一下, 我们用FLOAT来模拟表示float, 这其中隐含着哪些取舍?

接下来我们来考虑FLOAT类型的常见运算, 假设实数a, bFLOAT类型表示分别为A, B.

  • 由于我们使用整数来表示FLOAT类型, FLOAT类型的加法可以直接用整数加法来进行:
    A + B = a * 2^16 + b * 2^16 = (a + b) * 2^16
    
  • 由于我们使用补码的方式来表示FLOAT类型数据, 因此FLOAT类型的减法用整数减法来进行.
    A - B = a * 2^16 - b * 2^16 = (a - b) * 2^16
    
  • FLOAT类型的乘除法和加减法就不一样了:
    A * B = a * 2^16 * b * 2^16 = (a * b) * 2^32 != (a * b) * 2^16
    
    也就是说, 直接把两个FLOAT数据相乘得到的结果并不等于相应的两个浮点数乘积的FLOAT表示. 为了得到正确的结果, 我们需要对相乘的结果进行调整: 只要将结果除以2^16, 就能得出正确的结果了. 除法也需要对结果进行调整, 至于如何调整, 当然难不倒聪明的你啦.
  • 如果把A = a * 2^16看成一个映射, 那么在这个映射的作用下, 关系运算是保序的, 即a <= b当且仅当A <= B, 故FLOAT类型的关系运算可以用整数的关系运算来进行.

有了这些结论, 要用FLOAT类型来模拟实数运算就很方便了. 除了乘除法需要额外实现之外, 其余运算都可以直接使用相应的整数运算来进行. 例如

float a = 1.2;
float b = 10;
int c = 0;
if (b > 7.9) {
  c = (a + 1) * b / 2.3;
}

FLOAT类型来模拟就是

FLOAT a = f2F(1.2);
FLOAT b = int2F(10);
int c = 0;
if (b > f2F(7.9)) {
  c = F2int(F_div_F(F_mul_F((a + int2F(1)), b), f2F(2.3)));
}

其中还引入了一些类型转换函数来实现和FLOAT相关的类型转换.

仙剑奇侠传的框架代码已经用FLOAT类型对浮点数进行了相应的处理. 你还需要实现一些和FLOAT类型相关的函数:

/* navy-apps/apps/pal/include/FLOAT.h */
int32_t F2int(FLOAT a);
FLOAT int2F(int a);
FLOAT F_mul_int(FLOAT a, int b);
FLOAT F_div_int(FLOAT a, int b);
/* navy-apps/apps/pal/src/FLOAT/FLOAT.c */
FLOAT f2F(float a);
FLOAT F_mul_F(FLOAT a, FLOAT b);
FLOAT F_div_F(FLOAT a, FLOAT b);
FLOAT Fabs(FLOAT a);

其中F_mul_int()F_div_int()用于计算一个FLOAT类型数据和一个整型数据的积/商, 这两种特殊情况可以快速计算出结果, 不需要将整型数据先转化成FLOAT类型再进行运算.

事实上, 我们并没有考虑计算结果溢出的情况, 不过仙剑奇侠传中的浮点数结果都可以在FLOAT类型中表示, 所以你可以不关心溢出的问题. 如果你不放心, 你可以在上述函数的实现中插入assertion来捕捉溢出错误.

实现binary scaling

实现上述函数来在仙剑奇侠传中对浮点数操作进行模拟. 实现正确后, 你就可以在仙剑奇侠传中成功进行战斗了.

results matching ""

    No results matching ""