Last active
May 17, 2019 03:47
-
-
Save ngocongcan/0aeef02bd0b9ff6ad9149505c844951a to your computer and use it in GitHub Desktop.
Converting exactly decimal to fraction
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class FractionHelper { | |
/* | |
* https://en.wikipedia.org/wiki/Continued_fraction | |
* Using this format instead float number (0.001) to avoid inaccurate value | |
* https://stackoverflow.com/questions/812815/php-intval-and-floor-return-value-that-is-too-low | |
*/ | |
const TOLERANCE = 1.e-4; | |
const PATTERN_FROM_STRING = '#^(-?\d+)(?:(?: (\d+))?/(\d+))?$#'; | |
public static function decimal2Fraction($x) { | |
if(is_null($x)) return null; | |
if(floor($x) - $x == 0) return floor($x); | |
$isNegative = $x < 0 ? -1 : 1 ; | |
$x = abs($x); | |
$tolerance = self::TOLERANCE; | |
$h1 = 1; $h2 = 0; | |
$k1 = 0; $k2 = 1; | |
$b = 1/$x; | |
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($x - $h1/$k1) >= ($x * $tolerance)); | |
$numerator = (int) $h1 * $isNegative; | |
$denominator = (int) $k1; | |
if ($numerator === $denominator) { | |
return '1'; | |
} | |
if (-1*$numerator === $denominator) { | |
return '-1'; | |
} | |
if (1 === $denominator) { | |
return (string) $numerator; | |
} | |
if (abs($numerator) > abs($denominator)) { | |
$whole = floor(abs($numerator) / $denominator); | |
if ($numerator < 0) { | |
$whole *= -1; | |
} | |
return sprintf('%d %d/%d', | |
$whole, | |
abs($numerator % $denominator), | |
$denominator | |
); | |
} | |
return sprintf('%d/%d', | |
$numerator, | |
$denominator | |
); | |
} | |
public static function fractionString2Decimal($string) { | |
if (preg_match(self::PATTERN_FROM_STRING, trim($string), $matches)) { | |
if (2 === count($matches)) { | |
// whole number | |
return floatval($matches[1]); | |
} else { | |
// either x y/z or x/y | |
if ($matches[2]) { | |
// x y/z | |
return (intval($matches[1]) + (intval($matches[2])/ intval($matches[3])) ); | |
} | |
// x/y | |
return (intval($matches[1])/ intval($matches[3])); | |
} | |
} | |
return floatval($string); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment