Integer and Float

In versions before Lua 5.3, only one type of number is supported, which is floating-point by default. You can use integers by modifying the source code of the Lua interpreter. I understand that this is because Lua was originally used as a configuration language, and most of its users are not programmers, and it does not distinguish between integers and floating-point numbers. For example, 5 and 5.0 are two identical numbers. Later, as the use of Lua expanded, and the need to support integers became stronger (such as bit operations), finally in Lua version 5.3, integers and floating-point numbers were distinguished. This also brings some complexity. The main binary operators are divided into the following three types of processing rules kind:

  • Supports integer and floating point numbers, including +, -, *, // and %. If both operands are integers, the result is also an integer; otherwise (both operands have at least one floating-point number) the result is a floating-point number.
  • Only floats are supported, including / and ^. Regardless of the type of the operands, the result is a floating point number. For example 5/2, although both operands are integers, they will be converted to floating point numbers, and then the result is 2.5.
  • Only integers are supported, including 5 bit operations. The operands must be integers, and the result is also an integer.

The processing of the above three types will be reflected in the constant folding fold_const() function of syntax analysis and when the virtual machine executes. The code is cumbersome and omitted here.

Type Conversion

Lua also defines the above rules of type conversion (mainly the rules in the case of incomplete conversion):

  • Integer to Float: If the full conversion is not possible, the closest floating point number is used. i.e. the conversion will not fail, only precision will be lost.
  • Float to integer: If the conversion cannot be completed, an exception will be thrown.

In the Rust language, the rules for converting integers to floating-points are the same, but converting floating-points to integers is different. This is considered a bug and will be fixed. Before the fix, we can only do this integrity check ourselves, that is, throw an exception if the conversion fails. For this we implement the ftoi() function:

pub fn ftoi(f: f64) -> Option<i64> {
     let i = f as i64;
     if i as f64 != f {
         none
     } else {
         Some(i)
     }
}

You can directly use as when converting an integer to a floating-point type, and you need to use this function when converting a floating-point type to an integer.

This conversion will be involved in the syntax analysis and virtual machine execution stages, so create a new utils.rs file to put these general functions.

Compare

In the Lua language, in most cases, the distinction between integers and floating-point numbers is avoided as much as possible. The most direct example is that the result of the statement 5 == 5.0 is true, so Value::Integer(5) and Value::Float(5.0) are equal in the Lua language. Another point is that if these two values are used as the key of the table, they are also considered to be the same key. To this end, we have to modify the two trait implementations of Value before.

The first is the PartialEq trait that compares for equality:

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

Then there is the Hash trait:

impl Hash for Value {
     fn hash<H: Hasher>(&self, state: &mut H) {
         match self {
             Value::Float(f) =>
                 if let Some(i) = ftoi(*f) {
                     i.hash(state)
                 } else {
                     unsafe {
                         mem::transmute::<f64, i64>(*f).hash(state)
                     }
                 }

However, there is still one place where the type needs to be distinguished, that is, when adding a constant to the constant table during syntax analysis, when querying whether the constant already exists. To do this, implement a type-sensitive comparison method:

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

Test

At this point, the syntax analysis of the binary operation statement is finally completed. The virtual machine execution part is very simple and is skipped here. You can test the Lua code as follows:

g = 10
local a,b,c = 1.1, 2.0, 100

print(100+g) -- commutative, AddInt
print(a-1)
print(100/c) -- result is float
print(100>>b) -- 2.0 will be convert to int 2
print(100>>a) -- panic