整数和浮点数

在Lua 5.3之前的版本中,只支持一种类型的数字,默认是浮点数,可以通过修改Lua解释器源码来使用整数。我理解这是因为Lua最初是被用作配置语言,面向的使用者大多不是程序员,是不区分整数和浮点数的,比如55.0就是两个完全一样的数字。后来随着Lua使用范围的扩大,同时支持整数的需求越发强烈(比如位运算),最终在Lua 5.3版本中区分了整数和浮点数。这也带来了一些复杂度,主要二元运算符对不同类型的处理规则,分为如下三类:

  • 支持整数和浮点数,包括+-*//%。如果两个操作数都是整数,则结果也是整数;否则(两个操作数至少有一个浮点数)结果是浮点数。
  • 只支持浮点数,包括/^。无论操作数是什么类型,结果都是浮点数。比如5/2,两个操作数虽然都是整数,但会转换为浮点数,然后计算结果为2.5
  • 只支持整数,包括5个位操作。要求操作数一定是整数,结果也是整数。

对上述三类的处理,在语法分析的常量折叠fold_const()函数和虚拟机执行时,都会体现。代码很繁琐,这里省略。

类型转换

Lua也定义了上述类型转换的规则(主要是不能完整转换情况下的规则):

  • 整型转浮点型:如果不能完整转换,则使用最接近的浮点数。即转换不会失败,只会丢失精度。
  • 浮点型转整型:如果不能完整转换,则抛出异常。

而Rust语言中,整型转浮点型规则一样,但浮点型转整型就不同了,没有检查是否能完整转换。这被认为是个bug并会修复。在修复前,我们只能自己做这个完整性的检查,即如果转换失败,则抛出异常。为此我们实现ftoi()函数:

  1. pub fn ftoi(f: f64) -> Option<i64> {
  2. let i = f as i64;
  3. if i as f64 != f {
  4. None
  5. } else {
  6. Some(i)
  7. }
  8. }

整型转浮点型时直接用as即可,而浮点型转整型时就需要用这个函数。

在语法分析和虚拟机执行阶段,都会涉及到这个转换,所以新建utils.rs文件用来放这些通用函数。

比较

Lua语言中,大部分情况下是尽量避免整数和浮点数的区别。最直接的例子就是,这个语句5 == 5.0的结果是true,所以Value::Integer(5)Value::Float(5.0),在Lua语言中是相等的。另外一个地方是,用这两个value做table的key的话,也认为是同一个key。为此,我们就要修改之前对Value的两个trait实现。

首先是比较相等的PartialEq trait:

  1. impl PartialEq for Value {
  2. fn eq(&self, other: &Self) -> bool {
  3. match (self, other) {
  4. (Value::Integer(i), Value::Float(f)) |
  5. (Value::Float(f), Value::Integer(i)) => *i as f64 == *f && *i == *f as i64,

然后是Hash trait:

  1. impl Hash for Value {
  2. fn hash<H: Hasher>(&self, state: &mut H) {
  3. match self {
  4. Value::Float(f) =>
  5. if let Some(i) = ftoi(*f) {
  6. i.hash(state)
  7. } else {
  8. unsafe {
  9. mem::transmute::<f64, i64>(*f).hash(state)
  10. }
  11. }

不过,还是有一个地方需要区分类型的,就是在语法分析时,向常量表中添加常量时,查询常量是否已经存在的时候。为此要实现一个区分类型的比较方法:

  1. impl Value {
  2. pub fn same(&self, other: &Self) -> bool {
  3. // eliminate Integer and Float with same number value
  4. mem::discriminant(self) == mem::discriminant(other) && self == other
  5. }
  6. }

测试

至此,二元运算语句的语法分析终于完成。虚拟机执行部分就很简单,这里略过。可以使用如下测试Lua代码:

  1. {{#include ../listing/ch05.arithmetic/test_lua/binops.lua}}