PHP将小数转换为分数并返回?

时间:2009-12-23 17:01:19

标签: php decimal fractions

我希望用户能够输入如下部分:

 1/2
 2 1/4
 3

并将其转换为相应的十进制数,以便保存在MySQL中,这样我就可以通过它进行排序并对其进行其他比较。

但是我需要能够在向用户显示时将小数转换回分数

所以基本上我需要一个将分数字符串转换为十进制的函数:

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

和一个可以将小数转换为派系字符串的函数:

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

我该怎么做?

15 个答案:

答案 0 :(得分:19)

我想我也会存储字符串表示,因为一旦你运行数学运算,你就不会收回它!

而且,这是一个快速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

答案 1 :(得分:18)

有时您需要找到一种方法来实现它并且可以接受舍入。因此,如果你决定为你做什么范围的舍入,你可以建立这样的函数。将小数转换为最接近匹配的小数。您可以通过添加更多要测试的分母来扩展准确度。

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

答案 2 :(得分:8)

可以将PEAR的Math_Fraction类用于某些的需求

<?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();

?>

答案 3 :(得分:3)

这是首先确定有效分数的解决方案(尽管不一定是最简单的分数)。所以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模块,则可以use the one that comes with gcd

正如其他许多人所说,你需要使用有理数。因此,如果您将1/7转换为小数,然后尝试将其转换回小数,那么您将失去运气,因为精度损失将阻止它回到1/7。对于我的目的,这是可以接受的,因为我正在处理的所有数字(标准测量)无论如何都是有理数。

答案 4 :(得分:2)

上面几乎没有改进,但保持简单。

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

答案 5 :(得分:1)

哥们,这有帮助吗?

[]中


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

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

答案 6 :(得分:0)

一种方法是检索十进制值并将其乘以2,3,4等,直到得到一个整数。

但是,我坚持Derek给出的答案。猜猜当用户插入n /(n + 1)n为高时会发生什么。这样的算法必须扫描所有数字直到n + 1。 更不用说你很可能会遇到近似问题。

答案 7 :(得分:0)

你将不得不面对一个严重的问题,因为花车不够精确。

当您必须处理1.3333时,PHP会对此值进行估算......因此您永远无法将其转换为1 1/3

似乎很容易克服,但如果您希望自己的程序能够将1/7901~ 1,2656625743576762435134793064169e-4)与1/7907~ 1,2647021626406981155937776653598e-4)区别开来......这将是真是个地狱!!

恕我直言,如果你想处理数学,你应该依赖外部库......或者尝试让PHP与Matlab进行通信。

如果你想了解更多,我建议你挖掘浮点问题......从wikipedia开始。

答案 8 :(得分:0)

如果仅使用有限数量的分母,Jir方法的变体实际上可以起作用:将所有分数乘以最小公分母(并将结果四舍五入以丢弃由于近似值而产生的任何剩余小数)。

即:如果你只需要对付半身,女人和四分之一,只需将所有东西都乘以12。

如果您知道共同点,那么通过确切地知道要搜索哪些数字而不是搜索所有n + 1可能会大大降低搜索速度。

如果你必须处理很多不寻常的分数,比如1 / 7,1 / 13等,请坚持使用Derek的解决方案并存储原始值。

答案 9 :(得分:0)

小数到十进制是非常简单的,有很多解决方案。我会修剪字符串,用'+'替换空格,以及除空格之外的任何东西,/。或'''然后通过'eval'运行它的数字。

小数到分数几乎不可能正确执行 - 尤其是因为您的小数部分可能必须首先转换为二进制 - 此时您会失去很多精度。作为学术练习.....如果你可以忍受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;
   }
  }    
 }
}

但就个人而言,我只是将原始表示存储在数据库的单独字段中。

答案 10 :(得分:0)

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
)

答案 11 :(得分:0)

我发了一篇博文,其中有几个解决方案,我采用的最新方法是: 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;
}

答案 12 :(得分:0)

为Derek接受的答案添加更多逻辑 - 检查&#34;除以零&#34;和整数输入检查。

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

答案 13 :(得分: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;
}

答案 14 :(得分:0)

使用第 3 方库,例如: https://packagist.org/packages/phospr/fraction

用法:

$fraction = Fraction::fromFloat(1.5);
echo "Fraction is: " . $fraction->getNumerator() . '/' . $fraction->getDenominator();
echo "Float is: " . $fraction->toFloat();

我通常会在 https://packagist.org 上进行快速搜索,看看是否已经存在可以解决我正在尝试做的事情的东西,如果存在,那么我可以利用社区已经投入解决的大量时间问题(这将比我能够投入的时间多得多)而且它也更有可能没有错误,已经过其他人的实战测试,甚至可能有一个测试套件覆盖它。< /p>

节省时间并提高质量。