File Coverage

blib/lib/Math/PlanePath/ImaginaryBase.pm
Criterion Covered Total %
statement 122 152 80.2
branch 22 32 68.7
condition 6 6 100.0
subroutine 17 24 70.8
pod 10 10 100.0
total 177 224 79.0


line stmt bran cond sub pod time code
1             # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde
2              
3             # This file is part of Math-PlanePath.
4             #
5             # Math-PlanePath is free software; you can redistribute it and/or modify
6             # it under the terms of the GNU General Public License as published by the
7             # Free Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # Math-PlanePath is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13             # for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Math-PlanePath. If not, see .
17              
18              
19             # math-image --path=ImaginaryBase --lines --scale=10
20             # math-image --path=ImaginaryBase --all --output=numbers_dash --size=80x50
21             #
22             # cf A005351 positives as negabinary index
23             # A005352 negatives as negabinary index
24             # A039724 positives as negabinary index, in binary
25             # A027615 negabinary bit count
26             # = 3 * A072894(n+1) - 2n - 3
27             # A098725 first diffs of A072894
28             # A000695 same value binary and negabinary, being base 4 digits 0,1
29             # A001045 abs(negabinary) of 0b11111 all ones (2^n-(-1)^n)/3
30             # A185269 negabinary primes
31             #
32             # A073785 positives as -3 index
33             # A007608 positives as -4 index
34             # A073786 -5
35             # A073787 -6
36             # A073788 -7
37             # A073789 -8
38             # A073790 -9
39             # A039723 positives as negadecimal index
40             # A051022 same value integer and negadecimal, 0s between digits
41             #
42             # http://mathworld.wolfram.com/Negabinary.html
43             # http://mathworld.wolfram.com/Negadecimal.html
44              
45             package Math::PlanePath::ImaginaryBase;
46 4     4   8123 use 5.004;
  4         20  
47 4     4   31 use strict;
  4         8  
  4         263  
48             #use List::Util 'min','max';
49             *min = \&Math::PlanePath::_min;
50             *max = \&Math::PlanePath::_max;
51              
52 4     4   26 use vars '$VERSION', '@ISA';
  4         19  
  4         241  
53             $VERSION = 128;
54 4     4   581 use Math::PlanePath;
  4         8  
  4         225  
55             @ISA = ('Math::PlanePath');
56             *_divrem_mutate = \&Math::PlanePath::_divrem_mutate;
57              
58             use Math::PlanePath::Base::Generic
59 4         233 'is_infinite',
60 4     4   25 'round_nearest';
  4         7  
61             use Math::PlanePath::Base::Digits
62 4         242 'parameter_info_array', # radix parameter
63             'round_down_pow',
64             'round_up_pow',
65             'digit_split_lowtohigh',
66 4     4   392 'digit_join_lowtohigh';
  4         7  
67              
68 4     4   1349 use Math::PlanePath::ZOrderCurve;
  4         9  
  4         171  
69             *_digit_interleave = \&Math::PlanePath::ZOrderCurve::_digit_interleave;
70              
71             # uncomment this to run the ### lines
72             #use Smart::Comments;
73              
74              
75 4     4   24 use constant n_start => 0;
  4         8  
  4         235  
76 4     4   23 use constant xy_is_visited => 1;
  4         8  
  4         174  
77 4     4   21 use constant absdx_minimum => 1; # X coord always changes
  4         8  
  4         5164  
78              
79             sub x_negative_at_n {
80 0     0 1 0 my ($self) = @_;
81 0         0 return $self->{'radix'}**2;
82             }
83             sub y_negative_at_n {
84 0     0 1 0 my ($self) = @_;
85 0         0 return $self->{'radix'}**3;
86             }
87              
88             sub dir_maximum_dxdy {
89 0     0 1 0 my ($self) = @_;
90 0         0 return ($self->{'radix'}-1, -2);
91             }
92              
93             sub turn_any_straight {
94 0     0 1 0 my ($self) = @_;
95 0         0 return ($self->{'radix'} != 2); # radix=2 never straight
96             }
97             sub _UNDOCUMENTED__turn_any_left_at_n {
98 0     0   0 my ($self) = @_;
99 0         0 return $self->{'radix'} - 1;
100             }
101             sub _UNDOCUMENTED__turn_any_right_at_n {
102 0     0   0 my ($self) = @_;
103 0         0 return $self->{'radix'};
104             }
105              
106              
107              
108             #------------------------------------------------------------------------------
109             sub new {
110 9     9 1 947 my $self = shift->SUPER::new(@_);
111              
112 9         25 my $radix = $self->{'radix'};
113 9 100 100     39 if (! defined $radix || $radix <= 2) { $radix = 2; }
  4         8  
114 9         17 $self->{'radix'} = $radix;
115              
116 9         17 return $self;
117             }
118              
119             sub n_to_xy {
120 100     100 1 10208 my ($self, $n) = @_;
121             ### ImaginaryBase n_to_xy(): $n
122              
123 100 50       211 if ($n < 0) { return; }
  0         0  
124 100 50       225 if (is_infinite($n)) { return ($n,$n); }
  0         0  
125              
126             # ENHANCE-ME: lowest non-(r-1) digit determines direction to next, or
127             # something like that
128             {
129 100         144 my $int = int($n);
  100         123  
130             ### $int
131             ### $n
132 100 50       168 if ($n != $int) {
133 0         0 my ($x1,$y1) = $self->n_to_xy($int);
134 0         0 my ($x2,$y2) = $self->n_to_xy($int+1);
135 0         0 my $frac = $n - $int; # inherit possible BigFloat
136 0         0 my $dx = $x2-$x1;
137 0         0 my $dy = $y2-$y1;
138 0         0 return ($frac*$dx + $x1, $frac*$dy + $y1);
139             }
140 100         135 $n = $int; # BigFloat int() gives BigInt, use that
141             }
142              
143 100         145 my $radix = $self->{'radix'};
144 100         110 my $x = 0;
145 100         111 my $y = 0;
146 100         117 my $len = ($n*0)+1; # inherit bignum 1
147              
148 100 50       206 if (my @digits = digit_split_lowtohigh($n, $radix)) {
149 100         144 $radix = -$radix;
150 100         120 for (;;) {
151 180         216 $x += (shift @digits) * $len; # digits low to high
152 180 100       301 @digits || last;
153              
154 122         156 $y += (shift @digits) * $len; # digits low to high
155 122 100       246 @digits || last;
156              
157 80         93 $len *= $radix; # $radix negative negates each time
158             }
159             }
160              
161             ### final: "$x,$y"
162 100         190 return ($x,$y);
163             }
164              
165             # ($x-$digit) and ($y-$digit) are multiples of $radix, but apply int() in
166             # case floating point rounding
167             #
168             sub xy_to_n {
169 1385     1385 1 29391 my ($self, $x, $y) = @_;
170             ### ImaginaryBase xy_to_n(): "$x, $y"
171              
172 1385         2320 $x = round_nearest ($x);
173 1385 50       2282 if (is_infinite($x)) { return ($x); }
  0         0  
174              
175 1385         2573 $y = round_nearest ($y);
176 1385 50       2292 if (is_infinite($y)) { return ($y); }
  0         0  
177              
178 1385         2238 my $radix = $self->{'radix'};
179 1385         1773 my $zero = ($x * 0 * $y); # inherit bignum 0
180 1385         1678 my @n; # digits low to high
181              
182 1385   100     2444 while ($x || $y) {
183             ### at: "x=$x,y=$y n=".join(',',@n)
184              
185 2962         4682 push @n, _divrem_mutate ($x, $radix);
186 2962         3724 $x = -$x;
187 2962         4644 push @n, _divrem_mutate ($y, $radix);
188 2962         6465 $y = -$y;
189             }
190 1385         2629 return digit_join_lowtohigh (\@n,$radix, $zero);
191             }
192              
193             # left xmax = (r-1) + (r^2 -r) + (r^3-r^2) + ... + (r^k - r^(k-1))
194             # = r^(k-1) - 1
195             #
196             # right xmin = - (r + r^3 + ... + r^(2k+1))
197             # = -r * (1 + r^2 + ... + r^2k)
198             # = -r * ((r^2)^(k+1) -1) / (r^2 - 1)
199             #
200              
201             # exact
202             sub rect_to_n_range {
203 104     104 1 6407 my ($self, $x1,$y1, $x2,$y2) = @_;
204             ### ImaginaryBase rect_to_n_range(): "$x1,$y1 $x2,$y2"
205              
206 104         232 $x1 = round_nearest($x1);
207 104         175 $y1 = round_nearest($y1);
208 104         153 $x2 = round_nearest($x2);
209 104         152 $y2 = round_nearest($y2);
210              
211 104         172 my $zero = $x1 * 0 * $y1 * $x2 * $y2;
212 104         1918 my $radix = $self->{'radix'};
213              
214 104         164 my ($min_xdigits, $max_xdigits)
215             = _negaradix_range_digits_lowtohigh($x1,$x2, $radix);
216 104 100       180 unless (defined $min_xdigits) {
217 2         9 return (0, $max_xdigits); # infinity
218             }
219              
220 102         146 my ($min_ydigits, $max_ydigits)
221             = _negaradix_range_digits_lowtohigh($y1,$y2, $radix);
222 102 100       182 unless (defined $min_ydigits) {
223 2         9 return (0, $max_ydigits); # infinity
224             }
225              
226             ### $min_xdigits
227             ### $max_xdigits
228             ### min_x: digit_join_lowtohigh ($min_xdigits, $radix, $zero)
229             ### max_x: digit_join_lowtohigh ($max_xdigits, $radix, $zero)
230             ### $min_ydigits
231             ### $max_ydigits
232             ### min_y: digit_join_lowtohigh ($min_ydigits, $radix, $zero)
233             ### max_y: digit_join_lowtohigh ($max_ydigits, $radix, $zero)
234              
235 100         208 my @min_digits = _digit_interleave ($min_xdigits, $min_ydigits);
236 100         177 my @max_digits = _digit_interleave ($max_xdigits, $max_ydigits);
237              
238             ### final ...
239             ### @min_digits
240             ### @max_digits
241              
242 100         221 return (digit_join_lowtohigh (\@min_digits, $radix, $zero),
243             digit_join_lowtohigh (\@max_digits, $radix, $zero));
244             }
245              
246              
247              
248             # Return arrayrefs ($min_digits, $max_digits) which are the digits making
249             # up the index range for negaradix values $x1 to $x2 inclusive.
250             # The arrays are lowtohigh, so $min_digits->[0] is the least significant digit.
251             #
252             sub _negaradix_range_digits_lowtohigh {
253 302     302   470 my ($x1,$x2, $radix) = @_;
254             ### _negaradix_range_digits(): "$x1,$x2 radix=$radix"
255              
256 302 100       505 if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } # make x1 <= x2
  26         353  
257              
258 302         659 my $radix_minus_1 = $radix - 1;
259             ### $radix
260             ### $radix_minus_1
261              
262              
263 302         474 my ($len, $level, $min_base) = _negaradix_range_level ($x1,$x2, $radix);
264             ### $len
265             ### $level
266 302 100       2093 if (is_infinite($level)) {
267 4         1387 return (undef, $level);
268             }
269 298         455 my $max_base = $min_base;
270              
271             ### assert: $min_base <= $x1
272             ### assert: $min_base + $len > $x2
273              
274 298         401 my @min_digits; # digits formed high to low, stored low to high
275             my @max_digits;
276 298         499 while (--$level > 0) {
277 356         457 $len /= $radix;
278             ### at: "len=$len reverse"
279              
280             # reversed digits, x1 low end for max, x2 high end for min
281             {
282 356         730 my $digit = max (0,
283             min ($radix_minus_1,
284             int (($x2 - $min_base) / $len)));
285             ### min base: $min_base
286             ### min diff: $x2-$min_base
287             ### min digit raw: $digit
288             ### min digit reversed: $radix_minus_1 - $digit
289 356         535 $min_base += $digit * $len;
290 356         561 $min_digits[$level] = $radix_minus_1 - $digit;
291             }
292             {
293 356         399 my $digit = max (0,
  356         411  
  356         688  
294             min ($radix_minus_1,
295             int (($x1 - $max_base) / $len)));
296             ### max base: $max_base
297             ### max diff: $x1-$max_base
298             ### max digit raw: $digit
299             ### max digit reversed: $radix_minus_1 - $digit
300 356         476 $max_base += $digit * $len;
301 356         584 $max_digits[$level--] = $radix_minus_1 - $digit;
302             }
303              
304 356         827 $len /= $radix;
305             ### at: "len=$len plain"
306              
307             # plain digits, x1 low end for min, x2 high end for max
308             {
309 356         660 my $digit = max (0,
310             min ($radix_minus_1,
311             int (($x1 - $min_base) / $len)));
312             ### min base: $min_base
313             ### min diff: $x1-$min_base
314             ### min digit: $digit
315 356         526 $min_base += $digit * $len;
316 356         448 $min_digits[$level] = $digit;
317             }
318             {
319 356         409 my $digit = max (0,
  356         435  
  356         631  
320             min ($radix_minus_1,
321             int (($x2 - $max_base) / $len)));
322             ### max base: $max_base
323             ### max diff: $x2-$max_base
324             ### max digit: $digit
325 356         494 $max_base += $digit * $len;
326 356         655 $max_digits[$level] = $digit;
327             }
328             }
329             ### @min_digits
330             ### @max_digits
331 298         710 return (\@min_digits, \@max_digits);
332             }
333              
334             # return ($len,$level,$base)
335             # $level = number of digits in the bigest integer in negaradix $x1..$x2,
336             # rounded up to be $level even
337             # $len = $radix**$level
338             # $base = lowest negaradix reached by indexes from 0 to $len-1
339             #
340             # have $base <= $x1, $x2 < $base+$len
341             # and $level is the smallest even number with that coverage
342             #
343             # negabinary
344             # 0,1,5,21
345             #
346             # negaternary
347             # 1 3 9 27 81 243
348             # 0,2, 20 182
349             # -6 -60 -546
350             #
351             sub _negaradix_range_level {
352 302     302   459 my ($x1,$x2, $radix) = @_;
353             ### _negaradix_range_level(): "$x1,$x2 radix=$radix"
354             ### assert: $x1 <= $x2
355              
356 302         709 my ($len, $level)
357             = round_down_pow (max($radix - $x1*($radix + 1),
358             (($radix+1)*$x2 - 1) * $radix),
359             $radix);
360 302 100       905 if ($level & 1) {
361             ### increase level to even ...
362 109         710 $len *= $radix;
363 109         548 $level += 1;
364             }
365             ### $len
366             ### $level
367              
368             # because level is even r^2k-1 is a multiple of r^2-1 and therefore of r+1
369             ### assert: ($len-1) % ($radix+1) == 0
370              
371 302         1084 return ($len,
372             $level,
373             ((1-$len) / ($radix+1)) * $radix); # base
374             }
375              
376              
377             #------------------------------------------------------------------------------
378             # levels
379              
380             # shared by ImaginaryHalf and CubicBase
381             sub level_to_n_range {
382 8     8 1 600 my ($self, $level) = @_;
383 8         28 return (0, $self->{'radix'}**$level - 1);
384             }
385             sub n_to_level {
386 0     0 1   my ($self, $n) = @_;
387 0 0         if ($n < 0) { return undef; }
  0            
388 0 0         if (is_infinite($n)) { return $n; }
  0            
389 0           $n = round_nearest($n);
390 0           my ($pow, $exp) = round_up_pow ($n+1, $self->{'radix'});
391 0           return $exp;
392             }
393              
394             #------------------------------------------------------------------------------
395             1;
396             __END__