web-dev-qa-db-ja.com

PHP小数を分数に変換して戻す?

ユーザーが次のような端数を入力できるようにしたいと思います。

 1/2
 2 1/4
 3

そして、それを対応する10進数に変換して、MySQLに保存します。そうすれば、並べ替えて他の比較を行うことができます。

ただし、ユーザーに表示するときに、小数を分数に戻すことができる必要があります

したがって、基本的に、分数文字列を10進数に変換する関数が必要です。

fraction_to_decimal("2 1/4");// return 2.25

小数を派閥文字列に変換できる関数:

decimal_to_fraction(.5); // return "1/2"

これどうやってするの?

21
JD Isaacks

文字列表現も保存すると思います。一度計算を実行すると、元に戻らないからです。

そして、これがquick-n-dirty計算関数であり、保証はありません。

$input = '1 1/2';
$fraction = array('whole' => 0);
preg_match('/^((?P<whole>\d+)(?=\s))?(\s*)?(?P<numerator>\d+)\/(?P<denominator>\d+)$/', $input, $fraction);
$result = $fraction['whole'] + $fraction['numerator']/$fraction['denominator'];
print_r($result);die;

ああ、完全を期すために、チェックを追加して$fraction['denominator'] != 0

19
Derek Illchuk

場合によっては、それを行う方法を見つける必要があり、丸めは許容されます。したがって、どの範囲の丸めが機能するかを決定した場合は、このような関数を作成できます。小数を最も一致する分数に変換します。テストする分母を追加することで、精度を拡張できます。

function decToFraction($float) {
    // 1/2, 1/4, 1/8, 1/16, 1/3 ,2/3, 3/4, 3/8, 5/8, 7/8, 3/16, 5/16, 7/16,
    // 9/16, 11/16, 13/16, 15/16
    $whole = floor ( $float );
    $decimal = $float - $whole;
    $leastCommonDenom = 48; // 16 * 3;
    $denominators = array (2, 3, 4, 8, 16, 24, 48 );
    $roundedDecimal = round ( $decimal * $leastCommonDenom ) / $leastCommonDenom;
    if ($roundedDecimal == 0)
        return $whole;
    if ($roundedDecimal == 1)
        return $whole + 1;
    foreach ( $denominators as $d ) {
        if ($roundedDecimal * $d == floor ( $roundedDecimal * $d )) {
            $denom = $d;
            break;
        }
    }
    return ($whole == 0 ? '' : $whole) . " " . ($roundedDecimal * $denom) . "/" . $denom;
}
21
mmcconkie

PEARのMath_Fractionクラスをsomeのニーズに使用できるようにするには

<?php

include "Math/Fraction.php";

$fr = new Math_Fraction(1,2);


// print as a string
// output: 1/2
echo $fr->toString();

// print as float
// output: 0.5
echo $fr->toFloat();

?>
8
codaddict

これは、最初に有効な分数を決定するソリューションです(ただし、必ずしも最も単純な分数である必要はありません)。つまり、0.05-> 5/100です。次に、分子と分母の最大公約数を決定して、最も単純な分数である1/20に減らします。

function decimal_to_fraction($fraction) {
  $base = floor($fraction);
  $fraction -= $base;
  if( $fraction == 0 ) return $base;
  list($ignore, $numerator) = preg_split('/\./', $fraction, 2);
  $denominator = pow(10, strlen($numerator));
  $gcd = gcd($numerator, $denominator);
  $fraction = ($numerator / $gcd) . '/' . ($denominator / $gcd);
  if( $base > 0 ) {
    return $base . ' ' . $fraction;
  } else {
    return $fraction;
  }
}

# Borrowed from: http://www.php.net/manual/en/function.gmp-gcd.php#69189
function gcd($a,$b) {
  return ($a % $b) ? gcd($b,$a % $b) : $b;
}

これには、gcdの純粋なPHP実装が含まれますが、gmpモジュールがインストールされていることが確実な場合は、 gcdに付属しているものを使用してください

他の多くの人が指摘しているように、有理数を使用する必要があります。したがって、1/7を小数に変換してから小数に戻そうとすると、精度が失われるために1/7に戻れないため、運が悪くなります。私が扱っているすべての数値(標準測定値)はとにかく有理数であるため、私の目的ではこれは許容されます。

4
Eric Anderson

上記の改善はほとんどありませんが、シンプルに保ちます。

function dec2frac($f) {
  $base = floor($f);
  if ($base) {
    $out = $base . ' ';
    $f = $f - $base;
  }
  if ($f != 0) {
    $d = 1;
    while (fmod($f, 1) != 0.0) {
      $f *= 2;
      $d *= 2;
    }
    $n = sprintf('%.0f', $f);
    $d = sprintf('%.0f', $d);
    $out .= $n . '/' . $d;
  }
  return $out;
}
2
doublejosh

仲間、これは助けになりますか?

[] s


function toFraction($number) {
    if (!is_int($number)) {
        $number = floatval($number);
        $denominator = round(1 / $number);

        return "1/{$denominator}";
    }
    else {
        return $number;
    }
}
1
Marlon Pascoal

私はこれに対するいくつかの解決策をブログに投稿しました。私が取った最新のアプローチは次のとおりです。 http://www.carlosabundis.com/2014/03/25/converting-decimals-to-fractions-with- php-v2 /

    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;
}
0
Carlos Abundis

小数への分数は非常に簡単で、解決策はたくさんあります。文字列をトリミングし、スペースを「+」に置き換え、スペース以外のものを使用します/、。または ''の数字を使用して、 'eval'を実行します。

小数から小数への変換は事実上不可能です。特に、小数を最初に2進数に変換する必要があるためです。この時点で、精度が大幅に低下します。アカデミックな演習として..... 20976/41953と1/2の違いで生きることができる場合は、事前定義された分数のあいまい一致を試すことができます。

(おそらく同じアルゴリズムを実装するためのより良い方法がありますが、それは読者のための演習として残しておきます)。

define('DECIMAL_DIGITS',5);

function decimal_2_frac($inp_decimal)
{
  static $fracs;
  if (!is_array($fracs)) {
    init_fracs($fracs);
  }
  $int_part=(integer)$inp_decimal;
  $inp_decimal=$inp_decimal-$int_part;
  $candidate='';
  $distance=10;
  foreach ($fracs as $decimal=>$frac) {
     if (abs($decimal-$inp_decimal)<$distance) {
       $candidate=$frac;
       $distance=abs($decimal-$inp_decimal);
     }
  if (abs($decimal-$inp_decimal)>$distance) {
     break;
  }
 }
 return $int_part . ' ' . $candidate;
}

function init_fracs(&$fracs)
{
   $fracs=array(); 
   for ($x=2;$x<(5*DECIMAL_DIGITS);$x++) {
       // there's probably a beter way to calculate the loop limit
      for ($y=1; $y<$x; $y++) {
         $decimal=round($y/$x,DECIMAL_DIGITS);
         $frac="$x/$y";
         if (!array_key_exists($decimal,$fracs)) {
         $fracs[$decimal]=$frac;
   }
  }    
 }
}

しかし、個人的には、元の表現をデータベースの別のフィールドに保存するだけです。

0
symcbean

アプローチは、10進値を取得し、整数が得られるまで2、3、4などを掛けることです。

しかし、私はデレクの答えに固執します。ユーザーがnをhighにn /(n + 1)を挿入するとどうなるかを推測します。このようなアルゴリズムでは、n +1までのすべての数値をスキャンする必要があります。言うまでもなく、近似の問題が発生する可能性があります。

0
Jir
function dec2frac($f)
{
    $d = 1

    while (fmod($f, 1) != 0.0) {
        $f *= 2;
        $d *= 2;
    }

    $n = sprintf('%.0f', $f);
    $d = sprintf('%.0f', $d);

    return array($n, $d);
}

次に$f == $n / $d

例えば:

print_r(dec2frac(3.1415926));

出力:

Array
(
    [0] => 3537118815677477  // $n
    [1] => 1125899906842624  // $d
)
0
function frac2dec($fraction) {
    list($whole, $fractional) = explode(' ', $fraction);

    $type = empty($fractional) ? 'improper' : 'mixed';

    list($numerator, $denominator) = explode('/', $type == 'improper' ? $whole : $fractional);

    $decimal = $numerator / ( 0 == $denominator ? 1 : $denominator );

    return $type == 'improper' ? $decimal : $whole + $decimal;
}
0
ricanontherun

デレクの受け入れられた答えにもう少しロジックを追加するだけです-「ゼロ除算」と整数入力チェックをチェックしてください。

function fractionToDec($input) {
    if (strpos($input, '/') === FALSE) {
        $result = $input;
    } else {
        $fraction = array('whole' => 0);
        preg_match('/^((?P<whole>\d+)(?=\s))?(\s*)?(?P<numerator>\d+)\/(?P<denominator>\d+)$/', $input, $fraction);
        $result = $fraction['whole'];

        if ($fraction['denominator'] > 0)
            $result += $fraction['numerator'] / $fraction['denominator'];
    }

    return $result;
}
0
Stan

フロートは十分に正確ではないため、深刻な問題に直面する必要があります。

1.3333を処理する必要がある場合、PHPはこの値を推定します...したがって、1 1/3に変換することはできません。

克服するのは簡単なようですが、プログラムで1/7901~ 1,2656625743576762435134793064169e-4)と1/7907~ 1,2647021626406981155937776653598e-4)を正確に区別したい場合...これは実際のことです地獄!!

私見ですが、数学を扱いたい場合は、外部ライブラリに依存する必要があります...またはPHP Matlabと通信するようにしてください。

詳細を知りたい場合は、浮動小数点の問題を掘り下げることをお勧めします... wikipedia で始まります。

0
Arno

限られた量の分母のみが使用されている場合、Jirのアプローチのバリエーションが実際に機能する可能性があります。つまり、すべてに最小公分母を掛けます(そして、結果を丸めて、近似のために残りの小数を破棄します)。

つまり、半分、3倍、4分の1だけを処理する必要がある場合は、すべてに12を掛けます。

また、最小公分母がわかっている場合は、可能なすべてのn + 1を検索するのではなく、検索する番号を正確に知ることで、検索速度を大幅に低下させるはずです。

1/7、1/13など、多くの異常な分数をうまく処理する必要がある場合は、Derekのソリューションに固執し、元の値も保存します。

0
DrYak