From cad8258a6fbf3141966c444a04841ae720765355 Mon Sep 17 00:00:00 2001 From: Jon Michael Aanes Date: Mon, 16 Jan 2017 16:10:10 +0100 Subject: [PATCH] Added `number.lua` file, to allow more informative representations of numbers. --- number.lua | 118 ++++++++++++++++++++++++++++++++++++++++ pretty.lua | 34 ++++++------ test/test_pretty.lua | 124 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 258 insertions(+), 18 deletions(-) create mode 100644 number.lua diff --git a/number.lua b/number.lua new file mode 100644 index 0000000..9745a39 --- /dev/null +++ b/number.lua @@ -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 diff --git a/pretty.lua b/pretty.lua index 2c1c190..32b89be 100644 --- a/pretty.lua +++ b/pretty.lua @@ -1,13 +1,17 @@ - -- Ensure loading library, if it exists, no matter where pretty.lua was loaded from. -- Load the library component -local LIBRARY +local LIBRARY, format_number do local thispath = ... and select('1', ...):match('.+%.') or '' + + -- Load library info local was_loaded, library = pcall(require, thispath..'library') LIBRARY = was_loaded and library or {} + + -- Load number formatting + was_loaded, format_number = pcall(require, thispath..'number') 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]) -- Tons of compare - if (type_key_a ~= 'string' or 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 + if (type_key_a == 'string' and type_key_b == 'string') then 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 return TYPE_SORT_ORDER[type_value_a] < TYPE_SORT_ORDER[type_value_b] end @@ -470,10 +474,8 @@ local function format_string (str, options, depth, l) local single_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) 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 end -local function format_number (value, options, depth, l) - local shorthand = options.math_shorthand - 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) +if not format_number then + function format_number (value, options, depth, l) + local shorthand = options.math_shorthand + 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) + end end end diff --git a/test/test_pretty.lua b/test/test_pretty.lua index 287aeec..6eafb51 100644 --- a/test/test_pretty.lua +++ b/test/test_pretty.lua @@ -131,6 +131,67 @@ format_test { 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 { input = 1/0, -- Same as math.huge expect = '1/0', @@ -149,13 +210,13 @@ format_test { format_test { input = 1/0, -- Same as math.huge options = { math_shorthand = true }, - expect = 'inf', + expect = '∞', } format_test { input = -1/0, -- Same as -math.huge options = { math_shorthand = true }, - expect = '-inf', + expect = '-∞', } format_test { @@ -164,6 +225,23 @@ format_test { 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 @@ -488,7 +566,6 @@ format_test { -- Can include very small tables expect = '{ { 1 }, { 2 }, { 3 } }', } - -------------------------------------------------------------------------------- -- Multi-line tables @@ -573,6 +650,47 @@ format_test { 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