Last active
          January 17, 2025 11:49 
        
      - 
      
 - 
        
Save mjf/c63f06b7e90a88fa20c8177c22e93f2e to your computer and use it in GitHub Desktop.  
Revisions
- 
        
mjf revised this gist
Jan 17, 2025 . 1 changed file with 0 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -140,7 +140,6 @@ local function assert_eq(actual, expected, message) success = actual == expected end end if not success then error( "Assertion failed: " .. message .. "\n" ..  - 
        
mjf created this gist
Jan 17, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,214 @@ 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()