File Coverage

blib/lib/Math/PlanePath/Base/Digits.pm
Criterion Covered Total %
statement 95 97 97.9
branch 40 44 90.9
condition 11 15 73.3
subroutine 12 12 100.0
pod 5 5 100.0
total 163 173 94.2


line stmt bran cond sub pod time code
1             # Copyright 2010, 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 it
6             # under the terms of the GNU General Public License as published by the Free
7             # 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             #
20             # bit_join_lowtohigh
21              
22              
23             package Math::PlanePath::Base::Digits;
24 63     63   86374 use 5.004;
  63         230  
25 63     63   384 use strict;
  63         156  
  63         1913  
26              
27 63     63   416 use vars '$VERSION','@ISA','@EXPORT_OK';
  63         177  
  63         4752  
28             $VERSION = 129;
29              
30 63     63   410 use Exporter;
  63         476  
  63         5643  
31             @ISA = ('Exporter');
32             @EXPORT_OK = ('parameter_info_array',
33             'bit_split_lowtohigh',
34             'digit_split_lowtohigh',
35             'digit_join_lowtohigh',
36             'round_down_pow',
37             'round_up_pow');
38              
39             # uncomment this to run the ### lines
40             # use Smart::Comments;
41              
42              
43 63         4783 use constant parameter_info_radix2 => { name => 'radix',
44             share_key => 'radix_2',
45             display => 'Radix',
46             type => 'integer',
47             minimum => 2,
48             default => 2,
49             width => 3,
50             description => 'Radix (number base).',
51 63     63   424 };
  63         486  
52 63     63   412 use constant parameter_info_array => [ parameter_info_radix2() ];
  63         120  
  63         66706  
53              
54              
55             #------------------------------------------------------------------------------
56              
57             # ENHANCE-ME: Sometimes the $pow value is not wanted,
58             # eg. SierpinskiArrowhead, though that tends to be approximation code rather
59             # than exact range calculations etc.
60             #
61             sub round_down_pow {
62 65967     65967 1 212725 my ($n, $base) = @_;
63             ### round_down_pow(): "$n base $base"
64              
65             # only for integer bases
66             ### assert: $base == int($base)
67              
68 65967 100       118513 if ($n < $base) {
69 26990         61416 return (1, 0);
70             }
71              
72             # Math::BigInt and Math::BigRat overloaded log() return NaN, use integer
73             # based blog()
74 38977 100       65778 if (ref $n) {
75 9 50       45 if ($n->isa('Math::BigRat')) {
76 0         0 $n = int($n);
77             }
78 9 100 66     61 if ($n->isa('Math::BigInt') || $n->isa('Math::BigInt::Lite')) {
79             ### use blog() ...
80 7         17 my $exp = $n->copy->blog($base);
81             ### exp: "$exp"
82 7         2482 return (Math::BigInt->new(1)->blsft($exp,$base),
83             $exp);
84             }
85             }
86              
87 38970         79368 my $exp = int(log($n)/log($base));
88 38970         2696748 my $pow = $base**$exp;
89             ### n: ref($n)." $n"
90             ### exp: ref($exp)." $exp"
91             ### pow: ref($pow)." $pow"
92              
93             # check how $pow actually falls against $n, not sure should trust float
94             # rounding in log()/log($base)
95             # Crib: $n as first arg in case $n==BigFloat and $pow==BigInt
96 38970 100       88272 if ($n < $pow) {
    100          
97             ### hmm, int(log) too big, decrease ...
98 2         4 $exp -= 1;
99 2         3 $pow = $base**$exp;
100             } elsif ($n >= $base*$pow) {
101             ### hmm, int(log) too small, increase ...
102 15         1785 $exp += 1;
103 15         811 $pow *= $base;
104             }
105              
106             ### result ...
107             ### pow: "$pow"
108             ### exp: "$exp"
109             ### $exp
110 38970         91453 return ($pow, $exp);
111             }
112              
113             sub round_up_pow {
114 118     118 1 11516 my ($n, $base) = @_;
115             ### round_up_pow(): "$n base $base"
116              
117             # only for integer bases
118             ### assert: $base == int($base)
119              
120 118 100       215 if ($n < 1) {
121 1         109 return (1, 0);
122             }
123              
124             # Math::BigInt and Math::BigRat overloaded log() return NaN, use integer
125             # based blog()
126 117 100       714 if (ref $n) {
127             ### $n
128 5 50       26 if ($n->isa('Math::BigRat')) {
129 0         0 $n = int($n);
130             }
131 5 50 33     17 if ($n->isa('Math::BigInt') || $n->isa('Math::BigInt::Lite')) {
132             ### use blog(): ref $n
133 5         12 my $exp = $n->copy->blog($base);
134             ### exp: $exp
135              
136 5         2401 my $pow = (ref $n)->new(1)->blsft($exp,$base);
137             # Crib: must have $n first to have Math::BigInt::Lite method preferred
138 5 100       1544 if ($n > $pow) {
139             ### blog too small, increase ...
140 3         119 $pow *= $base;
141 3         442 $exp += 1;
142             }
143 5         462 return ($pow, $exp);
144             }
145             }
146              
147 112         356 my $exp = int(log($n)/log($base) + 1);
148 112         162 my $pow = $base**$exp;
149             ### n: ref($n)." $n"
150             ### exp: ref($exp)." $exp"
151             ### pow: ref($pow)." $pow"
152              
153             # check how $pow actually falls against $n, not sure should trust float
154             # rounding in log()/log($base)
155             # Crib: $n as first arg in case $n==BigFloat and $pow==BigInt
156 112 100 66     360 if ($exp > 0 && $n <= $pow/$base) {
    100          
157             ### hmm, int(log) too big, decrease...
158 28         39 $exp -= 1;
159 28         41 $pow = $base**$exp;
160             } elsif ($n > $pow) {
161             ### hmm, int(log)+1 too small, increase...
162 1         3 $exp += 1;
163 1         2 $pow *= $base;
164             }
165 112         247 return ($pow, $exp);
166             }
167              
168             #------------------------------------------------------------------------------
169             {
170             my %binary_to_base4 = ('00' => '0',
171             '01' => '1',
172             '10' => '2',
173             '11' => '3');
174             my @bigint_coderef;
175             $bigint_coderef[4] = sub {
176             (my $str = $_[0]->as_bin) =~ s/^0b//; # strip leading 0b
177             if (length($str) & 1) {
178             $str = "0$str";
179             }
180             $str =~ s/(..)/$binary_to_base4{$1}/ge;
181             return reverse split //, $str;
182             };
183             $bigint_coderef[8] = sub {
184             (my $str = $_[0]->as_oct) =~ s/^0//; # strip leading 0
185             return reverse split //, $str;
186             };
187             $bigint_coderef[10] = sub {
188             return reverse split //, $_[0]->bstr;
189             };
190             $bigint_coderef[16] = sub {
191             (my $str = $_[0]->as_hex) =~ s/^0x//; # strip leading 0x
192             return reverse map {hex} split //, $str;
193             };
194              
195             # In _divrem() and _digit_split_lowtohigh() divide using rem=n%d then
196             # q=(n-rem)/d so that quotient is an exact division. If it's not exact
197             # then goes to float and loses precision if UV=64bit NV=53bit.
198              
199             sub digit_split_lowtohigh {
200 361545     361545 1 1232123 my ($n, $radix) = @_;
201             ### _digit_split_lowtohigh(): $n
202              
203 361545 100       634020 $n || return; # don't return '0' from BigInt stringize
204 345300 100       602392 if ($radix == 2) {
205 4741         7793 return bit_split_lowtohigh($n);
206             }
207              
208 340559         452559 my @ret;
209 340559 100 100     669110 if (ref $n && $n->isa('Math::BigInt')) {
210 19 100       50 if (my $coderef = $bigint_coderef[$radix]) {
211 10         28 return $coderef->($_[0]);
212             }
213 9         22 $n = $n->copy; # for bdiv() modification
214 9         181 do {
215 274         6273 (undef, my $digit) = $n->bdiv($radix);
216 274         75257 push @ret, $digit;
217             } while ($n);
218 9 50       223 if ($radix < 1_000_000) { # plain scalars if fit
219 9         19 foreach (@ret) {
220 274         5188 $_ = $_->numify; # mutate array
221             }
222             }
223              
224             } else {
225 340540         441339 do {
226 1432042         2639837 my $digit = $n % $radix;
227 1432042         2138218 push @ret, $digit;
228 1432042         2719472 $n = int(($n - $digit) / $radix);
229             } while ($n > 0);
230             }
231              
232 340549         773737 return @ret; # array[0] low digit
233             }
234             }
235              
236             # 2**32 on a 32-bit UV, or 2**64 on 64-bit
237 63     63   550 use constant 1.02 _UV_MAX_PLUS_1 => ((~0 >> 1) + 1) * 2.0;
  63         1023  
  63         19708  
238              
239             sub bit_split_lowtohigh {
240 19590     19590 1 31513 my ($n) = @_;
241 19590         26106 my @ret;
242 19590 100       34753 if ($n >= 1) {
243 19114 100 100     37100 if (ref $n && $n->isa('Math::BigInt')) {
244 10         44 (my $str = $n->as_bin) =~ s/^0b//; # strip leading 0b
245 10         5789 return reverse split //, $str;
246             }
247 19104 100       33806 if ($n <= _UV_MAX_PLUS_1) {
248 19103         87762 return reverse split //, sprintf('%b',$n);
249             }
250 1         480 do {
251 257         294042 my $digit = $n % 2;
252 257         126151 push @ret, $digit;
253 257         747 $n = int(($n - $digit) / 2);
254             } while ($n);
255             }
256 477         1767 return @ret; # array[0] low digit
257             }
258              
259              
260             #------------------------------------------------------------------------------
261             # $aref->[0] low digit
262             # ENHANCE-ME: BigInt new(), from_bin(), from_oct(), from_hex()
263              
264             sub digit_join_lowtohigh {
265 263589     263589 1 703853 my ($aref, $radix, $zero) = @_;
266              
267             ### digit_join_lowtohigh() ...
268             ### $aref
269             ### $radix
270             ### $zero
271              
272 263589 100       427461 my $n = (defined $zero ? $zero : 0);
273 263589         405716 foreach my $digit (reverse @$aref) { # high to low
274             ### $n
275 975546         5411612 $n *= $radix;
276 975546         5042254 $n += $digit;
277             }
278             ### $n
279 263589         682374 return $n;
280             }
281              
282              
283             1;
284             __END__