Phil Carmody <
pc+u...@asdf.org> writes:
> Use of a binary chop down Farey sequences might be better?
...
> It seems more heavyweight than brute force, but a lot of denominators
> will never even be looked at, and because the termwise sum of any two
> numbers in range is also in range, only the extremal new terms actually
> need to be checked, so there's a slim possibility that it might be a win.
> I suspect it would work best with more significant digits, as it should
> home in reasonably quickly.
Pfft, other stuff I should be doing is boring, let's find out...
============ 8< begin --
fareyprox.pl =====================
#!/usr/bin/perl -w
my ($int,$frac) = ($ARGV[0] =~ m/(\d*)\.(\d+)/);
$frac//die;
my $digits=length($frac);
my $denom=10**$digits;
my $lower=($frac-0.5)/$denom;
my $upper=($frac+0.5)/$denom;
printf("Searching for $int.$frac, treating as $int + [$lower,$upper]\n");
$frac=$frac/$denom;
my @ns=(0,1);
my @ds=(1,1);
print("Starting with: "); pretty(\@ns, \@ds);
my ($sums,$cmps)=(0,0);
# Early days, just have one range, binary chop it.
while(scalar(@ns)==2) {
my $nn = $ns[0]+$ns[1];
my $nd = $ds[0]+$ds[1];
$sums+=2;
print("\n[ $ns[0]/$ds[0] $nn/$nd $ns[1]/$ds[1] ]\n");
if($nn/$nd < $lower) {
$cmps--;
print("$nn/$nd is not in range, prune left\n");
$ns[0]=$nn;
$ds[0]=$nd;
} elsif($nn/$nd > $upper) {
print("$nn/$nd is not in range, prune right\n");
$ns[1]=$nn;
$ds[1]=$nd;
} else {
print("$nn/$nd is in range\n");
printf("%i/%i = %0.*f\n", $nn,$nd, $digits+3, $nn/$nd);
splice(@ns,1,0,$nn);
splice(@ds,1,0,$nd);
# and break...
}
$cmps+=2;
print("=> "); pretty(\@ns, \@ds);
print("work=$sums sums, $cmps cmps\n");
}
# With one guaranteed mid-point in the range, we're now doubling our
# array length each time through the loop, only pruning max 1 from
# each end.
while(1) {
my @nn = map { $ns[$_>>1] + ($_&1 ? $ns[($_+1)>>1] : 0) } (0..$#ns*2);
my @nd = map { $ds[$_>>1] + ($_&1 ? $ds[($_+1)>>1] : 0) } (0..$#ds*2);
$sums+=$#ns*2;
print("\n"); pretty(\@nn, \@nd);
my $shift=0;
if($nn[1]/$nd[1] < $lower) {
print("$nn[1]/$nd[1] is not in range, prune left\n");
$shift=1;
} else {
print("$nn[1]/$nd[1] is in range\n");
printf("%i/%i = %0.*f\n", $nn[1],$nd[1], $digits+3, $nn[1]/$nd[1]);
}
my $i=1;
while(($i+=2) < $#nn-2) {
printf("%i/%i = %0.*f\n", $nn[$i],$nd[$i], $digits+3, $nn[$i]/$nd[$i]);
}
if($nn[$i]/$nd[$i] > $upper) {
print("$nn[$i]/$nd[$i] is not in range, prune right\n");
--$i;
} else {
print("$nn[$i]/$nd[$i] is in range\n");
printf("%i/%i = %0.*f\n", $nn[$i],$nd[$i], $digits+3, $nn[$i]/$nd[$i]);
}
$cmps+=2;
@ns=splice(@nn,$shift,$i+2-$shift);
@ds=splice(@nd,$shift,$i+2-$shift);
print("=> "); pretty(\@ns, \@ds);
print("work=$sums sums, $cmps cmps\n");
}
sub pretty
{
my ($ns,$ds)=@_;
print("[", (map{" $ns->[$_]/$ds->[$_]"}(0..$#$ns)), " ]\n");
}
============ 8< end --
fareyprox.pl =====================
phil@razspaz:/tmp$ ./
fareyprox.pl 3.14159
Searching for 3.14159, treating as 3 + [0.141585,0.141595]
Starting with: [ 0/1 1/1 ]
[ 0/1 1/2 1/1 ]
1/2 is not in range, prune right
=> [ 0/1 1/2 ]
work=2 sums, 2 cmps
[...]
[ 14/99 15/106 1/7 ]
15/106 is not in range, prune left
=> [ 15/106 1/7 ]
work=42 sums, 27 cmps
[ 15/106 16/113 1/7 ]
16/113 is in range
16/113 = 0.14159292
=> [ 15/106 16/113 1/7 ]
work=44 sums, 29 cmps
[ 15/106 31/219 16/113 17/120 1/7 ]
31/219 is not in range, prune left
17/120 is not in range, prune right
=> [ 31/219 16/113 17/120 ]
work=48 sums, 31 cmps
[...
that 16/113 will never be pruned, so denominators are now guaranteed
to increment by at least 113 at every step
...]
[ 143/1010 159/1123 16/113 145/1024 129/911 ]
159/1123 is in range
159/1123 = 0.14158504
145/1024 is not in range, prune right
=> [ 143/1010 159/1123 16/113 145/1024 ]
work=80 sums, 47 cmps
[ 143/1010 302/2133 159/1123 175/1236 16/113 161/1137 145/1024 ]
302/2133 is not in range, prune left
175/1236 = 0.14158576
161/1137 is not in range, prune right
=> [ 302/2133 159/1123 175/1236 16/113 161/1137 ]
work=86 sums, 49 cmps
[...]
In the thousands already, so superficially it seems to be asymptotically
better than brute force. However, it's finding them massively out of order.
By the time it's spitting out only denominators greater than:
287/2027 = 0.14158855
it has done
work=622 sums, 63 cmps
and it's already started looking as deep as
5742/40555 = 0.14158550.
and just to do one more round takes it to
work=1138 sums, 65 cmps
which means the work is growing exponentially for only linear more progress.
Worse than brute force!
However, that's because I'm not throwing away fractions that have become
useless. Any fraction whose denominator exceeds a limit value (say $denom),
should be discarded immediately, and its neighbours marked as no longer
participating in that direction. Only sums where both sides are participating
towards each other should be performed (and any fraction who loses his 2nd
neighbour should also be discarded.
Anyone wanna code that up to see if it's a nett win in the long run?