22

我希望用户能够输入以下分数:

 1/2
 2 1/4
 3

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

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

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

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

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

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

我怎样才能做到这一点?

4

15 回答 15

25

有时您需要找到一种方法来执行此操作,并且舍入是可以接受的。因此,如果您决定适合您的舍入范围,您可以构建这样的函数。将小数转换为最匹配的分数。您可以通过添加更多要测试的分母来扩展准确性。

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;
}
于 2012-02-04T18:54:08.323 回答
19

我想我也会存储字符串表示形式,因为一旦你运行数学,你就不会把它找回来!

而且,这是一个快速n脏计算函数,不能保证:

$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.

于 2009-12-23T17:03:33.280 回答
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();

?>
于 2009-12-23T17:08:51.253 回答
5

这是一个首先确定有效分数的解决方案(尽管不一定是最简单的分数)。所以 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。出于我的目的,这是可以接受的,因为我处理的所有数字(标准测量值)无论如何都是有理数。

于 2013-06-14T20:46:37.737 回答
2

小伙伴们,这个有用吗?

[]s


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

        return "1/{$denominator}";
    }
    else {
        return $number;
    }
}
于 2013-02-15T00:19:55.430 回答
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;
}
于 2013-02-19T10:38:41.760 回答
0

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

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

于 2009-12-23T17:09:53.223 回答
0

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

当您不得不1.3333处理1 1/3.

这似乎很容易克服,但是如果您希望您的程序精确区分1/7901( ~ 1,2656625743576762435134793064169e-4) 和1/7907( ~ 1,2647021626406981155937776653598e-4) ......这将是一个真正的地狱!

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

如果您想了解更多信息,我建议您深入研究浮点问题......从维基百科开始。

于 2009-12-23T17:41:20.833 回答
0

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

即:如果你只需要处理一半、三次和四分之一,只需将所有内容乘以 12。

而且,如果您知道公分母,那么通过确切知道要搜索哪些数字而不是搜索所有可能的 n+1 数,这应该会大大降低搜索速度。

如果您必须处理许多不寻常的分数,例如 1/7、1/13 等,那么请坚持 Derek 的解决方案并存储原始值。

于 2009-12-23T17:45:13.850 回答
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;
   }
  }    
 }
}

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

于 2009-12-23T17:52:15.447 回答
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
)
于 2013-01-10T21:41:56.577 回答
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;
}
于 2014-03-25T23:37:31.273 回答
0

只需为 Derek 接受的答案添加更多逻辑 - 检查“除以零”和整数输入检查。

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;
}
于 2015-06-19T15:14:39.250 回答
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;
}
于 2015-08-14T15:47:43.843 回答
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上进行快速搜索,看看是否已经存在一些东西来解决我正在尝试做的事情,如果是,那么我可以利用社区已经投入的大量时间来解决问题问题(这将比我能够投入更多的时间)而且它也更有可能没有错误,已经过其他人的战斗测试,甚至可能有一个覆盖它的测试套件。

节省时间并提高质量。

于 2021-03-16T09:52:41.160 回答