Подтвердить что ты не робот

Преобразование float decimal в дроби

Я пытаюсь преобразовать вычисления, введенные пользователями с десятичными результатами во фракции. Например, 66.6666666667 - 66 2/3. Любые указатели? Заранее спасибо

4b9b3361

Ответ 1

Непрерывные дроби можно использовать для нахождения рациональных приближений к действительным числам, которые являются "лучшими" в строгом смысле. Здесь 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

Я написал больше об этом алгоритме и почему он работает, и даже демонстрацию JavaScript здесь: https://web.archive.org/web/20180731235708/http://jonisalonen.com/2012/converting-decimal-numbers-to -ratios/

Ответ 2

Преобразованный код 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

Ответ 3

В этом случае могут быть весьма полезны фракции Farey.

Они могут быть использованы для преобразования любого десятичного числа в дроби с наименьшим возможным знаменателем.

Извините - у меня нет прототипа в 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

Ответ 4

Основываясь на ответе @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";
}

Ответ 5

Вот мой подход к этой проблеме. Прекрасно работает с рациональными числами.

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