简体   繁体   English

将浮点小数转换为分数

[英]Converting float decimal to fraction

I am trying to convert calculations keyed in by users with decimal results into fractions.我正在尝试将用户输入的带有小数结果的计算转换为分数。 For eg;例如; 66.6666666667 into 66 2/3. 66.6666666667 变成 66 2/3。 Any pointers?任何指针? Thanx in advance提前谢谢

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 函数,它找到一个给定(正)浮点数的有理近似值,其相对误差小于$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("%s\n", float2rat(66.66667)); # 200/3
printf("%s\n", float2rat(sqrt(2)));  # 1393/985
printf("%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/我已经写了更多关于这个算法及其工作原理的文章,甚至还有一个 JavaScript 演示: https : //web.archive.org/web/20180731235708/http : //jonisalonen.com/2012/converting-decimal-numbers-to -比率/

Farey fractions can be quite useful in this case.在这种情况下,Farey 分数非常有用。

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:抱歉 - 我没有 PHP 原型,所以这里有一个 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将回答中的 Python 代码从 @APerson241 转换为 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.根据@Joni 的回答,这是我用来提取整数的内容。

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

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.所以我创建了一个代码,它使用@Joni 创建的函数来呈现一种在烹饪食谱中很常见的格式,至少在巴西是这样。

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 ".因此,不是使用 3/2 这是 1.5 的结果,而是使用我创建的函数来呈现值 1 1/2,如果需要,您还可以添加一个字符串来连接这些值,创建类似“1 和 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 eg:用法例如:

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

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:基于@APerson 和@Jeff Monteiro 的回答,我创建了 Farey 分数的 PHP 版本,该版本将简化为具有最低分母的分数的整数值:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM