Module:Radix
From CloudModding MM Wiki
Documentation for this module may be created at Module:Radix/doc
-- module providing various functions for handling numbers in radices 2..36
-- note: not using tonumber() since it's not as featurful as we need
local p = {};
local LU = require('libraryUtil');
local B32 = require('bit32');
-- this function parses an integer; it's global to allow use by other modules on
-- the site. This function will stop at the first non-numeric character
-- encountered.
function p.parse_int(numstr, radix)
radix = radix or 0;
-- some type-checking
LU.checkType("parse_int", 1, numstr, "string");
LU.checkType("parse_int", 2, radix, "number");
assert(radix == math.floor(radix), "radix must be an integer, yet isn't");
if radix ~= 0 then
assert(2 <= radix, "radix must be at least 2 (and at most 36)");
assert(radix <= 36, "radix must be at most 36 (and at least 2)");
end
-- now, the function
local signage = 1;
if mw.ustring.sub(numstr, 1, 1) == '-' then
signage = -1;
numstr = mw.ustring.sub(numstr, 2);
end
if radix == 0 then
-- we'll need to figure out the radix on our own, then
local ohprefix = mw.ustring.sub(numstr, 1, 2);
if ohprefix == "0x" then
radix = 16;
elseif ohprefix == "0o" then
radix = 8;
elseif ohprefix == "0b" then
radix = 2;
elseif ohprefix == "0d" then
radix = 10;
end
-- if we changed the radix, then we need to take the oh prefix out of
-- the string, otherwise there's nothing to remove, and we default to
-- base 10.
if radix ~= 0 then
numstr = mw.ustring.sub(numstr, 3);
else
radix = 10;
end
end
-- and now we get to the conversion part
local res = 0;
-- loop in terms of codepoints so we're already set to do it if/when we
-- expand the range of characters handled in the future (the oh prefix stuff
-- earlier can be safely done assuming ASCII)
for cp in mw.ustring.gcodepoint(numstr) do
local thedigit;
if (0x30 <= cp) and (cp <= 0x39) then -- digits 0..9
thedigit = B32.band(cp, 0x0F);
elseif (0x41 <= B32.band(cp, 0xDF)) and (B32.band(cp, 0xDF) <= 0x5A) then -- ASCII letters A..Z, any case
thedigit = B32.band(cp, 0xDF) - 0x41 + 10;
else -- some unrecognizable character, we have to stop
break;
end
if thedigit >= radix then
-- while we could count this an invalid character and break, you
-- likely made a mistake, so we'll error instead
error("Invalid digit for radix!");
end
res = res * radix + thedigit;
end
return res * signage;
end
-- XXX TODO a function to parse floats, when needed
-- writes a number, int or float, to a string
function p.write_num(thenum, radix)
-- type-checking
LU.checkType("write_num", 1, thenum, "number");
LU.checkType("write_num", 2, radix, "number");
assert(radix == math.floor(radix), "radix must be an integer, yet isn't");
assert(2 <= radix, "radix must be at least 2 (and at most 36)");
assert(radix <= 36, "radix must be at most 36 (and at least 2)");
-- function
local res = "";
if thenum < 0 then
res = res .. "-";
thenum = math.abs(thenum);
end
local ipart = math.floor(thenum);
local fpart = thenum - ipart;
local charlist = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
-- it's easier to handle the integer part if we turn it into a number less
-- than one first (note that floor(log()) + 1 is just a neat trick to get
-- the length of an integer number in the chosen radix)
local isize = math.floor(math.log(ipart)/math.log(radix)) + 1;
ipart = ipart / (radix ^ isize);
-- now to write out the integer part; we multiply the integer part by the
-- radix, take the integer part of _that_, and subtract the written digit
-- from ipart.
for i = 1, isize do
ipart = ipart * radix;
res = res .. mw.ustring.sub(charlist, math.floor(ipart) + 1, math.floor(ipart) + 1);
ipart = ipart - math.floor(ipart);
end
-- if there's a fractional part, get to that now
if fpart ~= 0 then
res = res .. ".";
-- note that this is just like how we handled the integer part, only we
-- didn't have to divide first.
while fpart ~= 0 do
fpart = fpart * radix;
res = res .. mw.ustring.sub(charlist, math.floor(fpart) + 1, math.floor(fpart) + 1);
fpart = fpart - math.floor(fpart);
end
end
return res;
end
-- only this function can be #invoke'd, the ones above can't.
function p.ConvertInteger(frame)
local the_num = frame.args[1];
local from_rad = tonumber(frame.args[2]) or 0;
local to_rad = tonumber(frame.args[3]) or 10;
return p.write_num(p.parse_int(the_num, from_rad), to_rad);
end
return p;