1
0
pretty/number.lua

161 lines
5.7 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 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 SPECIAL_NUMBER = {
-- x = ∞
{ est = function (a) return math.huge end,
real = function (a) return math.huge end,
repr = function (a) return '1/0' 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,
},
-- 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,
},
-- 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,
},
-- x = 1/√a
{ est = function (n) return 1/(n^2) end,
real = function (a) return a >= 0 and 1/math.sqrt(a) end,
repr = function (a) return ('1/math.sqrt(%.0f)'):format(a) end,
},
-- x = lg a
{ est = function (n) return math.exp(n) end,
real = function (a) return a >= 0 and math.log(a) end,
repr = function (a) return ('math.log(%.0f)'):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(%.0f)'):format(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,
},
-- x = sqrt(a)
{ est = function (n) return n^2 end,
real = function (a) return a >= 0 and math.sqrt(a) end,
repr = function (a) return ('math.sqrt(%.0f)'):format(a) end,
},
}
--------------------------------------------------------------------------------
local function format_soft_num (n)
assert(type(n) == 'number')
if n ~= n then return '0/0'
elseif n == 0 then return '0'
elseif n < 0 then return '-' .. format_soft_num(-n)
end
-- Finding the shortest
local shortest, length = nil, math.huge
local function alternative_repr (repr)
if #repr < length then shortest, length = repr, #repr 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.repr(unpack(a)) )
elseif num then
local repr = special_number_tests.repr(unpack(a))
local native_precise = tonumber(('%'..#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%+?(%-?[^e]*)', function(a, b) return (a == '1' and '' or a..'*')..'10^'..b end))
-- Well, this is not a pretty number!
return shortest
end
local function format_hard_num (n)
assert(type(n) == 'number')
-- All the fun special cases
if n ~= n then return '0/0'
elseif n == 0 then return '0'
elseif n == math.huge then return '1/0'
elseif n == -math.huge then return '-1/0'
end
-- Now for the serious part.
for i = 0, 99 do
local repr = ('%.'..i..'f'):format(n)
local num = tonumber(repr)
if num == n then return repr end
end
assert(false)
end
return function (value, depth, l)
-- Formats the number nicely. If depth is 0 and we have some space for extra
-- info, we give some tidbits, to help investigation.
assert(type(value) == 'number')
assert(type(depth) == 'number' and type(l) == 'table')
-- First format a "soft" version. This number is not guarenteed to accurate.
-- It's purpose is to give a general idea of the value.
l[#l+1] = format_soft_num(value)
-- If we have space for it, format a "hard" value, also. This number is as
-- short as possible, while evaluating precisely to the value of the number.
if depth == 0 then
local hard_repr = format_hard_num(value)
if l[#l] ~= hard_repr then
l[#l+1] = ' -- Approx: '
l[#l+1] = hard_repr
end
-- TODO: Add extra information. I don't really know what is useful?
-- Prime factorization is fun, but useless unless people are doing
-- cryptography or general number theory.
-- Bit pattern is maybe too lowlevel.
end
end