local utf8 = require "utf8" -- FIXME: I don't really like this. Either have a local copy of the files, or remove the functionallity requiring it. -- Make sure we imported the correct library. local CAN_USE_SHORTHAND = not not utf8.width if not CAN_USE_SHORTHAND then utf8.width = function(a) return #a end end -- 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 vulgar_fraction (a, b) -- Returns a string representing the fraction a/b, with unicode characters. return utf8.superscript(a) .. '⁄' .. utf8.subscript(b) end local function is_integer (n) -- Predicate. Neither +inf, -inf nor nan are integer. return n == math.floor(n) and n ~= 1/0 and n ~= -1/0 end local function factorial (n) -- Returns the factorial of n. -- That is n! = n * (n-1) * (n-2) * ... * 1 local a = 1 for i = 2, n do a = a * i end return a end local function inverse_factorial (n) -- Returns the inverse factorial of n. -- That is some m, where n = m! local a, i = 1, 1 while a < n do i = i + 1 a = a * i end return (a == n) and i or nil end 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 format_num local SPECIAL_NUMBER = { -- x = ∞ { est = function (...) return ... end, real = function (a) return math.huge end, repr = function (a) return '1/0' end, short = function (a) return '∞' end, }, -- a = x { est = function (n) return n end, real = function (a) return a end, repr = function (a) return ('%i'):format(a) end, short = function (a) return ('%i'):format(a) 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, short = function (a, b) return a..'/'..b end, }, -- Factorial { est = function (n) return inverse_factorial(n) end, real = function (a) return factorial(a) end, repr = function (a) return ('fac(%i)'):format(a) end, short = function (a) return a..'!' 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, short = function (a) return '2'..utf8.superscript(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, short = function (a) return '10'..utf8.superscript(a) end, }, -- x = 1/√a { est = function (n) return 1/(n^2) end, real = function (a) return 1/math.sqrt(a) end, repr = function (a) return ('1/math.sqrt(%i)'):format(a) end, short = function (a) return '1/√'..utf8.overline(a) end, }, -- x = lg a { est = function (n) return math.exp(n) end, real = function (a) return math.log(a) end, repr = function (a) return ('math.log(%i)'):format(a) end, short = function (a) return ('lg(%i)'):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(%i)'):format(a) end, short = function (a) return a == 1 and 'ℯ' or 'ℯ'..utf8.superscript(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, short = function (a) return (a == 1 and '' or a)..'π' end, }, -- x = sqrt(a) { est = function (n) return n^2 end, real = function (a) return math.sqrt(a) end, repr = function (a) return ('math.sqrt(%i)'):format(a) end, short = function (a) return '√'..utf8.overline(a) end, }, } -------------------------------------------------------------------------------- function format_num (n, shorthand, soft_numbers) if n ~= n then return shorthand and 'NaN' or '0/0' elseif n < 0 then return '-' .. format_num(-n, shorthand) end -- Finding the shortest local shortest, length = nil, math.huge local function alternative_repr (repr) local repr_len = utf8.width(repr) if repr_len < length then shortest, length = repr, repr_len 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[shorthand and 'short' or 'repr'](unpack(a)) ) elseif num and soft_numbers then local repr = special_number_tests[shorthand and 'short' or 'repr'](unpack(a)) local native_precise = tonumber(('%'..utf8.width(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%+?(%-?)0+', function(a, b) return (a == '1' and '' or a..'*')..'10^'..b end)) -- Well, this is not a pretty number! return shortest end return function (value, depth, l) assert(type(value) == 'number') assert(type(depth) == 'number' and type(l) == 'table') -- TODO: Add support for more relaxed representations. l[#l+1] = format_num(value, CAN_USE_SHORTHAND and l.options.math_shorthand, l.options.soft_numbers) end