Reworked the number formatting system. Will now attempt to generate the shortest possible representation of the given number.
This commit is contained in:
parent
e5e23422ef
commit
ae73cc9b64
194
number.lua
194
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 = {}
|
||||
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
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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
|
||||
|
||||
-- Maybe it's a fractional 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)
|
||||
if numerator and denumberator ~= 1 then
|
||||
alternative_repr( format_number(numerator)..'/'..format_number(denumberator) )
|
||||
end
|
||||
return ( tostring(n):gsub('e%+?', '*10^') ) -- Parantheses to return first result.
|
||||
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))
|
||||
|
||||
-- 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
|
||||
|
|
179
test/test_number.lua
Normal file
179
test/test_number.lua
Normal file
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue
Block a user