2017-01-16 15:10:10 +00:00
|
|
|
|
|
|
|
|
|
-- 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
|
|
|
|
|
|
2017-04-03 18:01:36 +00:00
|
|
|
|
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
|
|
|
|
|
|
2017-01-16 15:10:10 +00:00
|
|
|
|
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
|
|
|
|
|
|
2017-04-03 18:01:36 +00:00
|
|
|
|
local UNICODE_SUPERSCRIPT = {
|
|
|
|
|
['0'] = '⁰', ['1'] = '¹', ['2'] = '²', ['3'] = '³', ['4'] = '⁴',
|
|
|
|
|
['5'] = '⁵', ['6'] = '⁶', ['7'] = '⁷', ['8'] = '⁸', ['9'] = '⁹',
|
|
|
|
|
['-'] = '⁻', ['+'] = '⁺', ['='] = '⁼', ['('] = '⁽', [')'] = '⁾',
|
2017-01-16 15:10:10 +00:00
|
|
|
|
}
|
2017-04-03 18:01:36 +00:00
|
|
|
|
local function to_superscript (n)
|
|
|
|
|
return tostring(n):gsub('.', function (a) return UNICODE_SUPERSCRIPT[a] end)
|
|
|
|
|
end
|
2017-01-16 15:10:10 +00:00
|
|
|
|
|
2017-04-03 18:01:36 +00:00
|
|
|
|
local UNICODE_CHAR_PATTERN = '[\01-\127\192-\255][\128-\191]*'
|
|
|
|
|
local function unicode_len (str)
|
|
|
|
|
local len = 0
|
|
|
|
|
for char in str:gmatch(UNICODE_CHAR_PATTERN) do len = len + 1 end
|
|
|
|
|
return len
|
|
|
|
|
end
|
2017-01-16 15:10:10 +00:00
|
|
|
|
|
2017-04-03 18:01:36 +00:00
|
|
|
|
--------------------------------------------------------------------------------
|
2017-01-16 15:10:10 +00:00
|
|
|
|
|
2017-04-03 18:01:36 +00:00
|
|
|
|
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,
|
|
|
|
|
},
|
|
|
|
|
-- 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'..to_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'..to_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/√'..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 'ℯ'..to_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 '√'..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,
|
|
|
|
|
},
|
|
|
|
|
}
|
2017-01-16 15:10:10 +00:00
|
|
|
|
|
2017-04-03 18:01:36 +00:00
|
|
|
|
--------------------------------------------------------------------------------
|
2017-01-16 15:10:10 +00:00
|
|
|
|
|
2017-04-03 18:01:36 +00:00
|
|
|
|
local LONG_ASS_CONSTANT = '___________________________________________________'
|
2017-01-16 15:10:10 +00:00
|
|
|
|
|
2017-04-03 18:01:36 +00:00
|
|
|
|
local function format_number (n, shorthand)
|
|
|
|
|
if n ~= n then return shorthand and 'NaN' or '0/0'
|
|
|
|
|
elseif n < 0 then return '-' .. format_number(-n, shorthand)
|
2017-01-16 15:10:10 +00:00
|
|
|
|
end
|
|
|
|
|
|
2017-04-03 18:01:36 +00:00
|
|
|
|
-- Finding the shortest
|
|
|
|
|
local shortest, length = LONG_ASS_CONSTANT, unicode_len(LONG_ASS_CONSTANT)
|
|
|
|
|
local function alternative_repr (repr)
|
|
|
|
|
local repr_len = unicode_len(repr)
|
|
|
|
|
if repr_len < length then shortest, length = repr, repr_len end
|
2017-01-16 15:10:10 +00:00
|
|
|
|
end
|
|
|
|
|
|
2017-04-03 18:01:36 +00:00
|
|
|
|
-- Maybe it's a "special" number?
|
|
|
|
|
for _, special_number_tests in pairs(SPECIAL_NUMBER) do
|
|
|
|
|
local a = special_number_tests.est(n)
|
|
|
|
|
if a then
|
|
|
|
|
local a_near = math.floor(a + 0.5)
|
|
|
|
|
if n == special_number_tests.real(a_near) then
|
|
|
|
|
alternative_repr( special_number_tests[shorthand and 'short' or 'repr'](a_near) )
|
|
|
|
|
end
|
2017-01-16 15:10:10 +00:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2017-04-03 18:01:36 +00:00
|
|
|
|
-- Maybe it's a fractional number?
|
|
|
|
|
do
|
|
|
|
|
local numerator, denumberator = calculate_fraction(n)
|
|
|
|
|
if numerator and denumberator ~= 1 then
|
|
|
|
|
alternative_repr( format_number(numerator)..'/'..format_number(denumberator) )
|
2017-01-16 15:10:10 +00:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2017-04-03 18:01:36 +00:00
|
|
|
|
-- 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))
|
2017-01-16 15:10:10 +00:00
|
|
|
|
|
2017-04-03 18:01:36 +00:00
|
|
|
|
-- Well, this is not a pretty number!
|
|
|
|
|
return shortest
|
2017-01-16 15:10:10 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
return function (value, options, depth, l)
|
2017-04-03 18:01:36 +00:00
|
|
|
|
l[#l+1] = format_number(value, options.math_shorthand)
|
2017-01-16 15:10:10 +00:00
|
|
|
|
end
|