1
0

Added number.lua file, to allow more informative representations of numbers.

This commit is contained in:
Jon Michael Aanes 2017-01-16 16:10:10 +01:00
parent b405afeae8
commit cad8258a6f
3 changed files with 258 additions and 18 deletions

118
number.lua Normal file
View File

@ -0,0 +1,118 @@
-- 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 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 MATH_SHORTHAND = {
-- Only used if options.math_shorthand = true
['0/0'] = 'nan',
['1/0'] = '',
['-1/0'] = '-∞',
['math.pi'] = 'π',
['math.exp(1)'] = 'e',
}
--------------------------------------------------------------------------------
-- Generate special number table
-- TODO: Restructure this.
local SPECIAL_NUMBER = {}
do
SPECIAL_NUMBER[ 1/0] = '1/0'
SPECIAL_NUMBER[math.pi] = 'math.pi'
SPECIAL_NUMBER[math.exp(1)] = 'math.exp(1)'
for i = -10, -6 do
SPECIAL_NUMBER[math.pow(10, i)] = '10^'..i
end
for i = -15, -7 do
SPECIAL_NUMBER[math.pow( 2, i)] = '2^'..i
end
for i = 5, 308 do
SPECIAL_NUMBER[math.pow(10, i)] = '10^'..i
end
for i = 17, 1023 do
SPECIAL_NUMBER[math.pow( 2, i) ] = '2^'..i
--SPECIAL_NUMBER[math.pow( 2, i) - 1] = '2^'..i..'-1'
--SPECIAL_NUMBER[math.pow( 2, i) + 1] = '2^'..i..'+1'
end
for i = 9, 170 do
SPECIAL_NUMBER[factorial(i)] = i..'!'
end
for i = 1, 100 do
local sqrt = math.sqrt(i)
if not is_integer(sqrt) then
SPECIAL_NUMBER[1/sqrt] = '1/math.sqrt('..i..')'
SPECIAL_NUMBER[sqrt] = 'math.sqrt('..i..')'
end
end
for i = 1, 100 do
local log = math.log(i)
if not is_integer(log) then
SPECIAL_NUMBER[log] = 'math.log('..i..')'
end
end
end
--------------------------------------------------------------------------------
local function format_number (n)
-- Returns a string of n formatted as to allow easy back into a lua interpreter.
if n ~= n then return '0/0'
elseif n < 0 then return '-' .. format_number(-n)
elseif SPECIAL_NUMBER[n] then return SPECIAL_NUMBER[n]
end
-- Maybe it's an integer?
if is_integer(n) then return tostring(n) end
-- Maybe it's a fraction?
local numerator, denumberator = calculate_fraction(n)
if numerator then
return format_number(numerator)..'/'..format_number(denumberator)
end
return ( tostring(n):gsub('e%+?', '*10^') ) -- Parantheses to return first result.
end
return function (value, options, depth, l)
l[#l+1] = format_number(value)
if options.math_shorthand then
l[#l] = MATH_SHORTHAND[ l[#l] ]
end
end

View File

@ -1,13 +1,17 @@
-- Ensure loading library, if it exists, no matter where pretty.lua was loaded from. -- Ensure loading library, if it exists, no matter where pretty.lua was loaded from.
-- Load the library component -- Load the library component
local LIBRARY local LIBRARY, format_number
do do
local thispath = ... and select('1', ...):match('.+%.') or '' local thispath = ... and select('1', ...):match('.+%.') or ''
-- Load library info
local was_loaded, library = pcall(require, thispath..'library') local was_loaded, library = pcall(require, thispath..'library')
LIBRARY = was_loaded and library or {} LIBRARY = was_loaded and library or {}
-- Load number formatting
was_loaded, format_number = pcall(require, thispath..'number')
end end
-- --
@ -114,10 +118,10 @@ local function compare_key_value_pairs (a, b)
local type_value_a, type_value_b = type(a[2]), type(b[2]) local type_value_a, type_value_b = type(a[2]), type(b[2])
-- Tons of compare -- Tons of compare
if (type_key_a ~= 'string' or type_key_b ~= 'string') then if (type_key_a == 'string' and type_key_b == 'string') then
return TYPE_SORT_ORDER[type_key_a] < TYPE_SORT_ORDER[type_key_b]
elseif (type_value_a == type_value_b) then
return alphanum_compare_strings(a[1], b[1]) return alphanum_compare_strings(a[1], b[1])
elseif (type_key_a == 'number' and type_key_b == 'number') then
return a[1] < b[1]
else else
return TYPE_SORT_ORDER[type_value_a] < TYPE_SORT_ORDER[type_value_b] return TYPE_SORT_ORDER[type_value_a] < TYPE_SORT_ORDER[type_value_b]
end end
@ -470,10 +474,8 @@ local function format_string (str, options, depth, l)
local single_quote_index = str:find('\'') local single_quote_index = str:find('\'')
local double_quote_index = str:find('\"') local double_quote_index = str:find('\"')
-- ...
local chance_of_longform = is_long_string and (newline_or_tab_index <= NR_CHARS_IN_LONG_STRING) or double_quote_index and single_quote_index local chance_of_longform = is_long_string and ((newline_or_tab_index or math.huge) <= NR_CHARS_IN_LONG_STRING) or double_quote_index and single_quote_index
local cut_string_index = options.cut_strings and (is_long_string or chance_of_longform) local cut_string_index = options.cut_strings and (is_long_string or chance_of_longform)
and math.min(NR_CHARS_IN_LONG_STRING - 3, newline_or_tab_index or 1/0, double_quote_index or 1/0, single_quote_index or 1/0) and math.min(NR_CHARS_IN_LONG_STRING - 3, newline_or_tab_index or 1/0, double_quote_index or 1/0, single_quote_index or 1/0)
@ -504,12 +506,14 @@ local function format_string (str, options, depth, l)
l[#l+1] = right l[#l+1] = right
end end
local function format_number (value, options, depth, l) if not format_number then
local shorthand = options.math_shorthand function format_number (value, options, depth, l)
if value ~= value then l[#l+1] = shorthand and 'nan' or '0/0' local shorthand = options.math_shorthand
elseif value == 1/0 then l[#l+1] = shorthand and 'inf' or '1/0' if value ~= value then l[#l+1] = shorthand and 'nan' or '0/0'
elseif value == -1/0 then l[#l+1] = shorthand and '-inf' or '-1/0' elseif value == 1/0 then l[#l+1] = shorthand and 'inf' or '1/0'
else l[#l+1] = tostring(value) elseif value == -1/0 then l[#l+1] = shorthand and '-inf' or '-1/0'
else l[#l+1] = tostring(value)
end
end end
end end

View File

@ -131,6 +131,67 @@ format_test {
expect = '-2000', expect = '-2000',
} }
--------------------------------------------------------------------------------
-- Numbers, Other formats
format_test {
input = 10^5,
expect = '10^5',
}
format_test {
input = 10^-6,
expect = '10^-6',
}
format_test {
input = 2^17,
expect = '2^17',
}
format_test {
input = 2^-7,
expect = '2^-7',
}
format_test {
input = 1/3,
expect = '1/3',
}
format_test {
input = 9/17,
expect = '9/17',
}
format_test {
input = math.sqrt(2),
expect = 'math.sqrt(2)',
}
format_test {
input = 1/math.sqrt(2),
expect = '1/math.sqrt(2)',
}
format_test {
input = math.log(2),
expect = 'math.log(2)',
}
--------------------------------------------------------------------------------
-- Numbers, Constants
format_test {
input = math.pi,
expect = 'math.pi',
}
format_test {
input = math.exp(1),
expect = 'math.exp(1)',
}
format_test { format_test {
input = 1/0, -- Same as math.huge input = 1/0, -- Same as math.huge
expect = '1/0', expect = '1/0',
@ -149,13 +210,13 @@ format_test {
format_test { format_test {
input = 1/0, -- Same as math.huge input = 1/0, -- Same as math.huge
options = { math_shorthand = true }, options = { math_shorthand = true },
expect = 'inf', expect = '',
} }
format_test { format_test {
input = -1/0, -- Same as -math.huge input = -1/0, -- Same as -math.huge
options = { math_shorthand = true }, options = { math_shorthand = true },
expect = '-inf', expect = '-',
} }
format_test { format_test {
@ -164,6 +225,23 @@ format_test {
expect = 'nan', expect = 'nan',
} }
format_test {
input = math.pi,
options = { math_shorthand = true },
expect = 'π',
}
format_test {
input = math.exp(1),
options = { math_shorthand = true },
expect = 'e',
}
format_test {
input = { 1/2, 1/3, 1/4, 1/5 },
expect = '{ 1/2, 1/3, 1/4, 1/5 }',
}
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Primitive types -- Primitive types
@ -488,7 +566,6 @@ format_test { -- Can include very small tables
expect = '{ { 1 }, { 2 }, { 3 } }', expect = '{ { 1 }, { 2 }, { 3 } }',
} }
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Multi-line tables -- Multi-line tables
@ -573,6 +650,47 @@ format_test {
expect = '{\n\t{ a = \'hello\', b = \'hi\' },\n\t{ a = \'hi\', b = \'hello\' }\n}', expect = '{\n\t{ a = \'hello\', b = \'hi\' },\n\t{ a = \'hi\', b = \'hello\' }\n}',
} }
--------------------------------------------------------------------------------
-- Table Sorting
format_test {
input = { 'c', 'b', 'a' },
expect = '{ \'c\', \'b\', \'a\' }',
}
format_test {
input = { a = 1, a1 = 0 },
expect = '{ a = 1, a1 = 0 }',
}
format_test {
input = { a10 = 1, a1 = 0 },
expect = '{ a1 = 0, a10 = 1 }',
}
format_test {
input = { a00 = 0, a1 = 1 },
expect = '{ a00 = 0, a1 = 1 }',
}
format_test {
input = { a = {}, b = true },
expect = '{ b = true, a = {} }',
}
format_test {
input = { a = {}, b = true, b1 = false },
expect = '{ b = true, b1 = false, a = {} }',
}
format_test {
input = { {}, true, false, 5 },
expect = '{ {}, true, false, 5 }',
}
-- TODO: Add more tests for sorting.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- CDATA -- CDATA