File Coverage

blib/lib/Math/Rational/Approx.pm
Criterion Covered Total %
statement 72 72 100.0
branch 18 22 81.8
condition 9 12 75.0
subroutine 16 16 100.0
pod 3 3 100.0
total 118 125 94.4


line stmt bran cond sub pod time code
1             # --8<--8<--8<--8<--
2             #
3             # Copyright (C) 2012 Smithsonian Astrophysical Observatory
4             #
5             # This file is part of Math::Rational::Approx
6             #
7             # Math::Rational::Approx is free software: you can redistribute it
8             # and/or modify it under the terms of the GNU General Public License
9             # as published by the Free Software Foundation, either version 3 of
10             # the License, or (at your option) any later version.
11             #
12             # This program is distributed in the hope that it will be useful,
13             # but WITHOUT ANY WARRANTY; without even the implied warranty of
14             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15             # GNU General Public License for more details.
16             #
17             # You should have received a copy of the GNU General Public License
18             # along with this program. If not, see .
19             #
20             # -->8-->8-->8-->8--
21              
22             package Math::Rational::Approx;
23              
24 9     9   2840 use strict;
  9         31  
  9         293  
25 9     9   135 use warnings;
  9         19  
  9         256  
26 9     9   45 use Carp;
  9         19  
  9         909  
27              
28              
29             our $VERSION = '0.02';
30              
31 9     9   51 use base 'Exporter';
  9         16  
  9         1655  
32             our %EXPORT_TAGS = ( all => [ qw( maxD contfrac contfrac_nd ) ],
33             );
34             our @EXPORT_OK = map { @$_} values %EXPORT_TAGS;
35              
36 9     9   8386 use POSIX 'floor';
  9         80317  
  9         101  
37 9     9   22217 use Math::BigFloat;
  9         175253  
  9         62  
38              
39 9     9   303081 use MooX::Types::MooseLike::Numeric ':all';
  9         83392  
  9         2623  
40 9     9   4174 use Params::Validate qw[ validate_pos ARRAYREF ];
  9         32116  
  9         10123  
41              
42             sub _check_bounds {
43              
44 26     26   45 my ( $x, $bounds ) = @_;
45              
46 26 100       96 croak( "incorrect number of elements in bounds\n" )
47             unless 4 == @$bounds;
48              
49 24         41 my ( $a, $b, $c, $d ) = @$bounds;
50             # ensure that a/b < c/d
51              
52 24 100       104 croak( "a/b is not less than c/d\n" )
53             unless $a / $b < $c / $d;
54              
55              
56 22 100 66     232 croak( "a/b and c/d do not bound x\n" )
57             unless $a / $b < $x && $x < $c / $d;
58              
59             }
60              
61             sub maxD {
62              
63             my ($x, $maxD, $bounds )
64             = validate_pos( @_,
65 43     43   142 { callbacks => { 'positive number' => sub { is_PositiveNum($_[0]) } },
66             },
67 41     41   1495 { callbacks => { 'positive integer' => sub { is_PositiveInt($_[0]) } },
68             },
69 43     43 1 26332 { type => ARRAYREF,
70             default => [],
71             },
72             ) ;
73              
74 38 100 66     1241 if ( defined $bounds && @$bounds ) {
75              
76 18         43 _check_bounds( $x, $bounds );
77             }
78              
79             else {
80              
81 20         82 my $base = floor( $x );
82              
83 20         37 @{$bounds} = ( $base, 1, $base+1, 1 );
  20         52  
84              
85             }
86              
87 35         45 my ( $a, $b, $c, $d ) = @{$bounds};
  35         58  
88              
89 35         42 my ( $nom, $denom );
90              
91 35   100     147 while ( $b <= $maxD && $d <= $maxD ) {
92              
93 261         372 my $mediant = ( $a + $c ) / ( $b + $d );
94              
95 261 100       547 if ( $x == $mediant ) {
    100          
96              
97 5 0       17 ( $nom, $denom ) =
    50          
98             $b + $d <= $maxD ? ( $a + $c, $b + $d )
99             : $d > $b ? ( $c, $d )
100             : ( $a, $b );
101              
102 5         10 last;
103             }
104              
105             elsif ( $x > $mediant ) {
106              
107 108         1900 ($a, $b) = ( $a + $c, $b + $d );
108              
109             }
110              
111             else {
112              
113 148         608 ($c, $d) = ( $a + $c, $b + $d );
114              
115             }
116              
117             }
118              
119 35 50 66     118 if ( ! defined $nom && ! defined $denom ) {
120 30 100       70 ( $nom, $denom ) = $b > $maxD ? ( $c, $d )
121             : ( $a, $b );
122             }
123              
124 35         45 @{$bounds} = ( $a, $b, $c, $d );
  35         96  
125              
126 35         132 return ( $nom, $denom, $bounds );
127             }
128              
129             sub contfrac {
130              
131             my ( $x, $max, $p ) =
132             validate_pos( @_,
133             { callbacks => {
134 24     24   1031 'positive number' => sub { is_PositiveNum($_[0]) },
135             },
136             },
137             { callbacks => {
138 22     22   4942 'positive integer' => sub { is_PositiveInt($_[0]) },
139             },
140             },
141 24     24 1 20685 { type => ARRAYREF, default => [] },
142             );
143              
144 19         750 $x = Math::BigFloat->new( $x );
145              
146 19         2157 my $one = Math::BigFloat->bone;
147              
148 19         734 for my $n ( 0 .. $max-1 ) {
149              
150 58         36803 my $int = $x->copy->bfloor;
151 58         4973 push @$p, $int;
152              
153             # if $x is actually rational, round off error in ( $x - $int )
154             # can drive the iteration beyond its true end point, causing
155             # bogus results. Let's hope that 10 digits is enough.
156 58 100       199 last if ( $x - $int)->bfround(-10)->is_zero;
157 53         26389 $x = $one->copy->bdiv( $x - $int );
158             }
159              
160 19         16038 return $p, $x;
161             }
162              
163             sub contfrac_nd {
164              
165             # ignore extra parameter to ease use of contfrac_nd( contfrac ( ... ) )
166 18     18 1 3846 my ( $terms ) = validate_pos( @_, { type => ARRAYREF }, 0 );
167 16         78 my @p = reverse @$terms;
168              
169 16         78 my $n = Math::BigInt->bone;
170 16         518 my $d = Math::BigInt->new( (shift @p) );
171              
172 16         3576 for my $p ( @p ) {
173              
174 53         157 $n += $d * $p;
175              
176 53         15545 ( $n, $d ) = ( $d, $n );
177              
178             }
179              
180 16         40 ( $n, $d ) = ( $d, $n );
181 16         92 return ( $n, $d );
182             }
183              
184              
185             1;
186              
187              
188              
189             __END__