Converting float decimal to fraction
Continued fractions can be used to find rational approximations to real numbers that are "best" in a strict sense. Here's a PHP function that finds a rational approximation to a given (positive) floating point number with a relative error less than $tolerance
:
<?phpfunction float2rat($n, $tolerance = 1.e-6) { $h1=1; $h2=0; $k1=0; $k2=1; $b = 1/$n; do { $b = 1/$b; $a = floor($b); $aux = $h1; $h1 = $a*$h1+$h2; $h2 = $aux; $aux = $k1; $k1 = $a*$k1+$k2; $k2 = $aux; $b = $b-$a; } while (abs($n-$h1/$k1) > $n*$tolerance); return "$h1/$k1";}printf("%s\n", float2rat(66.66667)); # 200/3printf("%s\n", float2rat(sqrt(2))); # 1393/985printf("%s\n", float2rat(0.43212)); # 748/1731
I have written more about this algorithm and why it works, and even a JavaScript demo here: https://web.archive.org/web/20180731235708/http://jonisalonen.com/2012/converting-decimal-numbers-to-ratios/
Farey fractions can be quite useful in this case.
They can be used to convert any decimal into a fraction with the lowest possible denominator.
Sorry - I don't have a prototype in PHP, so here's one in Python:
def farey(v, lim): """No error checking on args. lim = maximum denominator. Results are (numerator, denominator); (1, 0) is 'infinity'.""" if v < 0: n, d = farey(-v, lim) return (-n, d) z = lim - lim # Get a "zero of the right type" for the denominator lower, upper = (z, z+1), (z+1, z) while True: mediant = (lower[0] + upper[0]), (lower[1] + upper[1]) if v * mediant[1] > mediant[0]: if lim < mediant[1]: return upper lower = mediant elif v * mediant[1] == mediant[0]: if lim >= mediant[1]: return mediant if lower[1] < upper[1]: return lower return upper else: if lim < mediant[1]: return lower upper = mediant
Converted Python code in answer from @APerson241 to PHP
<?phpfunction farey($v, $lim) { // No error checking on args. lim = maximum denominator. // Results are array(numerator, denominator); array(1, 0) is 'infinity'. if($v < 0) { list($n, $d) = farey(-$v, $lim); return array(-$n, $d); } $z = $lim - $lim; // Get a "zero of the right type" for the denominator list($lower, $upper) = array(array($z, $z+1), array($z+1, $z)); while(true) { $mediant = array(($lower[0] + $upper[0]), ($lower[1] + $upper[1])); if($v * $mediant[1] > $mediant[0]) { if($lim < $mediant[1]) return $upper; $lower = $mediant; } else if($v * $mediant[1] == $mediant[0]) { if($lim >= $mediant[1]) return $mediant; if($lower[1] < $upper[1]) return $lower; return $upper; } else { if($lim < $mediant[1]) return $lower; $upper = $mediant; } }}// Example use:$f = farey(66.66667, 10);echo $f[0], '/', $f[1], "\n"; # 200/3$f = farey(sqrt(2), 1000);echo $f[0], '/', $f[1], "\n"; # 1393/985$f = farey(0.43212, 2000);echo $f[0], '/', $f[1], "\n"; # 748/1731