Attention! Blog has been moved. Please visit blog at new address devhacksandgoodies.pw.

Notice! This article was written in fast passion for Lua 5.3 alpha (work 2). It doesn’t cover all innovations of Lua 5.3 (work 2) and I currently don’t have time to improve it. I’m learning Scala btw, it is more suitable for my needs.

Main new features of Lua 5.3 alpha (work 2) are following:

Two internal types “integer” and “float” for type “number” – yes, they are internal, because Lua ideology hasn’t changed, it still tries to protect a user from messing with numerical types. Internal type is called subtype. I will show below how to get subtype of variable containing number.
Native bitwise operators support. Warning, in default installtion they are 64-bitish.
Little UTF-8 support

Lua 5.3 installation offers specific configurations for sizes of “integer” and “float”:

64-bit integer and 64-bit double (called “Standart Lua”, default) – #define LUA_INT_LONGLONG, #define LUA_REAL_DOUBLE
32-bit integer and 32-bit float (called “Small Lua”) – #define LUA_INT_INT, #define LUA_REAL_FLOAT
Mixed (will be used very rarely)
You can configure then in luaconf.h, just find lines “#define LUA_INT_LONGLONG”, “#define LUA_REAL_DOUBLE” and change them to your needs. For me it is a bit strange that there is LUA_INTEGER macrodefinition which is defines “integer” type and there is no LUA_FLOAT macrodefinition which should be used to define “float” type, the authors decided to use old LUA_NUMBER macrodefinition for that, hope this will not create mess and bugs.

Learning changes by coding

Let us now do some code, most of the code based on Lua 5.3 test suite.

The following will print sizes in bits of subtypes “integer” and “float”:

local debug = require("debug")
print(debug.Csize('I') .. "-bit integers") -- 64-bit integers
print(debug.Csize('F') .. "-bit floats") -- 64-bit floats

As I promised here is how to print subtype of number:

local numInteger = 2
print(type(numInteger)) -- "number"
print(math.type(numInteger)) -- "integer"
local numFloat = 2.0
print(type(numFloat)) -- "number"
print(math.type(numFloat)) -- "float"

-- for non-numeric types it just returns "nil"
print(math.type("")) -- "nil"
print(math.type("10")) -- "nil"
print(math.type({})) -- "nil"
print(math.type(nil)) -- "nil"

There is interesting integeration of arithmetics on numbers with strings (more JavaScript-like) and new integer division operator:

assert(10/20 == 0.5)
assert(10-20 == -10)
assert(10*-20 == -200)
assert(10^3 == 1000)
assert(10/"20" == 0.5)
assert("10"-"20" == -10)
assert("10"*"-20" == -200)
assert("10"^"3" == 1000)
assert(2/0 == 10.0/0)
assert(10 // 3 == 3) -- this operator seems changed from "\" to "//"
assert(math.type(10 // 3) == "integer")
assert(10 + 3 == 13)
assert(math.type(10 + 3) == "integer")
assert(10.0 + 3 == 13.0)
assert(math.type(10.0 + 3) == "float")
assert(10 + 3.0 == 13.0)
assert(math.type(10 + 3.0) == "float")
assert(4 % -3 == -2)
assert(8 % (4 + (-3)*2) == 0)

assert(not pcall(function() return 10 // 0 end))
assert(not pcall(function() return 10 // 2.0^90 end)) -- integer overflow
assert(not pcall(function() return 10 % 0 end)0

Converting integer to float

assert(math.type(numInteger) == "integer")
numFloat = numInteger + 0.0 -- convert integer to float
assert(math.type(numFloat) == "float")

Lets play with integer overflow:

print(1000*1000*1000*1000*1000*1000) -- 1000000000000000000
print(1000*1000*1000*1000*1000*1000*10) -- -8446744073709551616
print(1000*1000*1000*1000*1000*1000*100) -- 7766279631452241920
print(1000*1000*1000*1000*1000*1000*100.0) -- 1e+20 = 100000000000000000000.0
print(0x7FFFFFFFFFFFFFFF) -- maximal 64-bit signed integer 9223372036854775807
print(0x7FFFFFFFFFFFFFFF + 1) -- wrap to -9223372036854775808
print(0x8000000000000000) -- minimal 64-bit signed integer -9223372036854775808
print(0x8000000000000000 - 1) -- wrap to 9223372036854775807

Lets play with bitwise operations:

local a = -1
a = 0xFFFFFFFFFFFFFFFF -- also -1
a = 0 - 1 -- also -1
assert(a & -1 == -1) -- masking with 0xFFFFFFFFFFFFFFFF preserves original integer -1
assert(35 & -1 == 35) -- masking with 0xFFFFFFFFFFFFFFFF preserves original integer 35
assert(~0 == -1) -- inverting bits using Logical Unary NOT

a = 0xF0F0F0F0F0F0F0F0
assert(a | -1 == -1)
assert(a ~ a == 0) -- reminded me "XOR eax, eax" 🙂
assert(a ~ 0 == a)
assert(a ~ ~a == -1)
assert(a >> 4 == ~a)

Lets play with UTF8:

local function utf8_len(str)
    return #str - select(2, string.gsub(str, "[\x80-\xBF]", function() end))
end

local str = "Συρακούσα"
print(#str, utf8_len(str), utf8.len(str)) -- will print 19     9      9
str = '\u{03a3}\u{03c5}\u{03c1}\u{03b1}\u{03ba}\u{03bf}\u{1f7b}\u{03c3}\u{03b1}' -- same string encoded
print(str)

Porting code from previous versions Lua 5.2/5.1

Most important change I had to made to my code is related to tostring() function and perhaps in your case it will be __tostring too. The problem if we try to print variable with type “number” which has subtype “float”, even if it has integer value inside, it will be printed with “.0” at the end.

local a = 8.0
local b = 4.0
local num = a / b
print(tostring(num)) -- will print "2.0"
print(num) -- tostring() called internally inside print()

-- but beware table.concat() uses __concat which differes from __tostring logic and makes me sad!
local t = { "I want to print integer without dot and digits after dot ", num }
print(table.concat(t)) -- will print "I want to print integer without dot and digits after dot 2"
t[1] = "I want to print float "
t[2] = 2.3
print(table.concat(t)) -- will print "I want to print float 2.3"

-- there is also cave it that you can't create float keys containing integers, for example
t[2.0] = 456 - will be still t[2] so you can assign t[1.0], t[2.0], t[3.0] and iterate using ipairs() or for i, 3, 1 do <...> end,
--- but everything else is same as in previous versions:
t[2.4] = 77 - will be t[2.4], and of course
t['2'], t['2.0'], t['2.4'] = 8, 9, 10 - will be all different string keys

To fix this you now need to explictically specify format using string.format():

print(tostring(num)) -- "2.0"
print(string.format("%d", num)) -- "2"
print(("%d"):format(num)) -- short version which I prefer more

By the way, the short version works because of

local string = require("string") -- ensure that "string" library is specified in loadedlibs[] or preloadedlibs[] in linit.c
assert(debug.getmetatable("").__index == string)

and this is one of the most brilliant things of Lua 🙂

Computation involving integers can now cause overflows, they are not errors, but just silent overflows, I have not yet practiced with them, so I don’t know how to fix. But strictly controlling that all your computations are integer can give some perfomance benefits. Of course for heavy computations you should not use Lua VM, use C/C++ library or even better OpenCL.

For compatibility with previous bit libraries which all was 32-bit you can create something like this in /usr/share/lua/5.3/bit32.lua or /usr/local/share/lua/5.3/bit32.lua:

local bit32 = { }

bit32.bnot = function(a)
    return ~a & 0xFFFFFFFF
end

bit32.band = function(a, b)
    return a & b
end

bit32.bor = function(a, b)
    return (a | b) & 0xFFFFFFFF
end

bit32.bxor = function(a, b)
    return (a ~ b) & 0xFFFFFFFF
end

bit32.lshift = function(a, b)
    return (a << b) & 0xFFFFFFFF
end

bit32.rshift = function(a, b)
    return a >> b
end

return bit32

Full compatibility with Lua 5.2’s bit32 library:

-- no built-in 'bit32' library: implement it using bitwise operators

local bit32 = {}

function bit32.bnot (a)
  return ~a & 0xFFFFFFFF
end

--
-- in all vararg functions, avoid creating 'arg' table when there are
-- only 2 (or less) parameters, as 2 parameters is the common case
--

function bit32.band (x, y, z, ...)
  if not z then
    return ((x or -1) & (y or -1)) & 0xFFFFFFFF
  else
    local arg = {...}
    local res = x & y & z
    for i = 1, #arg do res = res & arg[i] end
    return res & 0xFFFFFFFF
  end
end

function bit32.bor (x, y, z, ...)
  if not z then
    return ((x or 0) | (y or 0)) & 0xFFFFFFFF
  else
    local arg = {...}
    local res = x | y | z
    for i = 1, #arg do res = res | arg[i] end
    return res & 0xFFFFFFFF
  end
end

function bit32.bxor (x, y, z, ...)
  if not z then
    return ((x or 0) ~ (y or 0)) & 0xFFFFFFFF
  else
    local arg = {...}
    local res = x ~ y ~ z
    for i = 1, #arg do res = res ~ arg[i] end
    return res & 0xFFFFFFFF
  end
end

function bit32.btest (...)
  return bit32.band(...) ~= 0
end

function bit32.lshift (a, b)
  return ((a & 0xFFFFFFFF) << b) & 0xFFFFFFFF
end

function bit32.rshift (a, b)
  return ((a & 0xFFFFFFFF) >> b) & 0xFFFFFFFF
end

function bit32.arshift (a, b)
  a = a & 0xFFFFFFFF
  if b <= 0 or (a & 0x80000000) == 0 then
    return (a >> b) & 0xFFFFFFFF
  else
    return ((a >> b) | ~(0xFFFFFFFF >> b)) & 0xFFFFFFFF
  end
end

function bit32.lrotate (a ,b)
  b = b & 31
  a = a & 0xFFFFFFFF
  if b ~= 0 then
    a = (a << b) | (a >> (32 - b))
  end
  return a & 0xFFFFFFFF
end

function bit32.rrotate (a, b)
  return bit32.lrotate(a, -b)
end

local function checkfield (f, w)
  w = w or 1
  assert(f >= 0, "field cannot be negative")
  assert(w > 0, "width must be positive")
  assert(f + w <= 32, "trying to access non-existent bits")
  return f, ~(-1 << w)
end

function bit32.extract (a, f, w)
  local f, mask = checkfield(f, w)
  return (a >> f) & mask
end

function bit32.replace (a, v, f, w)
  local f, mask = checkfield(f, w)
  v = v & mask
  a = (a & ~(mask << f)) | (v << f)
  return a & 0xFFFFFFFF
end

return bit32
Advertisement
Privacy Settings