【问题标题】:Finding a rational approximation of a real number when both the numerator and denominator have a restricted range当分子和分母都有一个有限的范围时,找到一个实数的有理逼近
【发布时间】:2021-12-18 07:25:46
【问题描述】:

我正在尝试学习如何在分子和分母都受到约束时找到实数的有理近似值。我现在看了很多页,包括以下两页,并了解了连分数法、Farey 序列和 Stern-Brocot 树。但是,在所有这些示例中,分子或分母都是不受约束的。

Algorithm for simplifying decimal to fractions
https://gist.github.com/mikeando/7073d62385a34a61a6f7

这是我的情况:

我正在测试混合信号 IC。
在我们的一项测试中,为了找到 IC 的最大工作频率,将进入 IC 的时钟信号设置为 12 MHz 并不断降低,直到 IC 能够运行简单的数字序列。
测试平台的主时钟范围为 25 到 66 MHz,设置它的函数采用 double。
在当前版本的测试中,它被设置为恒定的 50.0 MHz,然后在循环中调用对该频率进行分频的函数。除数是一个整数,可以是 1 到 4096 之间的任意值。

但是,这会导致测量不准确。
设备始终通过:
50 / 5 = 10 兆赫
50 / 6 = 8.333 兆赫

如果可能,为了获得更精确的测量结果,我希望能够在每次循环迭代中更改主时钟的频率和时钟除数。这就是为什么我试图学习如何编写像连分数算法这样的东西,同时对分子和分母都有约束。我正在设想这样的事情:

while(dFmax > dFmin)
{
    std::pair<double, int> bestSettings = GetBestClkSettings(dFmax);
    double dFreq = bestSettings.first;
    int iDiv = bestSettings.second;

    // Set up clock and timesets        
    clkset(dFreq);
    clkdivide(iDiv);

    // Run pattern
    // ...
    
    // Get results
    // ...
    
    dFmax -= 0.1;
}

我不仅花了几个小时试验第二个链接中的代码,而且还尝试编写一个函数,使用二进制搜索之类的东西来看看会发生什么。我完全意识到这是我无法用来实现目标的糟糕代码;我只是想表明我一直一直在尝试。

#include <iostream>
#include <stdio.h>
#include <cmath>


// The fraction struct and the main() function were largely taken from:
// https://gist.github.com/mikeando/7073d62385a34a61a6f7

struct fraction {
    int n;
    int d;
    
    fraction()
    {
        this->n = -1;
        this->d = -1;
    }
    fraction(int n, int d)
    {
        this->n = n;
        this->d = d;
    }
    
    double asDouble()
    {
        double dReal = static_cast<double>(n) / static_cast<double>(d);
        return dReal;
    }
};



fraction ApproximateFrequency(double dTargetFreqMHz, double dTol)
{
    fraction result;
    
    if (dTargetFreqMHz < (25.0 / 4096) || dTargetFreqMHz > 66.0)
    {
        return result;
    }
    else if (dTargetFreqMHz >= 25.0 && dTargetFreqMHz <= 66.0)
    {
        result.n = static_cast<int>(dTargetFreqMHz);
        result.d = 1;
        return result;
    }

    int iFrqLo = 25;
    int iFrqHi = 66;
    int iDivLo = 1;
    int iDivHi = 4096;

    int iFrqCurr = (iFrqLo + iFrqHi) / 2;
    int iDivCurr = (iDivLo + iDivHi) / 2;
    double dFreq = static_cast<double>(iFrqCurr) / static_cast<double>(iDivCurr);
    double dPrevFreq = 0;
    int iNumIters = 1;
    
    while (fabs(dTargetFreqMHz - dFreq) > dTol && fabs(dFreq - dPrevFreq) > 1e-8 && iNumIters < 25)
    {
        dPrevFreq = dFreq;
        
        if (dFreq < dTargetFreqMHz)
        {
            // The frequency needs to be increased.
            
            // The clock frequency could be increased:
            int iFrqNew = (iFrqCurr + iFrqHi) / 2;
            double dFrqIfClkInc = static_cast<double>(iFrqNew) / static_cast<double>(iDivCurr);
            double dClkIncDiff = fabs(dTargetFreqMHz - dFrqIfClkInc);
            
            // Or the divider could be decreased:
            int iDivNew = (iDivLo + iDivCurr) / 2;
            double dFrqIfDivDec = static_cast<double>(iFrqCurr) / static_cast<double>(iDivNew);
            double dDivDecDiff = fabs(dTargetFreqMHz - dFrqIfDivDec);
            
            // Find the option that produces a better result:
            if (dClkIncDiff < dDivDecDiff && iFrqNew >= 25 && iFrqNew <= 66)
            {
                iFrqCurr = iFrqNew;
            }
            else if (dDivDecDiff < dClkIncDiff && iDivNew >= 1 && iDivNew <= 4096)
            {
                iDivCurr = iDivNew;
            }
        }
        else
        {
            // The frequency needs to be decreased.
            
            // The clock frequency could be decreased:
            int iFrqNew = (iFrqLo + iFrqCurr) / 2;
            double dFrqIfClkDec = static_cast<double>(iFrqNew) / static_cast<double>(iDivCurr);
            double dClkDecDiff = fabs(dTargetFreqMHz - dFrqIfClkDec);
            
            // Or the divider could be increased:
            int iDivNew = (iDivCurr + iDivHi) / 2;
            double dFrqIfDivInc = static_cast<double>(iFrqCurr) / static_cast<double>(iDivNew);
            double dDivIncDiff = fabs(dTargetFreqMHz - dFrqIfDivInc);
            
            // Find the option that produces a better result:
            if (dClkDecDiff < dDivIncDiff && iFrqNew >= 25 && iFrqNew <= 66)
            {
                iFrqCurr = iFrqNew;
            }
            else if (dDivIncDiff < dClkDecDiff && iDivNew >= 1 && iDivNew <= 4096)
            {
                iDivCurr = iDivNew;
            }
        }
        
        // See the frequency attainable with the current settings
        dFreq = static_cast<double>(iFrqCurr) / static_cast<double>(iDivCurr);
        std::cout << "prev = " << dPrevFreq << ", current = " << dFreq << std::endl;
        iNumIters++;
    }
    
    result.n = iFrqCurr;
    result.d = iDivCurr;
    return result;
}



int main(int argc, char* argv[])
{
    double dTargetFreqMHz = 20.0;
    std::cout << "Target: " << dTargetFreqMHz << "\n\n";
    double dTol = 0.05;
    fraction mcf = ApproximateFrequency(dTargetFreqMHz, dTol);
    printf("tol=%f, n/d = %d/%d = %f (err=%f)\n", dTol, mcf.n, mcf.d, mcf.asDouble(), mcf.asDouble()-dTargetFreqMHz);
}

任何建议或提示将不胜感激。提前谢谢你。

【问题讨论】:

    标签: c++ algorithm


    【解决方案1】:

    由于您的范围非常有限,您可以强行使用它。只有 172,032 种可能的分子和分母组合需要检查。通过从 25 迭代到 66 并计算最接近的两个分母,可以提高算法的效率,在这种情况下,您只需检查 84 种可能性:

    fraction ApproximateFrequency(double dTargetFreqMHz, double dTol)
    {
        fraction result;
    
        if (dTargetFreqMHz < (25.0 / 4096) || dTargetFreqMHz > 66.0)
        {
            return result;
        }
        else if (dTargetFreqMHz >= 33.0 && dTargetFreqMHz <= 66.0)
        {
            result.n = static_cast<int>(dTargetFreqMHz);
            result.d = 1;
            return result;
        }
    
        double smallestError = 66.0;
        int closestNum = 0;
        int closestDenom = 0;
        for (int num = 25; num <= 66; num++)
        {
            int denom = floor((double)num / dTargetFreqMHz);
            if (denom >= 1 && denom <= 4096)
            {
                double freq = (double)num / double(denom);
                double err = fabs(dTargetFreqMHz - freq);
    
                if (err < smallestError)
                {
                    closestNum = num;
                    closestDenom = denom;
                    smallestError = err;
                }
                if (denom <= 4095)
                {
                    freq = (double)num / double(denom + 1);
                    err = fabs(dTargetFreqMHz - freq);
                    if (err < smallestError)
                    {
                        closestNum = num;
                        closestDenom = denom + 1;
                        smallestError = err;
                    }
                }
            }
        }
    
        result.n = closestNum;
        result.d = closestDenom;
    
        return result;
    }
    

    没有使用 dTol 参数,因此您可以去掉它。

    【讨论】:

    • 非常感谢!这很好用,而且很容易理解。显然我想多了这个问题。您还帮助我意识到,即使范围没有受到如此限制,也没有关系,因为该测试未用于生产,因此速度并不重要。再次感谢!
    猜你喜欢
    • 2022-01-05
    • 2011-05-22
    • 2019-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-15
    • 2019-07-23
    • 1970-01-01
    相关资源
    最近更新 更多