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 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   81861 use 5.004;
  63         225  
25 63     63   409 use strict;
  63         125  
  63         1759  
26              
27 63     63   374 use vars '$VERSION','@ISA','@EXPORT_OK';
  63         154  
  63         4101  
28             $VERSION = 128;
29              
30 63     63   414 use Exporter;
  63         469  
  63         5103  
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         4649 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   397 };
  63         450  
52 63     63   407 use constant parameter_info_array => [ parameter_info_radix2() ];
  63         147  
  63         60877  
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 65978     65978 1 204621 my ($n, $base) = @_;
63             ### round_down_pow(): "$n base $base"
64              
65             # only for integer bases
66             ### assert: $base == int($base)
67              
68 65978 100       113917 if ($n < $base) {
69 26975         61584 return (1, 0);
70             }
71              
72             # Math::BigInt and Math::BigRat overloaded log() return NaN, use integer
73             # based blog()
74 39003 100       62200 if (ref $n) {
75 9 50       47 if ($n->isa('Math::BigRat')) {
76 0         0 $n = int($n);
77             }
78 9 100 66     63 if ($n->isa('Math::BigInt') || $n->isa('Math::BigInt::Lite')) {
79             ### use blog() ...
80 7         19 my $exp = $n->copy->blog($base);
81             ### exp: "$exp"
82 7         2443 return (Math::BigInt->new(1)->blsft($exp,$base),
83             $exp);
84             }
85             }
86              
87 38996         74045 my $exp = int(log($n)/log($base));
88 38996         2676348 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 38996 100       82692 if ($n < $pow) {
    100          
97             ### hmm, int(log) too big, decrease ...
98 2         3 $exp -= 1;
99 2         3 $pow = $base**$exp;
100             } elsif ($n >= $base*$pow) {
101             ### hmm, int(log) too small, increase ...
102 15         1664 $exp += 1;
103 15         792 $pow *= $base;
104             }
105              
106             ### result ...
107             ### pow: "$pow"
108             ### exp: "$exp"
109             ### $exp
110 38996         85602 return ($pow, $exp);
111             }
112              
113             sub round_up_pow {
114 118     118 1 11291 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       220 if ($n < 1) {
121 1         107 return (1, 0);
122             }
123              
124             # Math::BigInt and Math::BigRat overloaded log() return NaN, use integer
125             # based blog()
126 117 100       703 if (ref $n) {
127             ### $n
128 5 50       23 if ($n->isa('Math::BigRat')) {
129 0         0 $n = int($n);
130             }
131 5 50 33     18 if ($n->isa('Math::BigInt') || $n->isa('Math::BigInt::Lite')) {
132             ### use blog(): ref $n
133 5         14 my $exp = $n->copy->blog($base);
134             ### exp: $exp
135              
136 5         2389 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       1523 if ($n > $pow) {
139             ### blog too small, increase ...
140 3         117 $pow *= $base;
141 3         451 $exp += 1;
142             }
143 5         472 return ($pow, $exp);
144             }
145             }
146              
147 112         225 my $exp = int(log($n)/log($base) + 1);
148 112         158 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     362 if ($exp > 0 && $n <= $pow/$base) {
    100          
157             ### hmm, int(log) too big, decrease...
158 28         40 $exp -= 1;
159 28         42 $pow = $base**$exp;
160             } elsif ($n > $pow) {
161             ### hmm, int(log)+1 too small, increase...
162 1         2 $exp += 1;
163 1         2 $pow *= $base;
164             }
165 112         235 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 360665     360665 1 1241341 my ($n, $radix) = @_;
201             ### _digit_split_lowtohigh(): $n
202              
203 360665 100       631778 $n || return; # don't return '0' from BigInt stringize
204 344473 100       590667 if ($radix == 2) {
205 4740         7648 return bit_split_lowtohigh($n);
206             }
207              
208 339733         441981 my @ret;
209 339733 100 100     663113 if (ref $n && $n->isa('Math::BigInt')) {
210 19 100       53 if (my $coderef = $bigint_coderef[$radix]) {
211 10         36 return $coderef->($_[0]);
212             }
213 9         22 $n = $n->copy; # for bdiv() modification
214 9         183 do {
215 274         6496 (undef, my $digit) = $n->bdiv($radix);
216 274         74432 push @ret, $digit;
217             } while ($n);
218 9 50       225 if ($radix < 1_000_000) { # plain scalars if fit
219 9         21 foreach (@ret) {
220 274         5283 $_ = $_->numify; # mutate array
221             }
222             }
223              
224             } else {
225 339714         435737 do {
226 1418500         2612841 my $digit = $n % $radix;
227 1418500         2100176 push @ret, $digit;
228 1418500         2653880 $n = int(($n - $digit) / $radix);
229             } while ($n > 0);
230             }
231              
232 339723         784938 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   605 use constant 1.02 _UV_MAX_PLUS_1 => ((~0 >> 1) + 1) * 2.0;
  63         1005  
  63         18492  
238              
239             sub bit_split_lowtohigh {
240 19534     19534 1 30124 my ($n) = @_;
241 19534         25263 my @ret;
242 19534 100       33636 if ($n >= 1) {
243 19072 100 100     36328 if (ref $n && $n->isa('Math::BigInt')) {
244 10         45 (my $str = $n->as_bin) =~ s/^0b//; # strip leading 0b
245 10         5562 return reverse split //, $str;
246             }
247 19062 100       33048 if ($n <= _UV_MAX_PLUS_1) {
248 19061         86521 return reverse split //, sprintf('%b',$n);
249             }
250 1         452 do {
251 257         287642 my $digit = $n % 2;
252 257         124347 push @ret, $digit;
253 257         732 $n = int(($n - $digit) / 2);
254             } while ($n);
255             }
256 463         1862 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 263568     263568 1 708972 my ($aref, $radix, $zero) = @_;
266              
267             ### digit_join_lowtohigh() ...
268             ### $aref
269             ### $radix
270             ### $zero
271              
272 263568 100       433121 my $n = (defined $zero ? $zero : 0);
273 263568         415408 foreach my $digit (reverse @$aref) { # high to low
274             ### $n
275 974440         5417014 $n *= $radix;
276 974440         5060191 $n += $digit;
277             }
278             ### $n
279 263568         695052 return $n;
280             }
281              
282              
283             1;
284             __END__