Converting float decimal to fraction

Posted on

Converting float decimal to fraction – Here in this article, we will share some of the most common and frequently asked about PHP problem in programming with detailed answers and code samples. There’s nothing quite so frustrating as being faced with PHP errors and being unable to figure out what is preventing your website from functioning as it should like php and math . If you have an existing PHP-based website or application that is experiencing performance issues, let’s get thinking about Converting float decimal to fraction.

I am trying to convert calculations keyed in by users with decimal results into fractions. For e.g.; 66.6666666667 into 66 2/3. Any pointers?
Thanx in advance

Solution :

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:

<?php
function 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("%sn", float2rat(66.66667)); # 200/3
printf("%sn", float2rat(sqrt(2)));  # 1393/985
printf("%sn", 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

<?php
function 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

Based upon @Joni’s answer, here is what I used to pull out the whole number.

function convert_decimal_to_fraction($decimal){

    $big_fraction = float2rat($decimal);
    $num_array = explode('/', $big_fraction);
    $numerator = $num_array[0];
    $denominator = $num_array[1];
    $whole_number = floor( $numerator / $denominator );
    $numerator = $numerator % $denominator;

    if($numerator == 0){
        return $whole_number;
    }else if ($whole_number == 0){
        return $numerator . '/' . $denominator;
    }else{
        return $whole_number . ' ' . $numerator . '/' . $denominator;
    }
}

function 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";
}

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

Here is my approach to this problem. Works fine with rational numbers.

function dec2fracso($dec){
    //Negative number flag.
    $num=$dec;
    if($num<0){
        $neg=true;
    }else{
        $neg=false;
    }

    //Extracts 2 strings from input number
    $decarr=explode('.',(string)$dec);

    //Checks for divided by zero input.
    if($decarr[1]==0){
        $decarr[1]=1;
        $fraccion[0]=$decarr[0];
        $fraccion[1]=$decarr[1];
        return $fraccion;
    }

    //Calculates the divisor before simplification.
    $long=strlen($decarr[1]);
    $div="1";
    for($x=0;$x<$long;$x++){
        $div.="0";
    }

    //Gets the greatest common divisor.
    $x=(int)$decarr[1];
    $y=(int)$div;
    $gcd=gmp_strval(gmp_gcd($x,$y));

    //Calculates the result and fills the array with the correct sign.
    if($neg){
        $fraccion[0]=((abs($decarr[0])*($y/$gcd))+($x/$gcd))*(-1);
    }else{
        $fraccion[0]=(abs($decarr[0])*($y/$gcd))+($x/$gcd);
    }
    $fraccion[1]=($y/$gcd);
    return $fraccion;
}

Sometimes it is necessary to treat only the decimals of a float. So I created a code that uses the function created by @Joni to present a format that is quite common in culinary recipes, at least in Brazil.

So instead of using 3/2 which is the result for 1.5, using the function I created it is possible to present the value 1 1/2, and if you want, you can also add a string to concatenate the values, creating something like “1 and 1/2 “.

function 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";
}

function float2fraction($float, $concat = ' '){
  
  // ensures that the number is float, 
  // even when the parameter is a string
  $float = (float)$float;

  if($float == 0 ){
    return $float;
  }
  
  // when float between -1 and 1
  if( $float > -1 && $float < 0  || $float < 1 && $float > 0 ){
    $fraction = float2rat($float);
    return $fraction;
  }
  else{

    // get the minor integer
    if( $float < 0 ){
      $integer = ceil($float);
    }
    else{
      $integer = floor($float);
    }

    // get the decimal
    $decimal = $float - $integer;

    if( $decimal != 0 ){

      $fraction = float2rat(abs($decimal));
      $fraction = $integer . $concat . $fraction;
      return $fraction;
    }
    else{
      return $float;
    }
  }
}

Usage e.g:

echo float2fraction(1.5);
will return "1 1/2"

Leave a Reply

Your email address will not be published. Required fields are marked *