Skip to content

Instantly share code, notes, and snippets.

@mjf
Last active January 17, 2025 11:49
Show Gist options
  • Save mjf/c63f06b7e90a88fa20c8177c22e93f2e to your computer and use it in GitHub Desktop.
Save mjf/c63f06b7e90a88fa20c8177c22e93f2e to your computer and use it in GitHub Desktop.
Radix functions in Lua (including example usage and a test suite)
local function from_radix(digits, base)
local ip = 0
local fp = 0
local dp = nil
for i, v in ipairs(digits) do
if v == "." then
dp = i
break
end
if type(v) ~= "number" then
error("Invalid digit in input")
end
if v < 0 or v >= base then
error("Digit out of range for given base")
end
end
if dp then
for i = 1, dp - 1 do
ip = ip * base + digits[i]
end
for i = dp + 1, #digits do
fp = fp + digits[i] / (base ^ (i - dp))
end
else
for i = 1, #digits do
ip = ip * base + digits[i]
end
end
return ip + fp
end
local function to_radix(x, base, frac_digits)
if base < 2 then
error("Base must be at least 2")
end
if frac_digits and frac_digits < 0 then
error("Number of fractional digits must be non-negative")
end
local result = {}
local ip = math.floor(x)
local fp = x - ip
repeat
table.insert(result, 1, ip % base)
ip = math.floor(ip / base)
until ip == 0
if frac_digits and frac_digits > 0 then
table.insert(result, ".")
for _ = 1, frac_digits do
fp = fp * base
local digit = math.floor(fp)
table.insert(result, digit)
fp = fp - digit
end
end
return result
end
local function clock_from_moment(tee)
local seconds = math.fmod(tee, 86400)
if seconds < 0 then
seconds = seconds + 86400
end
local hours = math.floor(seconds / 3600)
seconds = math.fmod(seconds, 3600)
local minutes = math.floor(seconds / 60)
seconds = math.fmod(seconds, 60)
return {hours, minutes, math.floor(seconds)}
end
local function time_from_clock(hms)
if #hms ~= 3 then
error("Clock time must have 3 elements: {hours, minutes, seconds}")
end
for _, v in ipairs(hms) do
if type(v) ~= "number" then
error("Clock time elements must be numbers")
end
end
if hms[1] < 0 or hms[1] > 23 then
error("Hours must be between 0 and 23")
end
if hms[2] < 0 or hms[2] > 59 then
error("Minutes must be between 0 and 59")
end
if hms[3] < 0 or hms[3] > 59 then
error("Seconds must be between 0 and 59")
end
return ((hms[1] * 3600) + (hms[2] * 60) + hms[3]) / 86400
end
local function table_tostring(t)
if type(t) ~= "table" then
return tostring(t)
end
local s = "{"
for k, v in pairs(t) do
s = s .. "[" .. tostring(k) .. "] = " .. tostring(v) .. ", "
end
if #s > 1 then
s = s:sub(1, #s - 2)
end
return s .. "}"
end
local function float_equal(a, b, tolerance)
tolerance = tolerance or 1e-9
return math.abs(a - b) < tolerance
end
local function assert_eq(actual, expected, message)
local success = false
if type(actual) == type(expected) then
if type(actual) == "table" then
local actual_count = 0
local expected_count = 0
for _ in pairs(actual) do
actual_count = actual_count + 1
end
for _ in pairs(expected) do
expected_count = expected_count + 1
end
if actual_count == expected_count then
success = true
for k, exp_v in pairs(expected) do
local v = actual[k]
if type(v) == "number" and type(exp_v) == "number" then
if not float_equal(v, exp_v) then
success = false
break
end
elseif tostring(v) ~= tostring(exp_v) then
success = false
break
end
end
end
elseif type(actual) == "number" then
success = float_equal(actual, expected)
else
success = actual == expected
end
end
if not success then
error(
"Assertion failed: " .. message .. "\n" ..
"Expected: " .. table_tostring(expected) .. "\n" ..
"Got: " .. table_tostring(actual)
)
end
end
local tests = {
{function()
return from_radix({1, 0, 1, ".", 1, 1}, 2)
end, 5.75, "Binary 101.11"},
{function()
return to_radix(5.75, 2, 5)
end, {1, 0, 1, ".", 1, 1, 0, 0, 0}, "Decimal 5.75 to binary"},
{function()
return to_radix(12345, 16)
end, {3, 0, 3, 9}, "Decimal 12345 to hex"},
{function()
return to_radix(0.3, 2, 10)
end, {0, ".", 0, 1, 0, 0, 1, 1, 0, 0, 1, 1}, "Decimal 0.3 to binary"},
{function()
return clock_from_moment(3723)
end, {1, 2, 3}, "3723 seconds to clock time"},
{function()
return clock_from_moment(0)
end, {0, 0, 0}, "0 seconds to clock time"},
{function()
return clock_from_moment(86400)
end, {0, 0, 0}, "86400 seconds to clock time"},
{function()
return clock_from_moment(86461)
end, {0, 1, 1}, "86461 seconds to clock time"},
{function()
return clock_from_moment(123456)
end, {10, 17, 36}, "123456 seconds to clock time"},
{function()
return time_from_clock({1, 2, 3})
end, 0.043090277777778, "Clock time 1:2:3 to fraction of day"},
{function()
return time_from_clock({0, 0, 0})
end, 0, "Clock time 0:0:0 to fraction of day"},
{function()
return time_from_clock({12, 0, 0})
end, 0.5, "Clock time 12:0:0 to fraction of day"},
{function()
return time_from_clock({23, 59, 59})
end, 0.99998842592593, "Clock time 23:59:59 to fraction of day"},
{function()
return time_from_clock({1, 10, 56})
end, 0.049259259259259, "Clock time 1:10:56 to fraction of day"}
}
local function run_tests()
local passed = 0
local total = #tests
for i, test in ipairs(tests) do
local description = test[3]
local actual = test[1]()
local expected = test[2]
assert_eq(actual, expected, description)
print("Test " .. i .. ": " .. description .. " - PASSED")
passed = passed + 1
end
print("\nPassed " .. passed .. " of " .. total .. " tests.")
end
run_tests()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment