1
0
pretty/number.lua

159 lines
6.1 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

local utf8 = require "utf8"
-- 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 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,
},
-- x = a/b
{ est = calculate_fraction,
real = function (a, b) return b ~= 1 and (a/b) end,
repr = function (a, b) return format_num(a)..'/'..format_num(b) end,
short = function (a, b) return format_num(a)..'/'..format_num(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,
},
-- 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,
},
}
--------------------------------------------------------------------------------
function format_num (n, shorthand)
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
if n == special_number_tests.real(unpack(a)) then
alternative_repr( special_number_tests[shorthand and 'short' or 'repr'](unpack(a)) )
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, options, depth, l)
l[#l+1] = format_num(value, options.math_shorthand)
end