计算某些界限内的最大有效分数

时间:2010-12-08 19:43:46

标签: c# algorithm optimization math mathematical-optimization

我正在尝试在仅接受整体买入/卖出金额的市场上进行与准确汇率相匹配的货币交易。我想以特定的速度进行最大规模的交易。这是一个玩具程序,而不是真正的交易机器人,所以我使用的是C#。

我需要一种能在合理的时间内返回答案的算法,即使分子和分母可能很大(100000 +)。

static bool CalcBiggestRationalFraction(float target_real, float epsilon, int numerator_max, int denominator_max, out int numerator, out int denominator)
{
    // target_real is the ratio we are tryig to achieve in our output fraction (numerator / denominator)
    // epsilon is the largest difference abs(target_real - (numerator / denominator)) we are willing to tolerate in the answer
    // numerator_max, denominator_max are the upper bounds on the numerator and the denominator in the answer
    //
    // in the case where there are multiple answers, we want to return the largest one
    //
    // in the case where an answer is found that is within epsilon, we return true and the answer.
    // in the case where an answer is not found that is within epsilon, we return false and the closest answer that we have found.
    //
    // ex: CalcBiggestRationalFraction(.5, .001, 4, 4, num, denom) returns (2/4) instead of (1/2).


}

在我想到我之前,我问了一个类似的问题(http://stackoverflow.com/questions/4385580/finding-the-closest-integer-fraction-to-a-given-random-real)实际上是试图完成,事实证明我正试图解决一个不同但相关的问题。

3 个答案:

答案 0 :(得分:6)

解决问题的规范方法是使用continued fraction expansion。请特别注意this section

答案 1 :(得分:2)

如果你想要未减少的分数,那么这是你可以做的一个优化:因为你永远不会对 n / 2 感兴趣,因为你想要 2n / 4 4n / 8 1024n / 2048 ,我们只需要检查一些数字。一旦我们检查2的任意倍数,我们就永远不需要检查2.因此,我相信您可以尝试分母denominator_maxdenominator_max/2,并且您将隐含地检查所有这些因素数字,即2denominator_max/2的所有内容。

我目前不在编译器中,所以我没有检查这段代码的正确性,甚至没有检查它编译,但它应该很接近。

static bool CalcBiggestRationalFraction(float target_real, float epsilon, 
    int numerator_max, int denominator_max, 
    out int numerator, out int denominator)
{
    if((int)Math.Round(target_real * denominator_max) > numerator_max)
    {
        // We were given values that don't match up.
        // For example, target real = 0.5, but max_num / max_den = 0.3
        denominator_max = (int)(numerator_max / target_real);
    }

    float bestEpsilon = float.MAX_VALUE;
    for(int den = denominator_max; den >= denominator_max/2, den--)
    {
        int num = (int)Math.Round(target_real * den);
        float thisEpsilon = Math.abs(((float)num / den) - target_real);
        if(thisEpsilon < bestEpsilon)
        {
            numerator = num;
            denominator = den;
            bestEpsilon = thisEpsilon;
        }
    }

    return bestEpsilon < epsilon;
}

答案 2 :(得分:1)

我们试试这个:

首先,我们需要将浮动变成一个分数。我能想到的最简单的方法是找到epsilon的数量级,将float乘以该顺序,并截断得到分子。

long orderOfMagnitude = 1
while(epsilon * orderOfMagnitude <1)
   orderOfMagnitude *= 10;

numerator = (int)(target_real*orderOfMagnitude);
denominator = orderOfMagnitude;

//sanity check; if the initial fraction isn't within the epsilon, then add sig figs until it is
while(target_real - (float)numerator / denominator > epsilon)
{
   orderOfMagnitude *= 10;
   numerator = (int)(target_real*orderOfMagnitude);
   denominator = orderOfMagnitude;
}

现在,我们可以将分数降低到最低限度。我所知道的最有效的方法是尝试除以小于或等于分子和分母中较小者的平方根的所有素数。

var primes = new List<int>{2,3,5,7,11,13,17,19,23}; //to start us off

var i = 0;
while (true)
{
   if(Math.Sqrt(numerator) < primes[i] || Math.Sqrt(denominator) < primes[i]) break;

   if(numerator % primes[i] == 0 && denominator % primes[i] == 0)
   {
      numerator /= primes[i];
      denominator /= primes[i];
      i=0;
   }
   else
   {
      i++;
      if(i > primes.Count)
      {
        //Find the next prime number by looking for the first number not divisible
        //by any prime < sqrt(number).
        //We are actually unlikely to have to use this, because the denominator
        //is a power of 10, so its prime factorization will be 2^x*5^x
        var next = primes.Last() + 2;
        bool add;
        do
        {
           add = true;
           for(var x=0; primes[x] <= Math.Sqrt(next); x++)
              if(next % primes[x] == 0)
              {
                add = false;
                break;
              }

           if(add)
              primes.Add(next);
           else
              next+=2;   
        } while(!add);
      }
   }
}