File Coverage

blib/lib/Math/NumSeq/Repdigits.pm
Criterion Covered Total %
statement 130 149 87.2
branch 48 60 80.0
condition 5 6 83.3
subroutine 20 22 90.9
pod 8 8 100.0
total 211 245 86.1


line stmt bran cond sub pod time code
1             # Copyright 2010, 2011, 2012, 2013, 2014 Kevin Ryde
2              
3             # This file is part of Math-NumSeq.
4             #
5             # Math-NumSeq 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-NumSeq 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-NumSeq. If not, see .
17              
18             package Math::NumSeq::Repdigits;
19 6     6   10703 use 5.004;
  6         23  
  6         277  
20 6     6   39 use strict;
  6         11  
  6         322  
21              
22 6     6   37 use vars '$VERSION', '@ISA';
  6         11  
  6         448  
23             $VERSION = 71;
24 6     6   692 use Math::NumSeq;
  6         13  
  6         460  
25             @ISA = ('Math::NumSeq');
26             *_is_infinite = \&Math::NumSeq::_is_infinite;
27             *_to_bigint = \&Math::NumSeq::_to_bigint;
28              
29 6     6   5351 use Math::NumSeq::NumAronson 8; # new in v.8
  6         103  
  6         293  
30             *_round_down_pow = \&Math::NumSeq::NumAronson::_round_down_pow;
31              
32             # uncomment this to run the ### lines
33             #use Smart::Comments;
34              
35              
36             # use constant name => Math::NumSeq::__('Repdigits');
37 6     6   31 use constant description => Math::NumSeq::__('Numbers which are a "repdigit", meaning 0, 1 ... 9, 11, 22, 33, ... 99, 111, 222, 333, ..., 999, etc. The default is decimal, or select a radix.');
  6         12  
  6         21  
38 6     6   28 use constant i_start => 0;
  6         10  
  6         263  
39 6     6   32 use constant characteristic_increasing => 1;
  6         11  
  6         248  
40 6     6   30 use constant characteristic_integer => 1;
  6         8  
  6         275  
41 6     6   40 use constant values_min => 0;
  6         11  
  6         248  
42              
43             use Math::NumSeq::Base::Digits
44 6     6   4531 'parameter_info_array'; # radix parameter
  6         14  
  6         11459  
45              
46             #------------------------------------------------------------------------------
47             # cf A002275 - repunits
48             # A108850 - repunits count of 1 bits
49              
50             my @oeis_anum = (
51             # OEIS-Catalogue array begin
52             undef, # 0
53             undef, # 1
54             'A000225', # radix=2 # 2^i-1
55             'A048328', # radix=3
56             'A048329', # radix=4
57             undef, # A048330 starts OFFSET=1 value=0
58             'A048331', # radix=6
59             'A048332', # radix=7
60             undef, # A048333 starts OFFSET=1 value=0
61             'A048334', # radix=9
62             'A010785', # radix=10 # starting from OFFSET=0 value=0
63             'A048335', # radix=11
64             'A048336', # radix=12
65             'A048337', # radix=13
66             'A048338', # radix=14
67             'A048339', # radix=15
68             'A048340', # radix=16
69             # OEIS-Catalogue array end
70             );
71             sub oeis_anum {
72 4     4 1 25 my ($self) = @_;
73 4         14 return $oeis_anum[$self->{'radix'}];
74             }
75              
76             #------------------------------------------------------------------------------
77              
78             sub rewind {
79 20     20 1 2473 my ($self) = @_;
80 20         45 my $radix = $self->{'radix'};
81 20 50       49 if ($radix < 2) {
82 0         0 $radix = $self->{'radix'} = 10;
83             }
84              
85 20         77 $self->{'i'} = $self->i_start;
86 20         38 $self->{'n'} = -1;
87 20 100       55 if ($radix != 2) {
88 16         32 $self->{'inc'} = 1;
89 16         57 $self->{'digit'} = -1;
90             }
91             }
92             sub _UNTESTED__seek_to_i {
93 0     0   0 my ($self, $i) = @_;
94 0         0 $self->{'i'} = $i;
95 0         0 my $radix = $self->{'radix'};
96 0 0       0 if ($radix == 2) {
97 0 0       0 if ($i == 0) {
98 0         0 $self->{'n'} = -1;
99             } else {
100 0         0 $self->{'n'} = $self->ith($i-1);
101             }
102             } else {
103 0         0 my $digit = $self->{'digit'} = ($i % $radix) - 1;
104 0         0 my $exp = int($i/$radix);
105 0         0 $self->{'inc'} = $self->ith($i-$digit);
106 0         0 $self->{'n'} = $self->{'inc'} * $digit;
107             }
108             }
109             sub _UNTESTED__seek_to_value {
110 0     0   0 my ($self, $value) = @_;
111 0         0 $self->seek_to_i($self->value_to_i_ceil($value));
112             }
113              
114             sub next {
115 132     132 1 2107 my ($self) = @_;
116              
117 132         199 my $i = $self->{'i'}++;
118 132         187 my $radix = $self->{'radix'};
119 132 100       229 if ($radix == 2) {
120 14 50       29 if ($i == 31) {
121 0         0 $self->{'n'} = _to_bigint($self->{'n'});
122             }
123 14 100       29 if ($i) {
124 12         19 $self->{'n'} *= 2;
125             }
126 14         40 return ($i, $self->{'n'} += 1);
127              
128             } else {
129             # ENHANCE-ME: automatic promote to bigint
130              
131 118         177 my $n = ($self->{'n'} += $self->{'inc'});
132 118 100       243 if (++$self->{'digit'} >= $radix) {
133 14         26 $self->{'inc'} = $self->{'inc'} * $radix + 1;
134 14         21 $self->{'digit'} = 1;
135 14         25 $self->{'n'} = ($n += 1); # not ++$n as that gives warnings on overflow
136             ### digit: $self->{'digit'}
137             ### inc: $self->{'inc'}
138             ### $n
139             }
140 118         267 return ($i, $n);
141             }
142             }
143              
144             sub ith {
145 371     371 1 6645 my ($self, $i) = @_;
146 371         519 my $radix = $self->{'radix'};
147              
148 371 50       787 if (_is_infinite ($i)) {
149 0         0 return $i;
150             }
151              
152 371 100       715 if ($radix == 2) {
153 29 100       66 my $one = ($i >= 31 ? _to_bigint(1) : 1);
154 29         118 return ($one << $i) - 1;
155             }
156              
157 342 100       656 if (($i-=1) < 0) {
158 18         48 return 0;
159             }
160 324         443 my $digit = ($i % ($radix-1)) + 1;
161 324         506 $i = int($i/($radix-1)) + 1;
162 324         988 return ($radix ** $i - 1) / ($radix - 1) * $digit;
163             }
164              
165             sub pred {
166 3376     3376 1 15540 my ($self, $value) = @_;
167              
168             {
169 3376         3577 my $int = int($value);
  3376         4084  
170 3376 100       6323 if ($value != $int) {
171 1653         3204 return 0;
172             }
173 1723         2438 $value = $int; # prefer BigInt if input BigFloat
174             }
175              
176 1723         2379 my $radix = $self->{'radix'};
177 1723 100       2955 if ($radix == 2) {
178 71         173 return ! (($value+1) & $value);
179              
180             }
181 1652 100       2819 if ($radix == 10) {
182 1028         1525 my $digit = substr($value,0,1);
183 1028         4356 return ($value !~ /[^$digit]/);
184             }
185              
186 624         770 my $digit = $value % $radix;
187 624         1409 while ($value = int($value/$radix)) {
188 701 100       1583 unless (($value % $radix) == $digit) { # false for inf or nan
189 562         1279 return 0;
190             }
191             }
192 62         136 return 1;
193             }
194              
195             sub value_to_i_ceil {
196 835     835 1 2138 my ($self, $value) = @_;
197             ### value_to_i_ceil(): $value
198              
199 835 50       1763 if (_is_infinite ($value)) {
200 0         0 return $value;
201             }
202 835 100       20055 if ($value <= 0) {
203 23         517 return 0;
204             }
205 812         8278 my $int = int($value);
206 812 100       2334 if ($value != $int) {
207 63         81 $int += 1;
208             }
209             ### $int
210              
211 812         2506 my $radix = $self->{'radix'};
212 812 50       1309 my @digits = _digit_split_lowtohigh($int, $radix)
213             or return 0; # if $value==0
214              
215 812         1669 my $high_digit = pop @digits;
216 812         1313 my $i = $high_digit + ($radix-1) * scalar(@digits);
217             ### $high_digit
218             ### $i
219              
220 812         2316 foreach my $digit (reverse @digits) { # high to low
221 1233 100       2389 if ($digit > $high_digit) {
222 204         676 return $i + 1;
223             }
224 1029 100       2386 if ($digit < $high_digit) {
225 206         256 last;
226             }
227             }
228 608         2058 return $i;
229             }
230             sub value_to_i_floor {
231 855     855 1 2338 my ($self, $value) = @_;
232              
233 855 100       1668 if ($value < 1) {
234 46         583 return 0;
235             }
236 809 50       6751 if (_is_infinite ($value)) {
237 0         0 return $value;
238             }
239 809         18962 $value = int($value);
240              
241 809         2263 my $radix = $self->{'radix'};
242 809 50       1404 my @digits = _digit_split_lowtohigh($value, $radix)
243             or return 0; # if $value==0
244              
245 809         1654 my $high_digit = pop @digits;
246 809         1261 my $i = $high_digit + ($radix-1) * scalar(@digits);
247              
248 809         2389 foreach my $digit (reverse @digits) { # high to low
249 1240 100       2830 if ($digit < $high_digit) {
250 281         931 return $i - 1;
251             }
252             }
253 528         1837 return $i;
254             }
255              
256             # either floor or 1 too big
257             sub value_to_i_estimate {
258 102     102 1 2754 my ($self, $value) = @_;
259             ### value_to_i_estimate() ...
260              
261 102 100       204 if ($value < 1) {
262 36         79 return 0;
263             }
264 66 50       472 if (_is_infinite ($value)) {
265 0         0 return $value;
266             }
267 66         1295 my $radix = $self->{'radix'};
268 66         174 my ($power, $exp) = _round_down_pow ($value, $radix);
269 66         846 return int($value/$power) # high digit
270             + ($radix-1) * $exp;
271             }
272              
273             #------------------------------------------------------------------------------
274              
275             {
276             my %binary_to_base4 = ('00' => '0',
277             '01' => '1',
278             '10' => '2',
279             '11' => '3');
280             my @radix_to_coderef;
281             $radix_to_coderef[2] = sub {
282             (my $str = $_[0]->as_bin) =~ s/^0b//; # strip leading 0b
283             return reverse split //, $str;
284             };
285             $radix_to_coderef[4] = sub {
286             (my $str = $_[0]->as_bin) =~ s/^0b//; # strip leading 0b
287             if (length($str) & 1) {
288             $str = "0$str";
289             }
290             $str =~ s/(..)/$binary_to_base4{$1}/ge;
291             return reverse split //, $str;
292             };
293             $radix_to_coderef[8] = sub {
294             (my $str = $_[0]->as_oct) =~ s/^0//; # strip leading 0
295             return reverse split //, $str;
296             };
297             $radix_to_coderef[10] = sub {
298             return reverse split //, $_[0]->bstr;
299             };
300             $radix_to_coderef[16] = sub {
301             (my $str = $_[0]->as_hex) =~ s/^0x//; # strip leading 0x
302             return reverse map {hex} split //, $str;
303             };
304              
305             sub _digit_split_lowtohigh {
306 35326     35326   42790 my ($n, $radix) = @_;
307             ### _digit_split_lowtohigh(): $n
308              
309 35326 100       63820 $n || return; # don't return '0' from BigInt stringize
310              
311 35122 100 66     79135 if (ref $n
      100        
312             && $n->isa('Math::BigInt')
313             && (my $coderef = $radix_to_coderef[$radix])) {
314 141         339 return $coderef->($_[0]);
315             }
316              
317 34981         37598 my @ret;
318 34981         34660 do {
319 308819         623920 push @ret, $n % $radix;
320             } while ($n = int($n/$radix));
321 34981         2806198 return @ret; # array[0] low digit
322             }
323             }
324              
325             1;
326             __END__