File Coverage

blib/lib/Math/Logic/Ternary/Word.pm
Criterion Covered Total %
statement 532 572 93.0
branch 156 168 92.8
condition 20 27 74.0
subroutine 106 106 100.0
pod 57 57 100.0
total 871 930 93.6


line stmt bran cond sub pod time code
1             # Copyright (c) 2012-2017 Martin Becker, Blaubeuren. All rights reserved.
2             # This package is free software; you can redistribute it and/or modify it
3             # under the same terms as Perl itself.
4              
5             package Math::Logic::Ternary::Word;
6              
7 15     15   233538 use 5.008;
  15         56  
8 15     15   78 use strict;
  15         30  
  15         333  
9 15     15   72 use warnings;
  15         30  
  15         444  
10 15     15   70 use Carp qw(croak);
  15         28  
  15         865  
11 15     15   80 use Scalar::Util qw(blessed);
  15         26  
  15         570  
12 15     15   1853 use Role::Basic qw(with);
  15         58950  
  15         86  
13 15     15   10882 use Math::BigInt;
  15         287821  
  15         85  
14 15     15   235484 use Math::Logic::Ternary::Trit;
  15         66  
  15         1062  
15             with qw(Math::Logic::Ternary::Object);
16              
17             # ----- static data -----
18              
19             our $VERSION = '0.004';
20             our @CARP_NOT = qw(Math::Logic::Ternary);
21              
22 15     15   108 use constant MAX_SIZE => 19683;
  15         32  
  15         868  
23 15     15   84 use constant MAX_IV_SIZE => 18;
  15         44  
  15         605  
24 15     15   106 use constant W_SIZE => 0;
  15         28  
  15         604  
25 15     15   90 use constant W_TRITS => 1;
  15         31  
  15         682  
26 15     15   80 use constant TRIT_PREFIX => Math::Logic::Ternary::Trit::TRIT_PREFIX();
  15         32  
  15         607  
27 15     15   84 use constant BASE3_PREFIX => '@';
  15         30  
  15         556  
28 15     15   81 use constant BASE27_PREFIX => '%';
  15         62  
  15         7224  
29              
30             my $zero = Math::Logic::Ternary::Trit->nil;
31             my $one = Math::Logic::Ternary::Trit->true;
32             my $two = Math::Logic::Ternary::Trit->false;
33             my @int_trits = ($zero, $one, $two);
34              
35             my @base3_chars = qw(n t f);
36             my %base3_trits =
37             map {
38             my $ch = $base3_chars[$_->as_int_u];
39             (lc $ch => $_, uc $ch => $_)
40             } @int_trits;
41             my @base27_chars = qw(_ a b c d e f g h i j k l m N O P Q R S T U V W X Y Z);
42             my %base27_words =
43             map {
44             my $ch = $base27_chars[$_];
45             my $w = Math::Logic::Ternary::Word->from_int(3, $_);
46             (lc $ch => $w, uc $ch => $w)
47             } -13..13;
48             my @comparison_relations = (
49             [gt => 'eqt'],
50             [lt => 'eqf'],
51             [ge => 'nef'],
52             [le => 'net'],
53             );
54             my @suffixes = ('', 'u', 'v');
55             my @_suffixes = ('', '_u', '_v');
56              
57             my @word_operators = (
58             ['Neg', 'W', 'W', 0],
59             ['Negv', 'Wt', 'WT', 2],
60             ['Lshift', 'Wt', 'WT'],
61             ['Rshift', 'Wt', 'WT'],
62             (
63             map {
64             my ($i, $sfx) = ($_, $suffixes[$_]);
65             ["Sign$sfx", 'W', 'T', $i],
66             ["Incr$sfx", 'Wt', 'WT', $i],
67             ["Decr$sfx", 'Wt', 'WT', $i],
68             ["Dpl$sfx", 'Wt', 'WT', $i],
69             (
70             map {
71             ["$_$sfx", 'WWt', 'T', $i]
72             } qw(Cmp Asc Gt Lt Ge Le)
73             ),
74             ["Sort2$sfx", 'WW', 'WW', $i],
75             ["Tlr$sfx", 'WW', 'W', $i],
76             ["Tgr$sfx", 'WW', 'W', $i],
77             ["Add$sfx", 'WWt', 'WT', $i],
78             ["Subt$sfx", 'WWt', 'WT', $i],
79             ["Sort3$sfx", 'WWW', 'WWW', $i],
80             ["Min$sfx", 'WWW', 'W', $i],
81             ["Med$sfx", 'WWW', 'W', $i],
82             ["Max$sfx", 'WWW', 'W', $i],
83             ["Mul$sfx", 'WWw', 'WW' . ('v' eq $sfx && 'T'), $i],
84             ["Div$sfx", 'WW', 'WWT', $i],
85             ["Ldiv$sfx", 'WWW', 'WWWT', $i],
86             } 0 .. $#suffixes
87             ),
88             (
89             map {
90             my ($i, $sfx) = ($_, $suffixes[$_]);
91             # ["Hlv$sfx", 'Wt', 'WT', $i],
92             # ["Amn$sfx", 'WWt', 'WT', $i],
93             ["Sum$sfx", 'WWWt', 'WT', $i],
94             } 0, 1
95             ),
96             ['Mpx', 'WWWW', 'W' ],
97             );
98             my @word_formatters = (
99             ['as_string'],
100             ['as_base27'],
101             (
102             map {
103             my ($i, $_sfx) = ($_, $_suffixes[$_]);
104             ["as_int$_sfx", $i],
105             ["as_modint$_sfx", $i],
106             } 0 .. $#_suffixes
107             ),
108             );
109              
110             my $modint_loaded = 0;
111              
112             # ----- other initializations -----
113              
114             _load_generated_methods();
115              
116             # ----- private subroutines -----
117              
118             sub _declare {
119 3750     3750   6102 my ($name, $ref) = @_;
120 3750         5828 my $fqname = __PACKAGE__ . '::' . $name;
121 15     15   104 no strict 'refs';
  15         29  
  15         84433  
122 3750         4598 *{$fqname} = $ref;
  3750         14666  
123             }
124              
125             sub _parse_int {
126 3077     3077   5746 my ($class, $size, $int, $tval, $base) = @_;
127 3077 100       6063 croak 'missing size information' if !$size;
128 3076 100       13508 croak 'integer argument expected' if $int !~ /^[-+]?\d+\z/;
129 3075 100 100     7450 if ($size > MAX_IV_SIZE && !ref $int) {
130 3         15 $int = Math::BigInt->new($int);
131             }
132 3075         4892 my @trits = ();
133 3075         5257 while ($int) {
134 8500 100       28194 croak "number too large for word size $size" if $size <= @trits;
135 8495         14351 my $trit = $int_trits[$int % 3];
136 8495         147733 $int = ($int - $trit->$tval) / $base;
137 8495         281042 push @trits, $trit;
138             }
139 3070         6970 return $class->from_trits($size, @trits);
140             }
141              
142             sub _parse_base {
143 36     36   82 my ($class, $size, $string, $base, $thash) = @_;
144             my @words =
145             map {
146 36 50       135 exists($thash->{$_}) ? $thash->{$_} :
  215 100       886  
147             q[ ] eq $_ ? () :
148             croak qq{illegal base$base character "$_"}
149             }
150             split //, reverse $string;
151 32         100 return $class->from_words($size, @words);
152             }
153              
154             sub _as_int {
155 11862     11862   18350 my ($this, $tval, $base) = @_;
156 11862         15155 my ($size, $trits) = @{$this}[W_SIZE, W_TRITS];
  11862         18394  
157 11862 100       20089 my $int = $size <= MAX_IV_SIZE? 0: Math::BigInt->new(0);
158 11862         15608 foreach my $trit (reverse @{$trits}) {
  11862         17594  
159 24126         161481 $int = $int * $base + $trit->$tval;
160             }
161 11862         29051 return $int;
162             }
163              
164             sub _modulus {
165 15     15   27 my ($size) = @_;
166 15 100       54 my $modulus = ($size <= MAX_IV_SIZE? 3: Math::BigInt->new(3)) ** $size;
167 15         2691 return $modulus;
168             }
169              
170             sub _max_int_v {
171 2     2   7 my ($size) = @_;
172 2 100       10 my $max_int_v = $size <= MAX_IV_SIZE? 0: Math::BigInt->new(0);
173 2         143 for (my $exp = 0; $exp < $size; $exp += 2) {
174 43         11473 $max_int_v = $max_int_v * 9 + 2;
175             }
176 2         401 return $max_int_v;
177             }
178              
179             sub _as_modint {
180 6     6   19 my ($this, $residue) = @_;
181 6         18 my $size = $this->Trits;
182 6 100       23 if (!$modint_loaded) {
183 1         7 require Math::ModInt;
184 1         3 $modint_loaded = 1;
185             }
186 6         19 return Math::ModInt->new($residue, _modulus($size));
187             }
188              
189             sub _check_modint_modulus {
190 9     9   21 my ($size, $modint) = @_;
191 9         26 my $given_mod = $modint->modulus;
192 9 100       96 if ($size) {
193 7         19 my ($wanted_mod) = _modulus($size);
194 7 100       24 if ($given_mod != $wanted_mod) {
195 1         117 croak qq{wrong modulus for this size, expected $wanted_mod};
196             }
197             }
198             else {
199 2         5 my $power = $given_mod;
200 2         4 $size = 0;
201 2         4 while (1) {
202 4 100       116 croak qq{modulus is not a power of 3} if 0 != $power % 3;
203 3         6 $power /= 3;
204 3         7 ++$size;
205 3 100       11 last if $power <= 1;
206             }
207             }
208 7         157 return $size;
209             }
210              
211             # ascending trits iterator factory
212             sub _trits_asc {
213 4708     4708   7489 my ($this) = @_;
214 4708         6541 my $i = 0;
215 4708     10920   21568 return sub { $this->Trit($i++) };
  10920         21913  
216             }
217              
218             # descending trits iterator factory, takes number of trits
219             sub _trits_desc {
220 27226     27226   38344 my ($this, $i) = @_;
221 27226     40370   77046 return sub { $this->Trit(--$i) };
  40370         66587  
222             }
223              
224             # logical operator factory, takes number of operands and a name
225             sub _logical {
226 1425     1425   2391 my ($argc, $op) = @_;
227             return sub {
228 30     30   56 my $this = shift;
229 30         51 my @args = map { $_->Sign } @_;
  45         76  
230 30         48 return $this->Sign->$op(@args);
231 1425         5680 };
232             }
233              
234             # tritwise operator factory, takes number of operands and a name
235             sub _tritwise {
236 1425     1425   2186 my ($argc, $op) = @_;
237             return sub {
238 9     9   48 my $this = shift;
239 9         20 my @args = map { _trits_asc($_) } @_;
  5         16  
240 9         23 my @trits = map { $_->$op(map { $_->() } @args) } $this->Trits;
  81         145  
  45         84  
241 9         24 return $this->convert_trits(@trits);
242 1425         6190 };
243             }
244              
245             # lower to higher significance cascading numerical operator factory
246             # takes argument count and names of principal and carry operator
247             # and optional default carry trit
248             # resulting op takes given number of numerical arguments and optional
249             # carry trit, by default 0, and returns numerical result and carry trit
250             sub _cascading {
251 270     270   587 my ($arity, $op, $cop, $default_carry) = (@_, $zero);
252             return sub {
253 3616 100   3616   1174561 croak "missing arguments" if @_ < $arity;
254 3607 100       8712 croak "array context expected" if !wantarray;
255 3587         5270 my $this = shift;
256 3587         7967 my @those = map { (shift)->_trits_asc } 2..$arity;
  4702         9620  
257 3587         7325 my ($carry) = (@_, $default_carry);
258 3587         7317 my @trits = $this->Trits;
259 3587         6386 foreach my $trit (@trits) {
260 9045         13921 my @args = map { $_->() } @those;
  10866         16352  
261 9045         24159 ($trit, $carry) =
262             ($trit->$op(@args, $carry), $trit->$cop(@args, $carry));
263             }
264 3587         7856 return ($this->convert_trits(@trits), $carry);
265 270         1498 };
266             }
267              
268             # # higher to lower significance extended cascading operator factory
269             # # takes names of three logical operators (result trit, carry trit, second
270             # # carry trit) and numeric addition operator
271             # # resulting op takes given number of numerical arguments and optional
272             # # carry trits and returns numerical result and carry trits
273             # sub _ext_casc {
274             # my ($arity, $op, $cop, $sop, $Aop) = @_;
275             # return sub {
276             # croak "missing arguments" if @_ < $arity;
277             # croak "array context expected" if !wantarray;
278             # my $this = shift;
279             # my $wsiz = $this->Trits;
280             # my @those = map { (shift)->_trits_dsc($wsiz) } 2..$arity;
281             # my ($carry, $sec_carry) = (@_, $zero, $zero);
282             # my @trits = $this->Trits;
283             # my @secs = ();
284             # croak 'NYI';
285             # };
286             # }
287              
288             # higher to lower significance cascading comparison operator factory
289             # takes name of logical comparison operator and optional name of
290             # result modifier function
291             # resulting op takes two numerical arguments and optional carry trit
292             # and returns result trit
293             sub _lexical {
294 180     180   336 my ($cmp, $map) = @_;
295             return sub {
296 9248     9248   21003 my ($this, $that, $carry) = @_;
297 9248 100       21275 croak 'missing arguments' if @_ < 2;
298 9235 100       19556 $carry = (2 == @_)? $zero: $carry->Sign;
299 9235         17353 my $i = $this->Trits;
300 9235         18132 my $i1 = $this->_trits_desc($i);
301 9235         15828 my $i2 = $that->_trits_desc($i);
302 9235   100     23488 while ($carry->is_nil && $i--) {
303 13861         23943 $carry = $i1->()->$cmp($i2->());
304             }
305 9235 100       31769 return $map? $carry->$map: $carry;
306 180         819 };
307             }
308              
309             # base-3 comparison operator factory
310             # takes optional name of result modifier function
311             # resulting op takes two numerical arguments and optional carry trit
312             # and returns result trit
313             sub _cmpv {
314 90     90   165 my ($map) = @_;
315 90         180 my @op = ('cmpu', 'ascu');
316             return sub {
317 4385     4385   11309 my ($this, $that, $carry) = @_;
318 4385 100       10996 croak 'missing arguments' if @_ < 2;
319 4378 100       8876 $carry = (2 == @_)? $zero: $carry->Sign;
320 4378         8527 my $i = $this->Trits;
321 4378         9169 my $i1 = $this->_trits_desc($i);
322 4378         8055 my $i2 = $that->_trits_desc($i);
323 4378   100     11719 while ($carry->is_nil && $i--) {
324 6324         12341 my $cmp = $op[$i & 1];
325 6324         9556 $carry = $i1->()->$cmp($i2->());
326             }
327 4378 100       14079 return $map? $carry->$map: $carry;
328             }
329 90         398 }
330              
331             # binary sorting factory
332             # takes numerical comparison name and one or more rank numbers
333             # resulting op takes two numerical arguments
334             # and returns the selected item or items from those
335             sub _sort2 {
336 135     135   284 my ($cmp, @sel) = @_;
337             return sub {
338 75 100   75   39864 croak 'missing arguments' if @_ < 2;
339 66 100       306 croak 'array context expected' if !wantarray;
340 63         130 my ($this, $that) = @_;
341 63         166 my $rel = $this->$cmp($that);
342 63 100       168 my @items = $rel->is_true? ($that, $this): ($this, $that);
343 63         210 return @items[@sel];
344 135         620 };
345             }
346              
347             # ternary sorting factory
348             # takes numerical comparison name and one or more rank numbers
349             # resulting op takes three numerical arguments
350             # and returns the selected item or items from those
351             sub _sort3 {
352 180     180   307 my $cmp = shift;
353 180         374 my @sel = qw(cmin cmed cmax)[@_];
354             return sub {
355 156 100   156   75965 croak 'missing arguments' if @_ < 3;
356 143 100 100     606 croak 'array context expected' if 1 < @sel && !wantarray;
357 140         347 my $r01 = $_[0]->$cmp($_[1]);
358 140         358 my $r02 = $_[0]->$cmp($_[2]);
359 140         318 my $r12 = $_[1]->$cmp($_[2]);
360 140         237 return map { $r01->$_($r02, $r12)->Mpx(@_) } @sel;
  210         513  
361 180         880 };
362             }
363              
364             sub _w2i2w {
365 45     45   154 my ($sfx) = @_;
366 45 100       136 if ('' ne $sfx) {
367 30         63 $sfx = "_$sfx";
368             }
369 45         158 return ("as_int$sfx", "convert_int$sfx");
370             }
371              
372             # create an object like left side holding least significant trits of right side
373             sub _truncate {
374 30     30   43 my ($this, $that) = @_;
375 30         44 my $size = $this->Trits;
376 30         53 my @trits = $that->Rtrits;
377 30 50       49 if ($size < @trits) {
378 0         0 splice @trits, $size;
379 0   0     0 while (@trits && $trits[-1]->is_nil) {
380 0         0 pop @trits;
381             }
382             }
383 30         71 return bless [$size, \@trits], ref $this;
384             }
385              
386             # COMING_UP (emulated with binary arithmetic for now)
387             sub _divmod {
388 45     45   82 my ($sfx) = @_;
389 45         83 my $ldiv = "Ldiv$sfx";
390             return sub {
391 10 100   10   339 croak 'missing arguments' if @_ < 2;
392 7 100       166 croak 'array context expected' if !wantarray;
393 4         7 my ($this, $that) = @_;
394 4         9 my ($lsw, $msw, $rem, $err) = $this->$ldiv($zero, $that);
395 4 50 66     10 if ($err->is_nil && $msw->Rtrits) {
396 0         0 $err = $two;
397             }
398 4         14 return ($lsw, $rem, $err);
399 45         248 };
400             }
401              
402             # COMING_UP (emulated with binary arithmetic for now)
403             sub _long_divmod {
404 45     45   82 my ($sfx) = @_;
405 45         124 my ($as_int, $convert) = _w2i2w($sfx);
406             return sub {
407 13 100   13   379 croak 'missing arguments' if @_ < 3;
408 10 100       173 croak 'array context expected' if !wantarray;
409 7         11 my ($this, $over, $that) = @_;
410 7         12 my $size = $this->Trits;
411 7         15 $over = $this->_truncate($over);
412 7         10 $that = $this->_truncate($that);
413 7         16 my $acc = Math::Logic::Ternary::Word->from_words(
414             $size * 2 + 1, $this, $over
415             );
416 7         17 my $num = $acc->$as_int;
417 7         13 my $den = $that->$as_int;
418 7 100       18 return ($this, $over, $that, $one) if 0 == $den;
419 5         6 my $rem;
420 5 100       9 if ($den < 0) {
421 2         4 $rem = $num % -$den;
422 2 100       6 if ($rem) {
423 1         2 $rem += $den;
424             }
425             }
426             else {
427 3         5 $rem = $num % $den;
428             }
429 5         8 my $quot = ($num - $rem) / $den;
430 5         11 my ($lsw, $msw, $xsw) = $acc->$convert($quot)->Words($size);
431 5 50       14 return ($lsw, $msw, $this->$convert($rem), $xsw->Rtrits? $two: $zero);
432 45         338 };
433             }
434              
435             sub _load_generated_methods {
436 15     15   75 my @ops = Math::Logic::Ternary::Trit->trit_operators;
437 15         49 foreach my $opr (@ops) {
438 1425         2036 my ($name, $argc) = @{$opr};
  1425         2412  
439 1425         2360 _declare($name, _tritwise($argc, $name));
440 1425         2701 _declare(uc($name), _logical($argc, $name));
441             }
442 15         47 *Neg = \¬
443 15         51 *Negv = _cascading(1, 'dpl', 'negcv');
444 15         43 foreach my $sfx ('', 'u') {
445 30         112 my $asc = "asc$sfx";
446 30         58 my $cmp = "cmp$sfx";
447 30         95 _declare(ucfirst($asc), _lexical($asc));
448 30         91 _declare(ucfirst($cmp), _lexical($cmp));
449 30         67 foreach my $cr (@comparison_relations) {
450 120         183 my ($rel, $map) = @{$cr};
  120         227  
451 120         241 _declare(ucfirst("$rel$sfx"), _lexical($cmp, $map));
452             }
453             }
454 15         51 *Cmpv = _cmpv();
455 15         49 *Ascv = _cmpv('not');
456 15         82 foreach my $cr (@comparison_relations) {
457 60         100 my ($rel, $map) = @{$cr};
  60         111  
458 60         146 _declare(ucfirst($rel . 'v'), _cmpv($map));
459             }
460 15         39 foreach my $sfx ('', 'u', 'v') {
461 45         99 my $Cmp = "Cmp$sfx";
462 45         128 _declare("Sort2$sfx", _sort2($Cmp, 0..1));
463 45         116 _declare( "Tlr$sfx", _sort2($Cmp, 0));
464 45         119 _declare( "Tgr$sfx", _sort2($Cmp, 1));
465 45         140 _declare("Sort3$sfx", _sort3($Cmp, 0..2));
466 45         123 _declare( "Min$sfx", _sort3($Cmp, 0));
467 45         117 _declare( "Med$sfx", _sort3($Cmp, 1));
468 45         122 _declare( "Max$sfx", _sort3($Cmp, 2));
469 45         151 _declare( "Incr$sfx", _cascading(1, 'incr', "incc$sfx", $one));
470 45         171 _declare( "Decr$sfx", _cascading(1, 'decr', "decc$sfx", $one));
471 45         132 _declare( "Dpl$sfx", _cascading(1, 'dpl', "dplc$sfx"));
472 45         143 _declare( "Add$sfx", _cascading(2, 'add', "addc$sfx"));
473 45         127 _declare( "Subt$sfx", _cascading(2, 'subt', "subc$sfx"));
474 45         125 _declare( "Div$sfx", _divmod($sfx));
475 45         126 _declare( "Ldiv$sfx", _long_divmod($sfx));
476 45 100       150 next if 'v' eq $sfx; # ops below are not not for base(-3)
477             # _declare( "Hlv$sfx",
478             # _ext_casc(1, 'hlv', "hlvc$sfx", "hlvs$sfx", "Add$sfx"));
479             # _declare( "Amn$sfx",
480             # _ext_casc(2, 'amn', "amnc$sfx", "amns$sfx", "Add$sfx"));
481 30         102 _declare( "Sum$sfx", _cascading(3, 'sum', "sumc$sfx"));
482             }
483 15         531 return;
484             }
485              
486             # ----- class methods -----
487              
488             sub from_trits {
489 7416     7416 1 20456 my ($class, $size, @trits) = @_;
490 7416 100       13469 if (!$size) {
491 5 100       248 croak 'missing arguments' if !@trits;
492 3         7 $size = @trits;
493             }
494 7414 100 100     25579 croak 'illegal size, use 1..' . MAX_SIZE if $size < 1 || MAX_SIZE < $size;
495 7412   100     21405 while (@trits && $trits[-1]->is_nil) {
496 2112         5467 pop @trits;
497             }
498 7412 100       15524 croak "too many trits for word size $size" if $size < @trits;
499 7410         36560 return bless [$size, \@trits], $class;
500             }
501              
502             sub from_words {
503 75     75 1 481 my ($class, $size, @words) = @_;
504 75         144 return $class->from_trits($size, map { $_->Trits } @words);
  346         580  
505             }
506              
507             sub from_bools {
508 2     2 1 8 my ($class, $size, @bools) = @_;
509 2         7 my @trits = map { Math::Logic::Ternary::Trit->from_bool($_) } @bools;
  6         16  
510 2         5 return $class->from_trits($size, @trits);
511             }
512              
513             sub from_int {
514 1323     1323 1 5293 my ($class, $size, $int) = @_;
515 1323         2680 return $class->_parse_int($size, $int, 'as_int', 3);
516             }
517              
518             sub from_int_u {
519 846     846 1 2265 my ($class, $size, $int) = @_;
520 846 100       1698 croak 'negative number has no unbalanced representation' if $int < 0;
521 845         2390 return $class->_parse_int($size, $int, 'as_int_u', 3);
522             }
523              
524             sub from_int_v {
525 909     909 1 1562 my ($class, $size, $int) = @_;
526 909         1930 return $class->_parse_int($size, $int, 'as_int_u', -3);
527             }
528              
529             sub from_base27 {
530 6     6 1 14 my ($class, $size, $string) = @_;
531 6 100       24 if (BASE27_PREFIX eq substr $string, 0, 1) {
532 1         4 $string = substr $string, 1;
533             }
534 6         18 return $class->_parse_base($size, $string, 27, \%base27_words);
535             }
536              
537             sub from_string {
538 34     34 1 2399 my ($class, $size, $string) = @_;
539 34         91 my $prefix = substr $string, 0, 1;
540 34 100       80 if (BASE3_PREFIX eq $prefix) {
541 13         27 $string = substr $string, 1;
542 13         57 return $class->_parse_base($size, $string, 3, \%base3_trits);
543             }
544 21 100       55 if (BASE27_PREFIX eq $prefix) {
545 11         23 $string = substr $string, 1;
546 11         32 return $class->_parse_base($size, $string, 27, \%base27_words);
547             }
548 10 100       27 if (TRIT_PREFIX eq $prefix) {
549 4         28 my $trit = Math::Logic::Ternary::Trit->from_string($string);
550 4 100       22 return $size? $class->from_trits($size, $trit): $trit;
551             }
552 6         26 return $class->_parse_base($size, $string, 3, \%base3_trits);
553             }
554              
555             sub from_modint {
556 5     5 1 14 my ($class, $size, $modint) = @_;
557 5         18 $size = _check_modint_modulus($size, $modint);
558 3         17 return $class->from_int($size, $modint->signed_residue);
559             }
560              
561             sub from_modint_u {
562 2     2 1 7 my ($class, $size, $modint) = @_;
563 2         7 $size = _check_modint_modulus($size, $modint);
564 2         8 return $class->from_int_u($size, $modint->residue);
565             }
566              
567             sub from_modint_v {
568 2     2 1 6 my ($class, $size, $modint) = @_;
569 2         7 $size = _check_modint_modulus($size, $modint);
570 2         5 my $modulus = _modulus($size);
571 2         10 my $max_int_v = _max_int_v($size);
572 2         12 my $residue = $modint->residue;
573 2         20 my $is_neg = $max_int_v < $residue;
574 2 100       98 $residue -= $modulus if $is_neg;
575 2         180 return $class->from_int_v($size, $residue);
576             }
577              
578             sub from_various {
579 478     478 1 1313 my ($class, $size, @args) = @_;
580 478 100       1359 return $class->from_trits($size) if !@args;
581 160         246 my $arg = $args[0];
582 160         354 my $type = blessed $arg;
583 160 100       308 if ($type) {
584             return $class->from_words($size, @args)
585 40 100       76 if eval { $type->DOES('Math::Logic::Ternary::Object') };
  40         189  
586 6 100       167 croak qq{cannot convert multiple "$type" objects into ternary word}
587             if 1 < @args;
588             return $class->from_int($size, $arg)
589 5 100       9 if eval { $type->isa('Math::BigInt') };
  5         39  
590             return $class->from_modint($size, $arg)
591 4 100       9 if eval { $type->isa('Math::ModInt') };
  4         29  
592 1         86 croak qq{cannot convert "$type" object into ternary word};
593             }
594 120         174 $type = ref $arg;
595 120 100       1806 croak qq{cannot convert $type reference into ternary word} if $type;
596 119 100       212 return $class->from_bools($size, @args) if 1 < @args;
597 118         223 my $prefix = substr $arg, 0, 1;
598 118 100       363 return $class->from_int($size, $arg) if $prefix =~ /^[\+\-\d]/;
599 24         72 return $class->from_string($size, $arg);
600             }
601              
602             sub word_operators {
603             return map {
604 5     5 1 869 my ($name, $sig, $rsig, @more) = @{$_};
  365         440  
  365         606  
605 365         450 my $min_args = $sig =~ tr/A-Z//;
606 365         507 my $var_args = $sig =~ tr/a-z//;
607 365         440 my $ret_vals = $rsig =~ tr/A-Z//;
608 365         812 [$name, $min_args, $var_args, $ret_vals, @more]
609             } @word_operators;
610             }
611              
612 5     5 1 2357 sub word_formatters { map { [@{$_}] } @word_formatters }
  40         62  
  40         101  
613              
614             # ----- object methods -----
615              
616             sub convert_trits {
617 3655     3655 1 5754 my $this = shift;
618 3655         6101 my $size = $this->Trits;
619 3655         11354 return ref($this)->from_trits($size, @_);
620             }
621              
622             sub convert_words {
623 2     2 1 740 my $this = shift;
624 2         5 my $size = $this->Trits;
625 2         10 return ref($this)->from_words($size, @_);
626             }
627              
628             sub convert_bools {
629 1     1 1 4 my $this = shift;
630 1         6 my $size = $this->Trits;
631 1         7 return ref($this)->from_bools($size, @_);
632             }
633              
634             sub convert_int {
635 816     816 1 390303 my ($this, $int) = @_;
636 816         1698 my $size = $this->Trits;
637 816         2606 return ref($this)->from_int($size, $int);
638             }
639              
640             sub convert_int_u {
641 841     841 1 343970 my ($this, $int) = @_;
642 841         1825 my $size = $this->Trits;
643 841         2500 return ref($this)->from_int_u($size, $int);
644             }
645              
646             sub convert_int_v {
647 905     905 1 359575 my ($this, $int) = @_;
648 905         1851 my $size = $this->Trits;
649 905         2578 return ref($this)->from_int_v($size, $int);
650             }
651              
652             sub convert_modint {
653 2     2 1 6528 my ($this, $modint) = @_;
654 2         9 my $size = $this->Trits;
655 2         15 return ref($this)->from_modint($size, $modint);
656             }
657              
658             sub convert_modint_u {
659 2     2 1 3963 my ($this, $modint) = @_;
660 2         8 my $size = $this->Trits;
661 2         10 return ref($this)->from_modint_u($size, $modint);
662             }
663              
664             sub convert_modint_v {
665 2     2 1 4305 my ($this, $modint) = @_;
666 2         8 my $size = $this->Trits;
667 2         10 return ref($this)->from_modint_v($size, $modint);
668             }
669              
670             sub convert_base27 {
671 6     6 1 868 my ($this, $string) = @_;
672 6         14 my $size = $this->Trits;
673 6         22 return ref($this)->from_base27($size, $string);
674             }
675              
676             sub convert_string {
677 6     6 1 1547 my ($this, $string) = @_;
678 6         14 my $size = $this->Trits;
679 6         21 return ref($this)->from_string($size, $string);
680             }
681              
682             sub convert_various {
683 19     19 1 2812 my $this = shift;
684 19         60 my $size = $this->Trits;
685 19         87 return ref($this)->from_various($size, @_);
686             }
687              
688             sub is_equal {
689 37     37 1 6026 my ($this, $that) = @_;
690 37         63 my $rtrits = $this->[W_TRITS];
691 37 100       55 return q[] if @{$rtrits} != $that->Rtrits;
  37         87  
692 35         55 my $pos = @{$rtrits};
  35         55  
693 35         79 while (--$pos >= 0) {
694 94 100       191 return q[] if $rtrits->[$pos]->as_int != $that->Trit($pos)->as_int;
695             }
696 33         111 return 1;
697             }
698              
699             sub Rtrits {
700 113     113 1 543 my ($this) = @_;
701 113         156 my $rtrits = $this->[W_TRITS];
702 113         131 return @{$rtrits};
  113         293  
703             }
704              
705             sub Sign {
706 116     116 1 1653 my ($this) = @_;
707 116         170 my $rtrits = $this->[W_TRITS];
708 116 100       146 return @{$rtrits}? $rtrits->[-1]: $zero;
  116         414  
709             }
710              
711             sub Signu {
712 2     2 1 8 my ($this) = @_;
713 2         7 my $rtrits = $this->[W_TRITS];
714 2 100       5 return @{$rtrits}? $one: $zero;
  2         15  
715             }
716              
717             sub Signv {
718 3     3 1 10 my ($this) = @_;
719 3         8 my $rtrits = $this->[W_TRITS];
720 3 100       7 return @{$rtrits} & 1? $one: @{$rtrits}? $two: $zero;
  3 100       16  
  2         16  
721             }
722              
723             sub Trit {
724 51424     51424 1 73408 my ($this, $pos) = @_;
725 51424         60682 my ($size, $trits) = @{$this}[W_SIZE, W_TRITS];
  51424         80000  
726 51424 100       84955 if ($pos < 0) {
727 10         13 $pos += $size;
728 10 100       21 return $zero if $pos < 0;
729             }
730 51423 100       59276 return $pos < @{$trits}? $trits->[$pos]: $zero;
  51423         136826  
731             }
732              
733             sub Trits {
734 24036     24036 1 41914 my ($this) = @_;
735 24036         32025 my ($size, $trits) = @{$this}[W_SIZE, W_TRITS];
  24036         45638  
736 24036 100       55466 return $size if !wantarray;
737 4101         5078 return (@{$trits}, ($zero) x ($size - @{$trits}));
  4101         6114  
  4101         9783  
738             }
739              
740             sub Words {
741 81     81 1 189 my ($this, $size) = @_;
742 81         171 my $class = ref $this;
743 81         197 my @trits = $this->Trits;
744 81         158 my @words = ();
745 81         203 while (@trits) {
746 282         687 push @words, $class->from_trits($size, splice @trits, 0, $size);
747             }
748 81         248 return @words;
749             }
750              
751 7686     7686 1 26173 sub as_int { $_[0]->_as_int('as_int', 3) }
752 3495     3495 1 10298 sub as_int_u { $_[0]->_as_int('as_int_u', 3) }
753 681     681 1 2957 sub as_int_v { $_[0]->_as_int('as_int_u', -3) }
754 18     18 1 58 sub res_mod3 { $_[0]->Trit(0)->res_mod3 }
755              
756 2     2 1 293 sub as_modint { $_[0]->_as_modint($_[0]->as_int ) }
757 2     2 1 10 sub as_modint_u { $_[0]->_as_modint($_[0]->as_int_u) }
758 2     2 1 11 sub as_modint_v { $_[0]->_as_modint($_[0]->as_int_v) }
759              
760             sub as_base27 {
761 71     71 1 1465 my ($this) = @_;
762 71         187 my @ints = map { $_->as_int } $this->Words(3);
  259         543  
763 71         551 return scalar reverse @base27_chars[@ints], BASE27_PREFIX;
764             }
765              
766             sub as_string {
767 244     244 1 81278 my ($this) = @_;
768 244         456 my @ints = map { $_->as_int_u } $this->Trits;
  2070         3330  
769 244         1039 return scalar reverse @base3_chars[@ints], BASE3_PREFIX;
770             }
771              
772             sub Mul {
773 6 100   6 1 120 croak 'missing arguments' if @_ < 2;
774 5 100       62 croak 'array context expected' if !wantarray;
775 4         8 my ($this, $that, $over) = (@_, $zero);
776 4         16 $that = $this->_truncate($that);
777 4         8 $over = $this->_truncate($over);
778 4         8 my $size = $this->Trits;
779 4         6 my @trits = $over->Rtrits;
780 4         6 my $i = 0;
781 4         6 foreach my $a ($this->Rtrits) {
782 10         14 my $j = $i;
783 10         11 my $c = $zero;
784 10         15 foreach my $b ($that->Rtrits) {
785 23         34 my $p = $a->eqv($b);
786 23 100       40 if ($j < @trits) {
787 11         13 my $t = $trits[$j];
788 11         20 $trits[$j] = $p->add( $c, $t);
789 11         21 $c = $p->addc($c, $t);
790             }
791             else {
792 12         22 $trits[$j] = $p->incr($c);
793 12         24 $c = $p->incc($c);
794             }
795 23         37 ++$j;
796             }
797 10         20 while (!$c->is_nil) {
798 1 50       3 if ($j < @trits) {
799 0         0 my $t = $trits[$j];
800 0         0 $trits[$j] = $t->incr($c);
801 0         0 $c = $t->incc($c);
802             }
803             else {
804 1         3 $trits[$j] = $c;
805 1         2 $c = $zero;
806             }
807 1         2 ++$j;
808             }
809 10         18 ++$i;
810             }
811 4         5 my @lst = splice @trits, 0, $size;
812 4         9 return ($this->convert_trits(@lst), $this->convert_trits(@trits));
813             }
814              
815             sub Mulu {
816 6 100   6 1 122 croak 'missing arguments' if @_ < 2;
817 5 100       61 croak 'array context expected' if !wantarray;
818 4         8 my ($this, $that, $over) = (@_, $zero);
819 4         9 $that = $this->_truncate($that);
820 4         8 $over = $this->_truncate($over);
821 4         7 my $size = $this->Trits;
822 4         7 my @trits = $over->Rtrits;
823 4         6 my $i = 0;
824 4         6 foreach my $a ($this->Rtrits) {
825 10         13 my $j = $i;
826 10         12 my $c = $zero;
827 10         16 foreach my $b ($that->Rtrits) {
828 23         40 my $p = $a->eqv($b);
829 23         40 my $pc = $a->mulcu($b);
830 23 100       39 if ($j < @trits) {
831 11         12 my $t = $trits[$j];
832 11         21 $trits[$j] = $p->add( $c, $t);
833 11         22 $c = $p->addcu($c, $t)->incr($pc);
834             }
835             else {
836 12         21 $trits[$j] = $p->incr( $c);
837 12         22 $c = $p->inccu($c)->incr($pc);
838             }
839 23         37 ++$j;
840             }
841 10         17 while (!$c->is_nil) {
842 0 0       0 if ($j < @trits) {
843 0         0 my $t = $trits[$j];
844 0         0 $trits[$j] = $t->incr( $c);
845 0         0 $c = $t->inccu($c);
846             }
847             else {
848 0         0 $trits[$j] = $c;
849 0         0 $c = $zero;
850             }
851 0         0 ++$j;
852             }
853 10         46 ++$i;
854             }
855 4         9 my @lst = splice @trits, 0, $size;
856 4         9 return ($this->convert_trits(@lst), $this->convert_trits(@trits));
857             }
858              
859             sub Mulv {
860 2 100   2 1 106 croak 'missing arguments' if @_ < 2;
861 1 50       55 croak 'array context expected' if !wantarray;
862 0         0 my ($this, $that, $over) = (@_, $zero);
863 0         0 $that = $this->_truncate($that);
864 0         0 $over = $this->_truncate($over);
865 0         0 my $size = $this->Trits;
866 0         0 my @trits = $over->Rtrits;
867 0         0 my $i = 0;
868 0         0 foreach my $a ($this->Rtrits) {
869 0         0 my $j = $i;
870 0         0 foreach my $b ($that->Rtrits) {
871 0         0 my $p = $a->eqv($b);
872 0         0 my $pc = $a->mulcu($b)->not;
873 0 0       0 my $t = $j < @trits? $trits[$j]: $zero;
874 0         0 $trits[$j] = $t->incr($p);
875 0         0 my $c = $t->inccu($p)->not;
876 0         0 my $k = $j;
877 0   0     0 while (!$c->is_nil || !$pc->is_nil) {
878 0 0       0 $t = ++$k < @trits? $trits[$k]: $zero;
879 0         0 $trits[$k] = $t->add($pc, $c);
880 0         0 $c = $t->addcx($pc, $c);
881 0         0 $pc = $zero;
882             }
883 0         0 ++$j;
884             }
885 0         0 ++$i;
886             }
887 0         0 my @lst = splice @trits, 0, $size;
888 0         0 my @mst = splice @trits, 0, $size;
889 0         0 my ($tt) = (@trits, $zero);
890 0         0 return ($this->convert_trits(@lst), $this->convert_trits(@mst), $tt);
891             }
892              
893 13     13 1 84 sub Mpx { (shift)->Sign->Mpx(@_) }
894              
895             # generic logical operator
896             sub GENERIC {
897 9     9 1 20 my ($this, $op, @args) = @_;
898 9         18 return $this->Sign->generic($op, map { $_->Sign } @args);
  9         17  
899             }
900              
901             # generic tritwise operator
902             sub generic {
903 1     1 1 6 my ($this, $op, @argws) = @_;
904 1         11 my $trit_op = Math::Logic::Ternary::Trit->make_generic($op);
905 1         5 my @args = map { _trits_asc($_) } @argws;
  1         4  
906 1         6 my @trits = map { $trit_op->($_, map { $_->() } @args) } $this->Trits;
  9         15  
  9         15  
907 1         6 return $this->convert_trits(@trits);
908             }
909              
910             sub Lshift {
911 19 100   19 1 175 croak 'array context expected' if !wantarray;
912 18         26 my ($this, $carry) = (@_, $zero);
913 18         27 my @trits = ($carry, $this->Trits);
914 18         22 $carry = pop @trits;
915 18         29 return ($this->convert_trits(@trits), $carry);
916             }
917              
918             sub Rshift {
919 19 100   19 1 147 croak 'array context expected' if !wantarray;
920 18         21 my $this = shift;
921 18 100       36 my ($carry, @trits) = @_? ($this->Trits, shift): ($this->Rtrits, $zero);
922 18         29 return ($this->convert_trits(@trits), $carry);
923             }
924              
925 1     1 1 4 sub min_int { $_[0]->sf }
926 1     1 1 3 sub max_int { $_[0]->st }
927 1     1 1 3 sub min_int_u { $_[0]->sn }
928 1     1 1 3 sub max_int_u { $_[0]->sf }
929              
930             sub min_int_v {
931 2     2 1 4 my ($this) = @_;
932 2         5 my @trits = map { ($zero, $two)[$_ & 1] } 0 .. $this->Trits - 1;
  15         22  
933 2         6 return $this->convert_trits(@trits);
934             }
935              
936             sub max_int_v {
937 2     2 1 4 my ($this) = @_;
938 2         5 my @trits = map { ($two, $zero)[$_ & 1] } 0 .. $this->Trits - 1;
  15         22  
939 2         5 return $this->convert_trits(@trits);
940             }
941              
942             1;
943             __END__