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
This was a great read! You *may* wish to update it, as `debug.numbits` is gone and has been replaced by `debug.Csize`. Also, `i` and `f` both return `4` (4*8 = 32) because those are reporting the C int and float types. `I` reports long long and `F` reports long double, which gives expected results (although 8, because everything is in bytes, except `b`, which is number of bits in a byte.) I only point this out because I spent some time fighting this. 😛
What’s wrong with this page? The fonts look ridiculous and the code examples are full of escaped HTML entities. It’s almost unreadable.
0 Pingbacks