1
0
pretty/number.lua

175 lines
6.6 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.

-- 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 UNICODE_SUPERSCRIPT = {
['0'] = '', ['1'] = '¹', ['2'] = '²', ['3'] = '³', ['4'] = '',
['5'] = '', ['6'] = '', ['7'] = '', ['8'] = '', ['9'] = '',
['-'] = '', ['+'] = '', ['='] = '', ['('] = '', [')'] = '',
}
local function to_superscript (n)
return tostring(n):gsub('.', function (a) return UNICODE_SUPERSCRIPT[a] end)
end
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
--------------------------------------------------------------------------------
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,
},
}
--------------------------------------------------------------------------------
local LONG_ASS_CONSTANT = '___________________________________________________'
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)
end
-- 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
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 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
end
end
-- 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) )
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_number(value, options.math_shorthand)
end