-- Constants local MAXIMUM_INT = 2^53 -- The maximum double for where all integers can be represented exactly. local MAXIMUM_ZERO = 10^-7 -- Used when attempting to determine fraction. Anything below is counted as 0. -------------------------------------------------------------------------------- -- Util local function calculate_fraction (n) -- Returns x and y such that x/y = n. If none could be found, returns nil. local a, b = 1, n % 1 while MAXIMUM_ZERO < b and a <= MAXIMUM_INT do local r = math.pow(b, -1) a, b = a * r, r % 1 -- Invariant: n * a / a = n end -- Check the values make sense. local numerator, denumberator = math.floor(n * a), math.floor(a) if numerator / denumberator == n then return numerator, denumberator end end -------------------------------------------------------------------------------- local SPECIAL_NUMBER = { -- x = ∞ { est = function (a) return math.huge end, real = function (a) return math.huge end, repr = function (a) return '1/0' end, }, -- x = a/b { est = calculate_fraction, real = function (a, b) return b ~= 1 and (a/b) end, repr = function (a, b) return a..'/'..b end, }, -- x = 2^a { est = function (n) return math.log(n)/math.log(2) end, real = function (a) return 2^a end, repr = function (a) return '2^'..a end, }, -- x = 10^a { est = function (n) return math.log(n)/math.log(10) end, real = function (a) return 10^a end, repr = function (a) return '10^'..a end, }, -- x = 1/√a { est = function (n) return 1/(n^2) end, real = function (a) return a >= 0 and 1/math.sqrt(a) end, repr = function (a) return ('1/math.sqrt(%.0f)'):format(a) end, }, -- x = lg a { est = function (n) return math.exp(n) end, real = function (a) return a >= 0 and math.log(a) end, repr = function (a) return ('math.log(%.0f)'):format(a) end, }, -- x = ℯ^a { est = function (n) return math.log(n) end, real = function (a) return math.exp(a) end, repr = function (a) return ('math.exp(%.0f)'):format(a) end, }, -- x = aπ { est = function (n) return n/math.pi end, real = function (a) return a*math.pi end, repr = function (a) return a == 1 and 'math.pi' or a..'*math.pi' end, }, -- x = sqrt(a) { est = function (n) return n^2 end, real = function (a) return a >= 0 and math.sqrt(a) end, repr = function (a) return ('math.sqrt(%.0f)'):format(a) end, }, } -------------------------------------------------------------------------------- local function format_soft_num (n) assert(type(n) == 'number') if n ~= n then return '0/0' elseif n == 0 then return '0' elseif n < 0 then return '-' .. format_soft_num(-n) end -- Finding the shortest local shortest, length = nil, math.huge local function alternative_repr (repr) if #repr < length then shortest, length = repr, #repr end end -- Maybe it's a "special" number? for _, special_number_tests in pairs(SPECIAL_NUMBER) do local a = { special_number_tests.est(n) } if a[1] then for i = 1, #a do a[i] = math.floor(a[i] + 0.5) end local num = special_number_tests.real(unpack(a)) if num == n then alternative_repr( special_number_tests.repr(unpack(a)) ) elseif num then local repr = special_number_tests.repr(unpack(a)) local native_precise = tonumber(('%'..#repr..'f'):format(n)) if math.abs(num - n) <= math.abs( native_precise - n ) then alternative_repr( repr ) end end end end -- Maybe it's a decimal number? alternative_repr( tostring(n):gsub('([^e]+)e%+?(%-?[^e]*)', function(a, b) return (a == '1' and '' or a..'*')..'10^'..b end)) -- Well, this is not a pretty number! return shortest end local function format_hard_num (n) assert(type(n) == 'number') -- All the fun special cases if n ~= n then return '0/0' elseif n == 0 then return '0' elseif n == math.huge then return '1/0' elseif n == -math.huge then return '-1/0' end -- Now for the serious part. for i = 0, 99 do local repr = ('%.'..i..'f'):format(n) local num = tonumber(repr) if num == n then return repr end end assert(false) end return function (value, depth, l) -- Formats the number nicely. If depth is 0 and we have some space for extra -- info, we give some tidbits, to help investigation. assert(type(value) == 'number') assert(type(depth) == 'number' and type(l) == 'table') -- First format a "soft" version. This number is not guarenteed to accurate. -- It's purpose is to give a general idea of the value. l[#l+1] = format_soft_num(value) -- If we have space for it, format a "hard" value, also. This number is as -- short as possible, while evaluating precisely to the value of the number. if depth == 0 then local hard_repr = format_hard_num(value) if l[#l] ~= hard_repr then l[#l+1] = ' -- Approx: ' l[#l+1] = hard_repr end -- TODO: Add extra information. I don't really know what is useful? -- Prime factorization is fun, but useless unless people are doing -- cryptography or general number theory. -- Bit pattern is maybe too lowlevel. end end