如何将float转换为fraction?

时间:2010-12-04 20:21:27

标签: algorithm math

这是一个家庭作业问题。我想写一个函数将float转换为一对整数:分子和分母。例如:float 0.5应转换为(1,2)。

我正在尝试smth。 (见下文)但坦率地说,这对我来说并不好看。

// f is the input float
int n = 1
while(fractional_part(f) > 0)
    f *= 10;
    n++
int m = f;
gcd = gcd(m, n)
return (m/gcd, n/gcd)

如何将浮点数转换为分数?

4 个答案:

答案 0 :(得分:1)

您可以使用Fraction library

但是,如果你想开发算法,这里有一个建议:

from math import floor
from fractions import gcd

def func(v, tol=1e-4):
    """
    Don't handle negative values.
    Use binary search to find the fraction of a float.
    The algorithm is based in a very simple theorem: If a < b then a < (a+b)/2 < b.
    """
    f = v - floor(v)
    lo = (0, 1)
    hi = (1, 1)
    while True:
        # mid = (lo + hi)/2
        # if lo = a/b and hi = c/d, then mid = (ad+bc)/(2ad)
        mid = (lo[0]*hi[1] + hi[0]*lo[1], 2*lo[1]*hi[1])
        # gcd to reduce fraction
        k = gcd(mid[0], mid[1])
        mid = (mid[0]/k, mid[1]/k)

        d = 1.*mid[0]/mid[1]
        # are we close enough?
        if abs(f - d) < tol:
            break
        # if we are above our goal, get high to middle
        elif d > f:
            hi = mid
        # if we are under our goal, get lower to middle
        else:
            lo = mid
    # Add integer part
    mid = (mid[0] + int(floor(v))*mid[1], mid[1])
    # Debug comparing to Fraction library solution.
    #print v, mid, Fraction('%s' % v)
    return mid

答案 1 :(得分:1)

考虑到浮点数总是在计算机内部表示为分数,分母的幂数为2(根据IEEE 754,32位浮点数的分母为2 ^ 24,64位浮点数的分母为2 ^ 53)。

这样做的一个特殊后果是,计算机不能代表大多数真实的数字,而只能代表理性数字的有限子集。但这确实是计算机可以执行的各种数值算法的充分子集;虽然这些算法总是考虑到上述限制而设计。

答案 2 :(得分:1)

我根据msbrogli的例子创建了C代码。包括防止溢出

/*******************************************************************/
/* calculate a num/den fraction to given float with smallest error */
/*******************************************************************/

RFIXED128 CalcFractionToFloat(FLOAT64 in_Value, FLOAT64 in_MaxError, UINT64 in_MaxValue)
{
    /* locals */
    UINT64    lv_Gcd;
    FLOAT64   lv_Now;
    FLOAT64   lv_NowError;
    FLOAT64   lv_Whole;
    FLOAT64   lv_Fract;
    RFIXED128 lv_NewAvg;
    RFIXED128 lv_Avg;
    RFIXED128 lv_Lo;
    RFIXED128 lv_Hi;


  // (default) max accepted error
  if (in_MaxError <= 0)
    in_MaxError = 1e-6;

  // get the whole part
  lv_Whole = floor(in_Value);

  // get the fractional part, this is in range of (lo..hi) aka (0..1)
  lv_Fract = in_Value - lv_Whole;

  // init lower boundary (LoNum/LoDen = 0/1 = 0)
  lv_Lo.num = 0;
  lv_Lo.den = 1;

  // init upper boundary (HiNum/HiDen = 1/1 = 1)
  lv_Hi.num = 1;
  lv_Hi.den = 1;

  // init output in case the first avg calculation overflows immediate
  lv_Avg.num = 0; 
  lv_Avg.den = 1; 

  // loop until error is below the limit
  for (;;)
  {
    // calculate the average:
    //
    //   average =  (lo+hi)/2
    //
    //           =  (LoNum/LoDen + HiNum/HiDen) / 2
    //
    //           = ((HiDen*LoNum)/(HiDen*LoDen) + (LoDen*HiNum)/(LoDen*HiDen)) / 2
    //
    //           =  (HiDen*LoNum + LoDen*HiNum) / (HiDen*LoDen*2)
    //
    lv_NewAvg.num = lv_Hi.den * lv_Lo.num + lv_Lo.den * lv_Hi.num;
    lv_NewAvg.den = lv_Hi.den * lv_Lo.den * 2;

    // check overflow if one is specified
    if (in_MaxValue)
      if (lv_NewAvg.num > in_MaxValue || lv_NewAvg.den > in_MaxValue)
        break;

    // calc greatest common divisor to reduce the average value
    lv_Gcd = CalcGreatestCommonDivisor64(lv_NewAvg.num, lv_NewAvg.den);

    // reduce the average value
    lv_Avg.num = lv_NewAvg.num / lv_Gcd;
    lv_Avg.den = lv_NewAvg.den / lv_Gcd;

    // reconstruct the value represented by this fraction
    lv_Now = (FLOAT64)lv_Avg.num / (FLOAT64)lv_Avg.den;

    // new absolute fractional error
    lv_NowError = fabsl(lv_Now - lv_Fract);

    // reached the goal?
    if (lv_NowError < in_MaxError)
      break;

    // binary search for better result
    if (lv_Now > in_Value)
      lv_Hi = lv_Avg;
    else
      lv_Lo = lv_Avg;
  }

  // add whole part
  lv_Avg.num += (INT)lv_Whole * lv_Avg.den;

  // return the result
  return lv_Avg;
}



-- additional code/definitions


// as numerator/denominator  - 64.64 signed FIXED-type, 128bit total
// - fraction is given by numerator/denominator
// - alias is 'rational'
typedef struct
{
  INT64  num;  // numerator, value aka whole part
  UINT64 den;  // denominator, fraction aka dividing part
} RFIXED128;


UINT64 CalcGreatestCommonDivisor64(UINT64 in_V1, UINT64 in_V2)
{
    /* locals */
    INT64 lv_PrevValQ;
    INT64 lv_PrevValR;
    INT64 lv_DivValQ;
    INT64 lv_DivValR;


  // validate
  if (!in_V1 || !in_V2)
    return FALSE;

  // divide larger by smaller
  if (in_V1 > in_V2)
  {
    // v1 is larger
    lv_DivValQ = in_V1;
    lv_DivValR = in_V2;
  }
  else
  {
    // v2 is larger
    lv_DivValQ = in_V2;
    lv_DivValR = in_V1;
  }

  // keep dividing the previous remainder with the new reminder until remainder is zero
  // - this is called "Euclid's algorithm"
  while (lv_DivValR > 0)
  {
    // remember previous remainder
    lv_PrevValQ = lv_DivValQ;
    lv_PrevValR = lv_DivValR;

    // divide again
    DivMod64(lv_DivValQ, lv_DivValR, &lv_DivValQ, &lv_DivValR);

    // previous remainder is next quotient
    lv_DivValQ = lv_PrevValR;
  }

  // the last remainder is the greatest common divisor
  return lv_PrevValR;
}

答案 3 :(得分:0)

See my answer to a very similar question。无论如何我会在这里重新发布:

ostringstream oss;
float num;
cin >> num;
oss << num;
string numStr = oss.str();
int i = numStr.length(), pow_ten = 0;
while (i > 0) {
    if (numStr[i] == '.')
        break;
    pow_ten++;
    i--;
}
for (int j = 1; j < pow_ten; j++) {
    num *= 10.0;
}
cout << static_cast<int>(num) << "/" << pow(10, pow_ten - 1) << endl;

通过将float转换为字符串值,您可以按相反的顺序遍历字符串,直到达到小数点。每次迭代都是10的幂,您需要将原始浮点值相乘以获得浮点值,小数点右边的全零。分数的分母将是您计算的功率(迭代次数)的十分之一。你需要将它降低到最低值,如果你知道GCD就很容易。