forked from enriquesl-git/Continued-Logarithm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Ternary.hs
394 lines (310 loc) · 12.4 KB
/
Ternary.hs
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
-----------------------------------------------------------------------------
-- |
-- Module : Ternary
-- Copyright : (c) Enrique Santos, June 2022
-- License : see LICENSE
--
-- Maintainer : Enrique Santos
-- Stability : internal
-- Portability : Portable
--
-- Nonpositional ternary ('Ternary') data type and its operations.
--
-- It is first defined a type SInt such that negative terms are
-- represented with the '-' sign following the value (exponent of 3).
--
-- Then, the type Ternary is defined as a list of SInt:
-- An example is: 377 = NT [6,5-,4-,3-,0-]
-- It should be an instance of 'Integral' and 'Signed', so it should
-- implementmet methods for: Ord, Num, Signed, Integral
--
-----------------------------------------------------------------------------
module Ternary (
SInt(..), Ternary(..), PairNT, -- data types
half, dup, sgn, -- from imported modules
i2terms, terms2i, oneTerm, pair2i, -- conversion
neg, add, sub, -- arithmetic & comparation
pw3, ntTripl, ntThird, mul, divStep, sqr, npSqr, -- quarter, geometric
-- sqrtStep, sqrtRem, sqrtMod, -- inverse
) where
-- import Prelude hiding (abs, signum)
import Integers -- as I
import Signed as S
import Data.Foldable
-- import Data.List -- unfold is not in Data.Foldable
-- import Data.Sequence -- better than List, faster
-- import qualified Data.Sequence as Sequence
default ()
--------------------------------------------------------------
--
-- | Continued Logarithm (CL) == Differential Non-positional Ternary
-- Accumulated C. Logarithm == Non-positional Ternary (NB)
--
---------------------------------------------------------------------------
{- | Ternary will be an instance of:
(Ord, Num, Integral, Signed, Num, Integral, Real, Foldable?) -}
newtype Ternary = NT [SInt] deriving (Eq, Show, Read)
-- type NT = Ternary -- just for brevity
type PairNT = (Ternary, Ternary)
pair2i :: PairNT -> (Integer, Integer)
pair2i (x, y) = (toInteger x, toInteger y)
---------------------------------------------------------------------------
{- Instances of Ternary -}
---------------------------------------------------------------------------
instance Ord Ternary where
(<=) (NT (S sx ax : xs)) (NT (S sy ay : ys))
| sx /= sy = sy
| ax /= ay = sy && ax < ay
| True = NT xs <= NT ys
(<=) (NT (S sx _ : _)) _ = not sx
(<=) _ y = sgn y
instance Enum Ternary where
fromEnum = fromEnum . toInteger
toEnum = fromInteger . toEnum -- not right
instance Signed Ternary where
(+.) a s = a + NT [S s 0] -- a +- 1
sgn (NT []) = True
sgn (NT x) = sSgn $ last x
instance Num Ternary where
fromInteger = NT . i2terms
abs = snd . sgnAbs
signum 0 = 0
signum n | sgn n = 1
signum _ = -1
negate (NT x) = NT (neg x)
(+) (NT x) (NT y) = NT (add x y)
(*) (NT x) (NT y) = NT (mul x y)
instance Integral Ternary where
toInteger (NT t) = terms2i t
{- Euclidean recursive division, minimum absolute rest -}
divMod _ 0 = error "Division by 0, in Ternary.divMod. "
divMod rs ds
| end = (0, rNext)
| True = (qNew, rNew)
where
end = sAbs qNext == 0 -- rs < ds ==> qNew == q
qNew = NT [qNext] + q -- accumulates q, without passing an accumulator
(q, rNew) = divMod rNext ds
(qNext, rNext) = divStep rs ds
{- | Returns quotient with positive rest always -}
quotRem n d
| sgn r = (q , r)
| sgn d = (q - 1, r + d)
| True = (q + 1, r - d)
where
(q, r) = divMod n d
instance Real Ternary where
toRational = toRational . toInteger
-- instance Foldable Ternary where
-- foldMap f (NT x) = NT (foldMap f x)
-- foldr f z (NT x) = NT (foldr f z x)
---------------------------------------------------------------------------
{- =
Conversion from and to Decimal base notation.
Continued Logarithm == continued product, by Euler transform
Here it is really cLog(x + 1)
-}
---------------------------------------------------------------------------
{- | From non-positional ternary to Integer, admits negative terms,
it also defines the recursive homographic transformation.
Used in Integral instance. -}
terms2i :: [SInt] -> Integer
terms2i = foldr transform 0 where
transform (S s a)
= (+) $ (*. s) . (^ a) $ 3
-- = (+) $ (*. s) . (3 ^) $ a
-- = (+) $ (*. s) $ 3 ^ a
{- | Gives in each step the closest power of 3.
That way, truncation will give the best approximation.
If odd subtract 1 and write 0, if even divide by 3 and count.
Write times counted.
x0 = 3^a0*sgn + x1; [a0,a1,...,an] == 3^a0 + 3^a1 + ... + 3^an
Should be an 'unfold' or equivalent to it.
Used in Num instance. -}
i2terms :: Integer -> [SInt] -- is: Integer Signed
i2terms = i2terms' 0 where -- reverse $
i2terms' _ 0 = []
i2terms' a n
| r == 0 = i2terms' (1 + a) q
| True = S (sgn r) a : i2terms' (1 + a) q
where (q, r) = quotMod 3 n
oneTerm :: Word -> Ternary
oneTerm x
| sgn x = NT [S True x]
| True = error "Negative argument in Ternary.oneTerm. "
---------------------------------------------------------------------------
{- = Arithmetic operations:
addition, substraction, opposite, absolute, (un)carry, successor, -}
---------------------------------------------------------------------------
{- | Addition, for both, positive and negative terms
-}
add, sub :: [SInt] -> [SInt] -> [SInt]
add xs [] = xs
add [] xs = xs
add [y] (x:xs)
| x == sNeg y = xs
| x == y = sNeg y : add [succ y] xs -- carry
| sAbs x < sAbs y = x : add [y] xs
| True = y : x : xs
add (y:ys) xs = add [y] $ add ys xs
sub = add . neg
neg :: [SInt] -> [SInt]
neg = fmap sNeg
{- | Carry for arithmetic operations,
carry is to reorganize terms to avoid intermediate repeated or
consecutive terms, thus minimizing the number of terms. It should
get each truncation the closest possible to the real value. Final
consecutive terms can stay only if previous term has the same sign.
O(log n) because recursion is not all of the times,
so it will stop recurring in a length proportional to log n, as much.
-}
{-
carry :: [SInt] -> [SInt]
carry (a : b : xs)
-- | a > succ b = a : carry (b : xs)
| negA == b = carry xs
| a == b = sucA : carry (negA : xs)
-- | negA == sucB = carry (negB : xs)
-- | a == sucB = carry $ sucA : carry (negB : xs)
| a < b = carry $ b : a : xs -- sort
where
negA = sNeg a
negB = sNeg b
sucA = succ a
sucB = succ b
carry xs = xs
-}
{- | carryAll is the recursive application of carry. Not used -}
-- carryAll :: [SInt] -> [SInt]
-- carryAll (x : xs) = carry $ x : carryAll xs
-- carryAll _ = []
-----------------------------------------------------------------------
-- * Base operations: bit-shift, triplicate, third,
---------------------------------------------------------------------------
-- | Multiplication by power of 3, MULtiple TRIPLication, bitwise left shift.
-- Power can be negative, which will give division, but
-- it should be checked that n >= -v; otherwise result would be rounded,
-- (or fractional CLog should be defined).
pw3 :: Int -> [SInt] -> [SInt]
pw3 = fmap . pw3term
where
pw3term n (S s v)
| sgn vn = S s $ fromIntegral vn -- conversion to natural
where vn = fromIntegral v + n -- conversion from natural
pw3term _ _ = error "Third of non-multiple of 3, in Ternary.pw3. "
-- | Triplicate: multiply by 3, faster than add;
-- third: like dividing by 3;
-- they are a particular case of product by power of 2: pw3
ntTripl, ntThird :: Ternary -> Ternary
ntTripl (NT x) = NT $ pw3 1 x -- takes more comparisons of conditions
ntThird (NT x) = NT $ pw3 (-1) x
ntDup x = x + x
ntQuad x = x + ntTripl x
-- ntHalf, ntQuarter :: [SInt] -> [SInt]
-- ntHalf (NT n) = NT . h $ reverse n
where
h [a, b]
| a == 0 = [] -- a : b : xs
| sgn a /= sgn b = reverse [b .. pred a]
-- | True = a : fmap negate $ reverse [b .. pred a]
-- | True = add [a1] $ h (a1 : b : xs)
-- where [a1] = pw3 (-1) [a]
-- h [0] = []
-- h [a] = pw3 (-1) [a]
-- h [] = []
-- ntQuarter = ntHalf . ntHalf
-- quarter NT x = q x
-- where
-- (a : b : c : d : xs) = reverse x
-- [a1] = pw3 (-1) [a]
-- q x
-- | a > b = a1 : a1 : a1 : b : c : d : xs
-- | a > c = b : a1 : a1 : a1 : c : d : xs
-- | a > d = b : c : a1 : a1 : a1 : d : xs
-- | True = a, q xs
--
-- | a == b && a == c && a == div= a : xs
-----------------------------------------------------------------------
-- * Geometric direct operations: multiplication, square
---------------------------------------------------------------------------
{- | Square, O(n^2), faster than @mul xs xs@ -}
sqr :: [SInt] -> [SInt]
-- | recursive version: x^2 + (2*x*xs + xs^2)
-- supposedly faster, as the term x^2 is calculated appart in a simpler way
sqr (S s x : xs)
= (S True (dup x) :) -- x^2 +
. add (sqr xs) -- xs^2 +
. mul xs -- xs *
$ [S (not s) x, S s (x + 1)] -- (x + x), in ternary
-- | non-recursive version: x^2 + (2*x + xs)*xs
-- sqr (S s x : xs) =
-- (S True (dup x) :)
-- . mul xs
-- $ S (not s) x: S s (x + 1) : xs
sqr _ = []
-- npSqr :: Ternary -> Ternary
npSqr (NT x) = NT (sqr x)
-- npSqr (NT [S _ x]) = oneSInt (dup x) -- single term square
-- mul, mulF :: Ternary -> Ternary -> Ternary
mul, mulE :: [SInt] -> [SInt] -> [SInt]
-- product by a one term element, equivalent to pw3
-- mul [S sa aa] [S sb ab] = [S (sa == sb) (aa + ab)]
-- mul [S sx ax] (S s v : ys) = S (sx == s) (ax + v) : mul [S sx ax] ys
mul [S True ax] ys = pw3 (fromIntegral ax) ys
mul [S _ ax] ys = pw3 (fromIntegral ax) $ neg ys
mul xs [y] = mul [y] xs
-- general product, several methods can be chosen
mul x y = mulE x y
-- mul x y = mulS x y
-- mul x y = mulF x y
{- | Multiplication
Egyptian method, O(n^2) -}
mulE xs (S sb ab : ys) = add (mul xs [S sb ab]) (mulE xs ys)
mulE _ _ = []
{- | Straightforward multiplication, O(n^2) -}
mulS (S sa aa : xs) (S sb ab : ys) =
(S (sa == sb) (aa + ab) :)
. add (mul xs [S sb ab])
. add (mul [S sa aa] ys)
$ mulS xs ys
mulS _ _ = []
{- | We define mulF to be x*y = 1/4(a^2 - b^2),
where a = x + y; b = x - y ('F' for 'Fast' or 'Fermat').
But ... quarter is not simple in Ternary.
Complexity is mainly on sqr, as the rest of the algorithm is <~ O(size). -}
-- mulF xs ys = quarter $ sub sqSub sqSum -- 1/4(A^2 - B^2)
-- where sqSub = sqr $ sub xs ys -- squared difference: (ys - xs)^2
-- sqSum = sqr $ add xs ys -- squared sum: (ys + xs)^2
---------------------------------------------------------------------------
{- = Inverse geometric operations, with integer rest:
division, square root, gcd, -}
---------------------------------------------------------------------------
divStep :: Ternary -> Ternary -> (SInt, Ternary)
{- | Single division step. (Not used yet)
Returns SINGLE TERM quotient which minimizes absolute rest,
and the new rest; keeps 'r + q*d' = constant, where:
d = divisor; q = quotient; r = rest or module. -}
divStep _ 0 = error "Divided by 0 in Ternary.divStep. "
divStep 0 _ = (S True 0, 0)
divStep (NT r) (NT d) = minimumBy comp candidates
where
-- positive rNew, reverse for optimization of steps
candidates = reverse $ (S True 0, NT r) : fmap qrPair digits
where
(S sr ar, S sd ad) = (last r, last d)
digits = [0 .. 1 + ar - ad]
qrPair q = (qNew, NT rNew)
where
qNew = S (sr == sd) q
rDif = mul [qNew] d
rNew = sub rDif r
comp (_, ra) (_, rb)
| res == EQ && sgn rb = GT -- in order to prioritize positive rest
| True = res
where
res = compare (S.abs ra) (S.abs rb)
-- divStep (NT r) (NT d) =
-- where
-- (r, d) = ()
--
---------------------------------------------------------------------------