ICS2017 Programming Assignment
  • Introduction
  • PA0 - 世界诞生的前夜: 开发环境配置
    • Installing a GNU/Linux VM
    • First Exploration with GNU/Linux
    • Installing Tools
    • Configuring vim
    • More Exploration
    • Transferring Files between host and container
    • Acquiring Source Code for PAs
  • PA1 - 开天辟地的篇章: 最简单的计算机
    • 在开始愉快的PA之旅之前
    • 开天辟地的篇章
    • RTFSC
    • 基础设施
    • 表达式求值
    • 监视点
    • i386手册
  • PA2 - 简单复杂的机器: 冯诺依曼计算机系统
    • 不停计算的机器
    • RTFSC(2)
    • 程序, 运行时环境与AM
    • 基础设施(2)
    • 输入输出
  • PA3 - 穿越时空的旅程: 异常控制流
    • 更方便的运行时环境
    • 等级森严的制度
    • 穿越时空的旅程
    • 文件系统
    • 一切皆文件
  • PA4 - 虚实交错的魔法: 分时多任务
    • 虚实交错的魔法
    • 超越容量的界限
    • 分时多任务
    • 来自外部的声音
    • 编写不朽的传奇
  • PA5 - 从一到无穷大: 程序与性能
    • 浮点数的支持
    • 通往高速的次元
    • 天下武功唯快不破
  • 杂项
    • 为什么要学习计算机系统基础
    • 实验提交要求
    • Linux入门教程
    • man入门教程
    • git入门教程
    • i386手册指令集阅读指南
    • i386手册勘误
    • 指令执行例子
Powered by GitBook
On this page

Was this helpful?

  1. PA5 - 从一到无穷大: 程序与性能

浮点数的支持

PreviousPA5 - 从一到无穷大: 程序与性能Next通往高速的次元

Last updated 6 years ago

Was this helpful?

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

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

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

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

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.2的FLOAT类型表示为:

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

接下来我们来考虑FLOAT类型的常见运算, 假设实数a, b的FLOAT类型表示分别为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