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 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
|
|
|
|
--------------------------------------------------------------------------------
|
2017-01-16 15:10:10 +00:00
|
|
|
|
|
2017-04-03 18:01:36 +00:00
|
|
|
|
local SPECIAL_NUMBER = {
|
|
|
|
|
-- x = ∞
|
2017-06-15 14:30:34 +00:00
|
|
|
|
{ est = function (a) return math.huge end,
|
|
|
|
|
real = function (a) return math.huge end,
|
|
|
|
|
repr = function (a) return '1/0' end,
|
2017-04-03 18:01:36 +00:00
|
|
|
|
},
|
2017-04-12 10:46:53 +00:00
|
|
|
|
-- x = a/b
|
|
|
|
|
{ est = calculate_fraction,
|
|
|
|
|
real = function (a, b) return b ~= 1 and (a/b) end,
|
2017-05-25 20:53:02 +00:00
|
|
|
|
repr = function (a, b) return a..'/'..b end,
|
2017-04-03 18:01:36 +00:00
|
|
|
|
},
|
|
|
|
|
-- 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,
|
2017-06-15 14:30:34 +00:00
|
|
|
|
real = function (a) return a >= 0 and 1/math.sqrt(a) end,
|
|
|
|
|
repr = function (a) return ('1/math.sqrt(%.0f)'):format(a) end,
|
2017-04-03 18:01:36 +00:00
|
|
|
|
},
|
|
|
|
|
-- x = lg a
|
|
|
|
|
{ est = function (n) return math.exp(n) end,
|
2017-06-15 14:30:34 +00:00
|
|
|
|
real = function (a) return a >= 0 and math.log(a) end,
|
|
|
|
|
repr = function (a) return ('math.log(%.0f)'):format(a) end,
|
2017-04-03 18:01:36 +00:00
|
|
|
|
},
|
|
|
|
|
-- x = ℯ^a
|
|
|
|
|
{ est = function (n) return math.log(n) end,
|
|
|
|
|
real = function (a) return math.exp(a) end,
|
2017-06-15 14:30:34 +00:00
|
|
|
|
repr = function (a) return ('math.exp(%.0f)'):format(a) end,
|
2017-04-03 18:01:36 +00:00
|
|
|
|
},
|
|
|
|
|
-- 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,
|
2017-06-15 14:30:34 +00:00
|
|
|
|
real = function (a) return a >= 0 and math.sqrt(a) end,
|
|
|
|
|
repr = function (a) return ('math.sqrt(%.0f)'):format(a) end,
|
2017-04-03 18:01:36 +00:00
|
|
|
|
},
|
|
|
|
|
}
|
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-06-15 14:30:34 +00:00
|
|
|
|
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)
|
2017-01-16 15:10:10 +00:00
|
|
|
|
end
|
|
|
|
|
|
2017-04-03 18:01:36 +00:00
|
|
|
|
-- Finding the shortest
|
2017-04-12 10:46:53 +00:00
|
|
|
|
local shortest, length = nil, math.huge
|
2017-04-03 18:01:36 +00:00
|
|
|
|
local function alternative_repr (repr)
|
2017-06-15 14:30:34 +00:00
|
|
|
|
if #repr < length then shortest, length = repr, #repr 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
|
2017-04-12 10:46:53 +00:00
|
|
|
|
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
|
2017-06-09 14:57:21 +00:00
|
|
|
|
local num = special_number_tests.real(unpack(a))
|
|
|
|
|
if num == n then
|
2017-06-15 14:30:34 +00:00
|
|
|
|
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))
|
2017-06-09 14:57:21 +00:00
|
|
|
|
if math.abs(num - n) <= math.abs( native_precise - n ) then
|
|
|
|
|
alternative_repr( repr )
|
|
|
|
|
end
|
2017-04-03 18:01:36 +00:00
|
|
|
|
end
|
2017-01-16 15:10:10 +00:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2017-04-03 18:01:36 +00:00
|
|
|
|
-- Maybe it's a decimal number?
|
2017-06-15 14:30:34 +00:00
|
|
|
|
alternative_repr( tostring(n):gsub('([^e]+)e%+?(%-?[^e]*)', 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
|
|
|
|
|
|
2017-06-15 14:30:34 +00:00
|
|
|
|
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
|
|
|
|
|
|
2017-06-05 21:24:24 +00:00
|
|
|
|
return function (value, depth, l)
|
2017-06-15 14:30:34 +00:00
|
|
|
|
-- Formats the number nicely. If depth is 0 and we have some space for extra
|
|
|
|
|
-- info, we give some tidbits, to help investigation.
|
|
|
|
|
|
2017-06-05 21:24:24 +00:00
|
|
|
|
assert(type(value) == 'number')
|
|
|
|
|
assert(type(depth) == 'number' and type(l) == 'table')
|
2017-06-15 14:30:34 +00:00
|
|
|
|
|
|
|
|
|
-- 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
|
2017-01-16 15:10:10 +00:00
|
|
|
|
end
|