1
0
pretty/number.lua

177 lines
7.0 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" -- 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')
l[#l+1] = format_num(value, CAN_USE_SHORTHAND and l.options.math_shorthand, l.options.soft_numbers)
end