From ae73cc9b6431b3179c5740d708a5b9b67b106154 Mon Sep 17 00:00:00 2001 From: Jon Michael Aanes Date: Mon, 3 Apr 2017 20:01:36 +0200 Subject: [PATCH] Reworked the number formatting system. Will now attempt to generate the shortest possible representation of the given number. --- number.lua | 178 +++++++++++++++++++++++++++--------------- test/test_number.lua | 179 +++++++++++++++++++++++++++++++++++++++++++ test/test_pretty.lua | 129 ------------------------------- test/tests.lua | 1 + 4 files changed, 297 insertions(+), 190 deletions(-) create mode 100644 test/test_number.lua diff --git a/number.lua b/number.lua index 9745a39..9fbfd92 100644 --- a/number.lua +++ b/number.lua @@ -20,6 +20,17 @@ local function factorial (n) 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 @@ -35,84 +46,129 @@ local function calculate_fraction (n) 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', +local UNICODE_SUPERSCRIPT = { + ['0'] = '⁰', ['1'] = '¹', ['2'] = '²', ['3'] = '³', ['4'] = '⁴', + ['5'] = '⁵', ['6'] = '⁶', ['7'] = '⁷', ['8'] = '⁸', ['9'] = '⁹', + ['-'] = '⁻', ['+'] = '⁺', ['='] = '⁼', ['('] = '⁽', [')'] = '⁾', +} +local function to_superscript (n) + return tostring(n):gsub('.', function (a) return UNICODE_SUPERSCRIPT[a] end) +end + +local UNICODE_CHAR_PATTERN = '[\01-\127\192-\255][\128-\191]*' +local function unicode_len (str) + local len = 0 + for char in str:gmatch(UNICODE_CHAR_PATTERN) do len = len + 1 end + return len +end + +-------------------------------------------------------------------------------- + +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, + }, + -- 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'..to_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'..to_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/√'..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 'ℯ'..to_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 '√'..a 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, + }, } -------------------------------------------------------------------------------- --- Generate special number table --- TODO: Restructure this. +local LONG_ASS_CONSTANT = '___________________________________________________' -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 +local function format_number (n, shorthand) + if n ~= n then return shorthand and 'NaN' or '0/0' + elseif n < 0 then return '-' .. format_number(-n, shorthand) end - for i = 5, 308 do - SPECIAL_NUMBER[math.pow(10, i)] = '10^'..i + -- Finding the shortest + local shortest, length = LONG_ASS_CONSTANT, unicode_len(LONG_ASS_CONSTANT) + local function alternative_repr (repr) + local repr_len = unicode_len(repr) + if repr_len < length then shortest, length = repr, repr_len end 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..')' + -- Maybe it's a "special" number? + for _, special_number_tests in pairs(SPECIAL_NUMBER) do + local a = special_number_tests.est(n) + if a then + local a_near = math.floor(a + 0.5) + if n == special_number_tests.real(a_near) then + alternative_repr( special_number_tests[shorthand and 'short' or 'repr'](a_near) ) + end end end - for i = 1, 100 do - local log = math.log(i) - if not is_integer(log) then - SPECIAL_NUMBER[log] = 'math.log('..i..')' + -- Maybe it's a fractional number? + do + local numerator, denumberator = calculate_fraction(n) + if numerator and denumberator ~= 1 then + alternative_repr( format_number(numerator)..'/'..format_number(denumberator) ) 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)) -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. + -- Well, this is not a pretty number! + return shortest 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 + l[#l+1] = format_number(value, options.math_shorthand) end diff --git a/test/test_number.lua b/test/test_number.lua new file mode 100644 index 0000000..1497f2a --- /dev/null +++ b/test/test_number.lua @@ -0,0 +1,179 @@ + +local SUITE = require('TestSuite').new('number') +SUITE:setEnviroment{ + format = require('pretty') +} + +local function format_test (t) + SUITE:addTest(t.expect, function () + assert_equal(t.expect, format(t.input, t.options)) + end) +end + +local function number_test (t) + SUITE:addTest(t.expect, function () + assert_equal(t.expect, format(t.input, {})) + end) + SUITE:addTest(t.shorthand or (t.expect .. ' shorthand'), function () + assert_equal(t.shorthand or t.expect, format(t.input, { math_shorthand = true })) + end) +end + +-------------------------------------------------------------------------------- +-- Integers + +number_test { + input = 0, + expect = '0', +} + +number_test { + input = -0, + expect = '0', +} + +number_test { + input = 2000, + expect = '2000', +} + +number_test { + input = -2000, + expect = '-2000', +} + +-------------------------------------------------------------------------------- +-- Exponents + +number_test { + input = 10^5, + expect = '10^5', + shorthand = '10⁵', +} + +number_test { + input = 10^-6, + expect = '10^-6', + shorthand = '10⁻⁶', +} + +number_test { + input = 2^17, + expect = '2^17', + shorthand = '2¹⁷', +} + +number_test { + input = 2^-7, + expect = '2^-7', + shorthand = '2⁻⁷', +} + +-------------------------------------------------------------------------------- +-- Decimal numbers + +number_test { + input = 0.1, + expect = '0.1', +} + +number_test { + input = 0.65, + expect = '0.65', +} + +-------------------------------------------------------------------------------- +-- Fractions + +number_test { + input = 1/3, + expect = '1/3', +} + +number_test { + input = 9/17, + expect = '9/17', +} + +-------------------------------------------------------------------------------- +-- Squareroots, logarithms, and other fine functions + +number_test { + input = math.sqrt(8), + expect = 'math.sqrt(8)', + shorthand = '√8', +} + +number_test { + input = 1/math.sqrt(8), + expect = '1/math.sqrt(8)', + shorthand = '1/√8', +} + +number_test { + input = math.log(8), + expect = 'math.log(8)', + shorthand = 'lg(8)', +} + +number_test { + input = 6227020800, + expect = 'fac(13)', + shorthand = '13!', +} + +-------------------------------------------------------------------------------- +-- Constants, and multiples of constants + +number_test { + input = math.pi, + expect = 'math.pi', + shorthand = 'π', +} + +number_test { + input = 7*math.pi, + expect = '7*math.pi', + shorthand = '7π', +} + +number_test { + input = math.exp(1), + expect = 'math.exp(1)', + shorthand = 'ℯ', +} + +number_test { + input = math.exp(10), + expect = 'math.exp(10)', + shorthand = 'ℯ¹⁰', +} + +number_test { + input = 1/0, + expect = '1/0', + shorthand = '∞' +} + +number_test { + input = -1/0, + expect = '-1/0', + shorthand = '-∞' +} + +number_test { + input = 0/0, + expect = '0/0', + shorthand = 'NaN', +} + +-------------------------------------------------------------------------------- + +format_test { + input = { 1/2, 1/3, 1/4, 1/5 }, + expect = '{ 1/2, 1/3, 1/4, 1/5 }', +} + +-------------------------------------------------------------------------------- + +return SUITE diff --git a/test/test_pretty.lua b/test/test_pretty.lua index a7ea451..7bf2bd6 100644 --- a/test/test_pretty.lua +++ b/test/test_pretty.lua @@ -106,135 +106,6 @@ format_test { expect = '\'ø\'', } --------------------------------------------------------------------------------- --- Numbers - -format_test { - input = 0, - expect = '0', -} - -format_test { - input = 2000, - expect = '2000', -} - -format_test { - input = -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 { - input = 1/0, -- Same as math.huge - expect = '1/0', -} - -format_test { - input = -1/0, -- Same as -math.huge - expect = '-1/0', -} - -format_test { - input = 0/0, -- Same as nan - expect = '0/0', -} - -format_test { - input = 1/0, -- Same as math.huge - options = { math_shorthand = true }, - expect = '∞', -} - -format_test { - input = -1/0, -- Same as -math.huge - options = { math_shorthand = true }, - expect = '-∞', -} - -format_test { - input = 0/0, -- Same as nan - options = { math_shorthand = true }, - 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 diff --git a/test/tests.lua b/test/tests.lua index 2ee9534..dad8e7b 100644 --- a/test/tests.lua +++ b/test/tests.lua @@ -3,4 +3,5 @@ package.path = package.path .. ';./test/?.lua;./src/?.lua' local TEST_SUITE = require("TestSuite").new('pretty') TEST_SUITE:addModules('test/test_*') + TEST_SUITE:setOptions(...) TEST_SUITE:runTests()