forked from thenumbernine/lua-ext
-
Notifications
You must be signed in to change notification settings - Fork 0
/
number.lua
127 lines (117 loc) · 4.01 KB
/
number.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
-- number metatable
local math = require 'ext.math'
-- flatten ext.math into this just like we flatten lua's string into ext.string
local hasutf8, utf8 = pcall(require, 'utf8')
local number = {}
number.__index = number
for k,v in pairs(math) do number[k] = math[k] end
-- [[ tostring machine precision of arbitrary base
number.alphabets = {
{('0'):byte(), ('9'):byte()}, -- latin numbers
{('a'):byte(), ('z'):byte()}, -- english
{0x3b1, 0x3c9}, -- greek
{0x430, 0x45f}, -- cyrillic
{0x561, 0x586}, -- armenian
{0x905, 0x939}, -- devanagari
{0x2f00, 0x2fd5}, -- kangxi
{0x3041, 0x3096}, -- hiragana
{0x30a1, 0x30fa}, -- katakana
{0x4e00, 0x9fd0}, -- chinese, japanese, korean characters
}
function number.charfor(digit)
local table = require 'ext.table'
for _,alphabet in ipairs(number.alphabets) do
local start,fin = table.unpack(alphabet)
if digit <= fin - start then
digit = digit + start
if hasutf8 then
return utf8.char(digit)
else
-- TODO this will fail with utf8 chars beyond ascii
return string.char(digit)
end
end
digit = digit - (fin - start + 1)
end
error 'you need more alphabets to represent that many digits'
end
-- TODO rename above function to 'tochar' ?
function number.todigit(ch)
local table = require 'ext.table'
local indexInAlphabet
if hasutf8 then
indexInAlphabet = utf8.codepoint(ch)
else
-- TODO this will fail with utf8 chars beyond ascii
indexInAlphabet = string.byte(ch)
end
local lastTotalIndex = 0
for _,alphabet in ipairs(number.alphabets) do
local start,fin = table.unpack(alphabet)
if indexInAlphabet >= start and indexInAlphabet <= fin then
return lastTotalIndex + (indexInAlphabet - start)
end
lastTotalIndex = lastTotalIndex + (fin - start + 1)
end
error"couldn't find the character in all the alphabets"
end
number.base = 10
number.maxdigits = 50
-- I'm not going to set this as __tostring by default, but I will leave it as part of the meta
-- feel free to use it with a line something like (function(m)m.__tostring=m.tostring end)(debug.getmetatable(0))
number.tostring = function(t, base, maxdigits)
local s = {}
if t < 0 then
t = -t
table.insert(s, '-')
end
if t == 0 then
table.insert(s, '0.')
else
--print('t',t)
if not base then base = number.base end
if not maxdigits then maxdigits = number.maxdigits end
--print('base',base)
local i = math.floor(math.log(t,base))+1
if i == math.huge then error'infinite number of digits' end
--print('i',i)
t = t / base^i
--print('t',t)
local dot
while true do
if i < 1 then
if not dot then
dot = true
table.insert(s, '.')
table.insert(s, ('0'):rep(-i))
end
if t == 0 then break end
if i <= -maxdigits then break end
end
t = t * base
local digit = math.floor(t)
t = t - digit
-- at this point 'digit' holds an integer value in [0,base)
-- there's two ways we can go about it:
-- 1) traditionally, where each digit represents an integer, times some base^i for some i
-- in this case, for fractional bases, the last (fraction) digit is only considered up to the fraction
-- i.e. in base 2.5, from 0b..1b we have the span of 1, partitioned by digits 0.1b at 2/5ths the distance, 0.2b at 4/5ths the distance, and 1.0 and 5/5ths the distance
-- from 1b..2b we have the same span,
-- and from 2b..10b we have half that span, from 2 to 2.5
-- therefore the value 2.2b would represent 2.8, which is also represetned by 10.0112210002002002...
-- so as long as the span between digits can represent fractions of base^i rather than whole base^i's
-- we can have multiple representations of numbers
-- 2) stretched. in this case a fractional span, such as from 2b to 10b in base 2.5, would be stretched
-- this is harder to convey when descibing the number system
table.insert(s, number.charfor(digit))
i = i - 1
--print('t',t,'i',i,'digit',digit)
end
end
return table.concat(s)
end
--]]
-- ('a'):byte():char() == 'a'
number.char = string.char
-- so the lookup goes: primitive number -> number -> math
return number