【问题标题】:PHP convert decimal into fraction and back?PHP将小数转换为分数并返回?
【发布时间】:2010-12-29 13:38:20
【问题描述】:

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

 1/2
 2 1/4
 3

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

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

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

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

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

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

我该怎么做?

【问题讨论】:

  • 虽然对用户来说很好,但您要求做很多工作而不是定义三个字段 - 整数、提名者和分母。
  • 问题是,考虑到浮点的内部表示,你经常会得到简单的分数,但没有简单的非周期性二进制浮点表示。 (以十进制表示法考虑 1/7,而不能使用周期性表示法)。见这里:en.wikipedia.org/wiki/Binary_numeral_system#Fractions_in_binary
  • 如果您希望浮点精度达到大数字,请查看gist.github.com/anonymous/8ec4a38db78701e7bbc6 我对其进行了调整,以便它可以支持最大 int 值的精度。甚至可以使用大数数学来实现无限精度。

标签: php decimal fractions


【解决方案1】:

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

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】:

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

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

    【讨论】:

    • 好的,所以我需要存储小数和小数,但是我如何 gt 小数开始呢?
    • 谢谢!如果 $input = "1" 但是你的函数返回空字符串。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

      • 漂亮,喜欢你使用 gcd。
      【解决方案5】:

      小伙伴们,这能帮上忙吗?

      []s


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

      【讨论】:

        【解决方案6】:

        上面的改进不大,但保持简单。

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

        【讨论】:

          【解决方案7】:

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

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

          【讨论】:

          • 我很高兴有评论解释“-1”背后的动机。
          • 近似:确实,除非您使用更好的启发式方法将结尾确定为“整数”,否则此方法将不起作用。因为除了浮点数的常见舍入问题外,只有 2 次方(1/2、1/4、1/8 等)的分数具有非周期二进制表示法。与其他所有内容一样,即使在正确计算出分母之后,您也可能仍然有 0.0001 的余数。 (二进制浮点数 10*0.1 不完全是 1)。
          【解决方案8】:

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

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

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

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

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

          【讨论】:

            【解决方案9】:

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

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

            如果你知道公分母,这应该会大大降低搜索速度,因为它可以准确地知道要搜索的数字,而不是搜索所有可能的 n+1。

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

            【讨论】:

              【解决方案10】:

              小数的分数非常简单,并且有很多解决方案。我会修剪字符串,用“+”替换空格,以及空格以外的任何内容,/,。或带有 '' 的数字,然后通过 '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;
                 }
                }    
               }
              }
              

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

              【讨论】:

              • 呵呵,在init_fracs()的末尾应该有一个ksort($fracs)
              【解决方案11】:
              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
              )
              

              【讨论】:

                【解决方案12】:

                我为此发表了一篇博客文章,其中包含几个解决方案,我最近采用的方法是: 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;
                }
                

                【讨论】:

                  【解决方案13】:

                  只需为 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;
                  }
                  

                  【讨论】:

                    【解决方案14】:
                    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;
                    }
                    

                    【讨论】:

                      【解决方案15】:

                      使用第 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 上快速搜索,看看是否已经存在解决我正在尝试做的事情的东西,如果是,那么我可以利用社区已经投入的大量时间来解决问题(这将比我能够投入更多的时间)而且它也更有可能没有错误,已经过其他人的实战测试,甚至可能有一个测试套件覆盖它。

                      节省时间并提高质量。

                      【讨论】:

                        猜你喜欢
                        • 2020-09-04
                        • 2015-08-13
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2011-09-14
                        • 1970-01-01
                        • 1970-01-01
                        相关资源
                        最近更新 更多