Based on @APerson's and @Jeff Monteiro's answers I've created PHP version of Farey fractions that will be simplified to whole values with fractions with lowest possible denominator:
<?php
class QuantityTransform
{
/**
* @see https://stackoverflow.com/questions/14330713/converting-float-decimal-to-fraction
*/
public static function decimalToFraction(float $decimal, $glue = ' ', int $limes = 10): string
{
if (null === $decimal || $decimal < 0.001) {
return '';
}
$wholeNumber = (int) floor($decimal);
$remainingDecimal = $decimal - $wholeNumber;
[$numerator, $denominator] = self::fareyFraction($remainingDecimal, $limes);
// Values rounded to 1 should be added to base value and returned without fraction part
if (is_int($simplifiedFraction = $numerator / $denominator)) {
$wholeNumber += $simplifiedFraction;
$numerator = 0;
}
return (0 === $wholeNumber && 0 === $numerator)
// Too small values will be returned in original format
? (string) $decimal
// Otherwise let's format value - only non-0 whole value / fractions will be returned
: trim(sprintf(
'%s%s%s',
(string) $wholeNumber ?: '',
$wholeNumber > 0 ? $glue : '',
0 === $numerator ? '' : ($numerator . '/' . $denominator)
));
}
/**
* @see https://stackoverflow.com/a/14330799/842480
*
* @return int[] Numerator and Denominator values
*/
private static function fareyFraction(float $value, int $limes): array
{
if ($value < 0) {
[$numerator, $denominator] = self::fareyFraction(-$value, $limes);
return [-$numerator, $denominator];
}
$zero = $limes - $limes;
$lower = [$zero, $zero + 1];
$upper = [$zero + 1, $zero];
while (true) {
$mediant = [$lower[0] + $upper[0], $lower[1] + $upper[1]];
if ($value * $mediant[1] > $mediant[0]) {
if ($limes < $mediant[1]) {
return $upper;
}
$lower = $mediant;
} elseif ($value * $mediant[1] === $mediant[0]) {
if ($limes >= $mediant[1]) {
return $mediant;
}
if ($lower[1] < $upper[1]) {
return $lower;
}
return $upper;
} else {
if ($limes < $mediant[1]) {
return $lower;
}
$upper = $mediant;
}
}
}
}
Then you san use it like:
QuantityTransform::decimalToFraction(0.06); // 0.06
QuantityTransform::decimalToFraction(0.75); // 3/4
QuantityTransform::decimalToFraction(1.75, ' and '); // 1 and 3/4
QuantityTransform::decimalToFraction(2.33, ' and '); // 2 and 1/3
QuantityTransform::decimalToFraction(2.58, ' ', 5); // 2 3/5
QuantityTransform::decimalToFraction(2.58, ' & ', 10); // 2 & 4/7
QuantityTransform::decimalToFraction(1.97); // 2