-
Notifications
You must be signed in to change notification settings - Fork 3
/
Binary.hs
342 lines (274 loc) · 11.3 KB
/
Binary.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
-----------------------------------------------------------------------------
-- |
-- Module : Binary
-- Copyright : (c) Enrique Santos, February 2019
-- License : see LICENSE
--
-- Maintainer : Enrique Santos
-- Stability : internal
-- Portability : Portable
--
-- Nonpositional binary (Binary) data type and its operations.
--
-- It is used a type Term for digit terms, such that negative terms are
-- represented with the '-' sign following the value (exponent of 2).
--
-- Then, the type Binary is defined as a list of Term terms:
-- An example is: 377 = NB [9, 7-, 3-, 0]
-- It should be an instance of 'Integer' and 'Signed', so it should
-- implementmet methods for: Ord, Num, Signed, Integral
--
-----------------------------------------------------------------------------
module Binary (
Term(..), Binary(..), PairNB, -- data types
half, dup, sgn, -- from imported modules
i2terms, terms2i, oneTerm, pair2i, -- conversion
neg, carry, add, sub, -- arithmetic & comparation
pw2, npDup, npHlf, sqr, mul, npSqr, divStep, -- 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 Binary
-- Accumulated C. Logarithm == Non-positional Binary (NB)
--
---------------------------------------------------------------------------
{- | Binary will be an instance of:
(Ord, Num, Integral, Signed, Num, Integral, Real, Foldable?) -}
newtype Binary = NB [Term] deriving (Eq, Show, Read)
-- type NB = Binary -- just for brevity
type PairNB = (Binary, Binary)
pair2i :: PairNB -> (Integer, Integer)
pair2i (x, y) = (toInteger x, toInteger y)
---------------------------------------------------------------------------
{- Instances of Binary -}
---------------------------------------------------------------------------
instance Ord Binary where
(<=) (NB (S sx ax : xs)) (NB (S sy ay : ys))
| sx /= sy = sy
| ax /= ay = sy && ax < ay
| True = NB xs <= NB ys
(<=) (NB (S sx _ : _)) _ = not sx
(<=) _ y = sgn y
instance Enum Binary where
fromEnum = fromEnum . toInteger
toEnum = fromInteger . toEnum -- not right
instance Signed Binary where
(+.) a s = a + NB [S s 0] -- a +- 1
sgn (NB (x : _)) = sSgn x
sgn _ = True
instance Num Binary where
fromInteger = NB . i2terms
abs = snd . sgnAbs
signum 0 = 0
signum n | sgn n = 1
signum _ = -1
negate (NB x) = NB (neg x)
(+) (NB x) (NB y) = NB (add x y)
(*) (NB x) (NB y) = NB (mul x y)
instance Integral Binary where
toInteger (NB t) = terms2i t
{- Euclidean recursive division, minimum absolute rest -}
divMod _ 0 = error "Division by 0, in Binary.divMod. "
divMod rs ds
| end = (0, rNext)
| True = (qNew, rNew)
where
end = sAbs qNext == 0 -- rs < ds ==> qNew == q
qNew = NB [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 Binary where
toRational = toRational . toInteger
-- instance Foldable Binary where
-- foldMap f (NB x) = NB (foldMap f x)
-- foldr f z (NB x) = NB (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 binary to Integer, admits negative terms,
it also defines the recursive homographic transformation.
Used in Integral instance. -}
terms2i :: [Term] -> Integer
terms2i = foldr transform 0 where
transform (S s a)
= (+) $ (*. s) . (^ a) $ 2
{- | Gives in each step the closest power of 2 (the lesser when two
equals). That way, truncation will give the best approximation.
If odd subtract 1 and write 0, if even divide by 2 and count,
write times counted.
x0 = 2^a0*sgn + x1; [a0,a1,...,an] == 2^a0 + 2^a1 + ... + 2^an
Should be an 'unfold' or equivalent to it.
Used in Num instance. -}
i2terms :: Integer -> [Term] -- is: Integer Signed
i2terms x = reverse $ i2terms' 0 x where
i2terms' _ 0 = []
i2terms' a n
| r == 1 = S True a : i2terms' (2 + a) q
| r == -1 = S False a : i2terms' (2 + a) q
| r == 0 = i2terms' (2 + a) q
| True = i2terms' (1 + a) (half n) -- 1 + dup q
where (q, r) = quotMod 4 n
oneTerm :: Int -> Binary
oneTerm x
| sgn x = NB [S True x]
| True = error "Negative argument in Binary.oneTerm. "
---------------------------------------------------------------------------
{- = Arithmetic operations:
addition, substraction, opposite, absolute, (un)carry, successor, -}
---------------------------------------------------------------------------
{- | Addition, for both, positive and negative terms
-}
add, sub :: [Term] -> [Term] -> [Term]
add [] ys = ys
add xs (b : ys)
| head xs <= b = carry . (b :) . add xs $ ys
add xs ys = add ys xs
sub = add . neg
neg :: [Term] -> [Term]
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 :: [Term] -> [Term]
carry (a : b : xs)
-- | a > succ b = a : carry (b : xs)
| negA == b = carry xs
| a == b = sucA : carry 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 :: [Term] -> [Term]
-- carryAll (x : xs) = carry $ x : carryAll xs
-- carryAll _ = []
-----------------------------------------------------------------------
-- * Base operations: bit-shift, duplicate, halve,
---------------------------------------------------------------------------
-- | Multiplication by power of 2, MULtiple DUPlication, 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).
-- names: pw2, mulDup, manyDup, DupN, timesDup, dupDup, mul2pow, dupRep/repDup, shift
pw2 :: Int -> [Term] -> [Term]
pw2 n = fmap (pw2term n)
where
pw2term n (S s v)
| vn < 0 = error "Third of non-multiple of 3, in Ternary.pw3. "
| True = S s $ fromIntegral vn -- conversion to natural
where vn = fromIntegral v + n -- conversion from natural
-- | quarter: like dividing by 4;
quarter :: [Term] -> [Term]
quarter x = pw2 (-2) x
-- | Duplicate: multiply by 2, faster than add xs xs;
-- half: like dividing by 2;
-- they are a particular case of product by power of 2: pw2
npDup, npHlf, npSqr :: Binary -> Binary
npDup (NB x) = NB $ pw2 1 x -- takes more comparisons of conditions
npHlf (NB x) = NB $ pw2 (-1) x
-----------------------------------------------------------------------
-- * Geometric direct operations: multiplication, square
---------------------------------------------------------------------------
{- | Square, O(n^2), faster than @mul xs xs@ -}
-- sqr :: Binary -> Binary
sqr :: [Term] -> [Term]
-- sqr (S s x : xs) = carry
-- . (S True (dup x) :)
-- . mul xs
-- $ (S s (x + 1) : xs) -- (2*x + xs)*xs
sqr (S s x : xs) = carry
. (S True (dup x) :)
. add (mul [S s (x + 1)] xs) -- multiplication by one term, more efficient
$ sqr xs -- (2*x + xs)*xs
sqr _ = []
npSqr (NB x) = NB (sqr x)
-- npSqr (NB [S _ x]) = oneTerm (dup x) -- single term square
-- mul, mulF :: Binary -> Binary -> Binary
mul, mulE, mulF :: [Term] -> [Term] -> [Term]
-- product by a one term element, equivalent to pw2
-- 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 = pw2 (fromIntegral ax) ys
mul [S _ ax] ys = pw2 (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) = carry
. (S (sa == sb) (aa + ab) :)
. add (mul xs [S sb ab])
. add (mul [S sa aa] ys)
$ mulS xs ys
mulS _ _ = []
{- | We define clMul to be x*y = 1/4(a^2 - b^2),
where a = x + y; b = x - y ('F' for 'Fast' or 'Fermat').
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 :: Binary -> Binary -> (Term, Binary)
{- | 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 Binary.divStep. "
divStep 0 _ = (S True 0, 0)
divStep (NB r) (NB d) = minimumBy comp candidates
where
-- positive rNew, reverse for optimization of steps
candidates = reverse $ (S True 0, NB r) : fmap qrPair digits
where
S sr ar = head r
S sd ad = head d
digits = [0 .. 1 + ar - ad]
qrPair q = (qNew, NB 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)
---------------------------------------------------------------------------