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 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 62     62   68656 use 5.004;
  62         549  
25 62     62   356 use strict;
  62         127  
  62         1706  
26              
27 62     62   339 use vars '$VERSION','@ISA','@EXPORT_OK';
  62         112  
  62         4072  
28             $VERSION = 127;
29              
30 62     62   408 use Exporter;
  62         161  
  62         5121  
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 62         4207 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 62     62   399 };
  62         434  
52 62     62   398 use constant parameter_info_array => [ parameter_info_radix2() ];
  62         471  
  62         61342  
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 65932     65932 1 188729 my ($n, $base) = @_;
63             ### round_down_pow(): "$n base $base"
64              
65             # only for integer bases
66             ### assert: $base == int($base)
67              
68 65932 100       115569 if ($n < $base) {
69 26971         61206 return (1, 0);
70             }
71              
72             # Math::BigInt and Math::BigRat overloaded log() return NaN, use integer
73             # based blog()
74 38961 100       63532 if (ref $n) {
75 9 50       38 if ($n->isa('Math::BigRat')) {
76 0         0 $n = int($n);
77             }
78 9 100 66     51 if ($n->isa('Math::BigInt') || $n->isa('Math::BigInt::Lite')) {
79             ### use blog() ...
80 7         42 my $exp = $n->copy->blog($base);
81             ### exp: "$exp"
82 7         2007 return (Math::BigInt->new(1)->blsft($exp,$base),
83             $exp);
84             }
85             }
86              
87 38954         75390 my $exp = int(log($n)/log($base));
88 38954         2173633 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 38954 100       83603 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         1359 $exp += 1;
103 15         663 $pow *= $base;
104             }
105              
106             ### result ...
107             ### pow: "$pow"
108             ### exp: "$exp"
109             ### $exp
110 38954         85711 return ($pow, $exp);
111             }
112              
113             sub round_up_pow {
114 118     118 1 9352 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       186 if ($n < 1) {
121 1         89 return (1, 0);
122             }
123              
124             # Math::BigInt and Math::BigRat overloaded log() return NaN, use integer
125             # based blog()
126 117 100       597 if (ref $n) {
127             ### $n
128 5 50       21 if ($n->isa('Math::BigRat')) {
129 0         0 $n = int($n);
130             }
131 5 50 33     15 if ($n->isa('Math::BigInt') || $n->isa('Math::BigInt::Lite')) {
132             ### use blog(): ref $n
133 5         11 my $exp = $n->copy->blog($base);
134             ### exp: $exp
135              
136 5         1990 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       1206 if ($n > $pow) {
139             ### blog too small, increase ...
140 3         95 $pow *= $base;
141 3         355 $exp += 1;
142             }
143 5         383 return ($pow, $exp);
144             }
145             }
146              
147 112         220 my $exp = int(log($n)/log($base) + 1);
148 112         133 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     301 if ($exp > 0 && $n <= $pow/$base) {
    100          
157             ### hmm, int(log) too big, decrease...
158 28         33 $exp -= 1;
159 28         36 $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         211 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 266701     266701 1 835737 my ($n, $radix) = @_;
201             ### _digit_split_lowtohigh(): $n
202              
203 266701 100       471308 $n || return; # don't return '0' from BigInt stringize
204 250550 100       432682 if ($radix == 2) {
205 4676         7684 return bit_split_lowtohigh($n);
206             }
207              
208 245874         323891 my @ret;
209 245874 100 100     479961 if (ref $n && $n->isa('Math::BigInt')) {
210 19 100       49 if (my $coderef = $bigint_coderef[$radix]) {
211 10         23 return $coderef->($_[0]);
212             }
213 9         18 $n = $n->copy; # for bdiv() modification
214 9         155 do {
215 274         6643 (undef, my $digit) = $n->bdiv($radix);
216 274         73951 push @ret, $digit;
217             } while ($n);
218 9 50       186 if ($radix < 1_000_000) { # plain scalars if fit
219 9         16 foreach (@ret) {
220 274         5293 $_ = $_->numify; # mutate array
221             }
222             }
223              
224             } else {
225 245855         312443 do {
226 966177         1874393 my $digit = $n % $radix;
227 966177         1464972 push @ret, $digit;
228 966177         1827554 $n = int(($n - $digit) / $radix);
229             } while ($n > 0);
230             }
231              
232 245864         565096 return @ret; # array[0] low digit
233             }
234             }
235              
236             # 2**32 on a 32-bit UV, or 2**64 on 64-bit
237 62     62   499 use constant 1.02 _UV_MAX_PLUS_1 => ((~0 >> 1) + 1) * 2.0;
  62         1220  
  62         17974  
238              
239             sub bit_split_lowtohigh {
240 19523     19523 1 30595 my ($n) = @_;
241 19523         26106 my @ret;
242 19523 100       34134 if ($n >= 1) {
243 19050 100 100     36365 if (ref $n && $n->isa('Math::BigInt')) {
244 10         42 (my $str = $n->as_bin) =~ s/^0b//; # strip leading 0b
245 10         5682 return reverse split //, $str;
246             }
247 19040 100       33634 if ($n <= _UV_MAX_PLUS_1) {
248 19039         87299 return reverse split //, sprintf('%b',$n);
249             }
250 1         370 do {
251 257         233869 my $digit = $n % 2;
252 257         100702 push @ret, $digit;
253 257         609 $n = int(($n - $digit) / 2);
254             } while ($n);
255             }
256 474         1614 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 132016     132016 1 382652 my ($aref, $radix, $zero) = @_;
266              
267             ### digit_join_lowtohigh() ...
268             ### $aref
269             ### $radix
270             ### $zero
271              
272 132016 100       212680 my $n = (defined $zero ? $zero : 0);
273 132016         202968 foreach my $digit (reverse @$aref) { # high to low
274             ### $n
275 577026         4921980 $n *= $radix;
276 577026         4470218 $n += $digit;
277             }
278             ### $n
279 132016         342924 return $n;
280             }
281              
282              
283             1;
284             __END__