I'd like to, from inside my Perl script, go from a floating point number,
like 0.33333333333333333, to a fraction, in this case 1/3. Is that
possible? And if so, how?
I think the simplest approach is to use 'continued fractions'. Here's
some code:
sub number_to_fraction {
my $d = shift;
my $c = Convergents->new;
sub {
return unless defined $d;
my $int = int($d);
my $frac = $d - $int;
$d = $frac ? 1/$frac : undef;
$c->input($int);
my ($num, $den) = ($c->nth(-1));
wantarray ? ($num, $den) : $num/$den;
};
}
my $X = shift || 0.3333333333333;
my $fractions = number_to_fraction($X);
for (1..7) {
my ($n, $d) = $fractions->();
print "$n / $d\n";
}
package Convergents;
sub new {
my ($class) = @_;
bless {n=>[0, 1], d=>[1, 0]} => $class;
}
sub input {
my ($self, $n) = @_;
push @{$self->{n}}, $self->{n}[-1] * $n + $self->{n}[-2];
push @{$self->{d}}, $self->{d}[-1] * $n + $self->{d}[-2];
$self;
}
sub nth {
my ($self, $n) = @_;
($self->{n}[$n], $self->{d}[$n])
}
The function 'number_to_fraction' takes a number and returns an object
which generates fractions that are close to that number. The initial
fractions are simple but inaccurate; the later ones are more accurate
but more complicated. You need to arrange your program to decide
which one it wants to use. That's a policy matter that I can't really
help with.
For your example of 0.33333333333333333, the function generates
0 / 1
1 / 3
and then stops, which I think is just what you would want.
For 3.141592654, it generates
3 / 1 = 3.000000000000
22 / 7 = 3.142857142857
333 / 106 = 3.141509433962
355 / 113 = 3.141592920354
104348 / 33215 = 3.141592653921
1148183 / 365478 = 3.141592654004
1252531 / 398693 = 3.141592653997
2400714 / 764171 = 3.141592654000
18057529 / 5747890 = 3.141592654000
38515772 / 12259951 = 3.141592654000
Which as you can see includes the famous 22/7 approximation to pi, as
well as the slightly less famous 355/113 approximation. The 355/113
approximation is very close to the corect answer, which is why the
next value out of the object (10438/33215) has such a large denominator.
In general, the object wil never generate a fraction with a larger
denominator as long as there is a more accurate fraction with a
smaller denominator. That is, it generates the simplest and most
accurate fractions possible.
Hope this helps.
This code is in the public domain; feel free to use it in your
program.