用于简化小数到分数的算法

时间:2011-02-26 02:55:54

标签: c# .net algorithm math

我尝试编写一种算法来将小数简化为一小部分,并意识到它并不太简单。令人惊讶的是,我在网上看到了我发现的所有代码,这些代码要么太长,要么在某些情况下不起作用。更令人讨厌的是,它们不适用于重复小数。我想知道是否会有一位数学家/程序员在这里理解所有涉及的过程,将小数简化为一小部分。任何人吗?

24 个答案:

答案 0 :(得分:41)

其他人给你的算法通过计算数字的Continued Fraction得到答案。这给出了一个分数序列,保证非常快速地收敛。然而,保证为您提供实数的距离epsilon内的最小分数。要找到你必须走Stern-Brocot tree

要做到这一点,你减去地板以获得[0,1]范围内的数字,然后你的估计值为0,你的估计值为1.现在进行二元搜索直到你足够接近。在每次迭代时,如果你的下限是a / b而你的上限是c / d,你的中间是(a + c)/(b + d)。测试你的中间部分对着x,然后让中间成为上部,下部,或者返回你的最终答案。

这是一些非惯用的(因此,即使你不懂语言,也可能是可读的)实现这种算法的Python。

def float_to_fraction (x, error=0.000001):
    n = int(math.floor(x))
    x -= n
    if x < error:
        return (n, 1)
    elif 1 - error < x:
        return (n+1, 1)

    # The lower fraction is 0/1
    lower_n = 0
    lower_d = 1
    # The upper fraction is 1/1
    upper_n = 1
    upper_d = 1
    while True:
        # The middle fraction is (lower_n + upper_n) / (lower_d + upper_d)
        middle_n = lower_n + upper_n
        middle_d = lower_d + upper_d
        # If x + error < middle
        if middle_d * (x + error) < middle_n:
            # middle is our new upper
            upper_n = middle_n
            upper_d = middle_d
        # Else If middle < x - error
        elif middle_n < (x - error) * middle_d:
            # middle is our new lower
            lower_n = middle_n
            lower_d = middle_d
        # Else middle is our best fraction
        else:
            return (n * middle_d + middle_n, middle_d)

答案 1 :(得分:26)

(2017年2月改进的代码 - 向下滚动到'优化'...)

(此答案末尾的算法比较表)

我在C#中实现了btilly's answer并且......

  • 添加了对负数的支持
  • 提供accuracy参数以指定最大值。相对误差,而不是最大值。绝对错误; 0.01会在值的1%范围内找到一小部分。
  • 提供优化
  • Double.NaNDouble.Infinity不受支持;你可能想要处理那些(example here)。
public Fraction RealToFraction(double value, double accuracy)
{
    if (accuracy <= 0.0 || accuracy >= 1.0)
    {
        throw new ArgumentOutOfRangeException("accuracy", "Must be > 0 and < 1.");
    }

    int sign = Math.Sign(value);

    if (sign == -1)
    {
        value = Math.Abs(value);
    }

    // Accuracy is the maximum relative error; convert to absolute maxError
    double maxError = sign == 0 ? accuracy : value * accuracy;

    int n = (int) Math.Floor(value);
    value -= n;

    if (value < maxError)
    {
        return new Fraction(sign * n, 1);
    }

    if (1 - maxError < value)
    {
        return new Fraction(sign * (n + 1), 1);
    }

    // The lower fraction is 0/1
    int lower_n = 0;
    int lower_d = 1;

    // The upper fraction is 1/1
    int upper_n = 1;
    int upper_d = 1;

    while (true)
    {
        // The middle fraction is (lower_n + upper_n) / (lower_d + upper_d)
        int middle_n = lower_n + upper_n;
        int middle_d = lower_d + upper_d;

        if (middle_d * (value + maxError) < middle_n)
        {
            // real + error < middle : middle is our new upper
            upper_n = middle_n;
            upper_d = middle_d;
        }
        else if (middle_n < (value - maxError) * middle_d)
        {
            // middle < real - error : middle is our new lower
            lower_n = middle_n;
            lower_d = middle_d;
        }
        else
        {
            // Middle is our best fraction
            return new Fraction((n * middle_d + middle_n) * sign, middle_d);
        }
    }
}

Fraction类型只是一个简单的结构。当然,请使用您自己喜欢的类型......(我喜欢Rick Davin的this one。)

public struct Fraction
{
    public Fraction(int n, int d)
    {
        N = n;
        D = d;
    }

    public int N { get; private set; }
    public int D { get; private set; }
}

2017年2月优化

对于某些值,例如0.010.001等,算法会经历数百或数千次线性迭代。为了解决这个问题,我实现了一种查找最终值的二进制方法 - 感谢btilly这个想法。在if - 语句中,替换以下内容:

// real + error < middle : middle is our new upper
Seek(ref upper_n, ref upper_d, lower_n, lower_d, (un, ud) => (lower_d + ud) * (value + maxError) < (lower_n + un));

// middle < real - error : middle is our new lower
Seek(ref lower_n, ref lower_d, upper_n, upper_d, (ln, ld) => (ln + upper_n) < (value - maxError) * (ld + upper_d));

以下是Seek方法实现:

/// <summary>
/// Binary seek for the value where f() becomes false.
/// </summary>
void Seek(ref int a, ref int b, int ainc, int binc, Func<int, int, bool> f)
{
    a += ainc;
    b += binc;

    if (f(a, b))
    {
        int weight = 1;

        do
        {
            weight *= 2;
            a += ainc * weight;
            b += binc * weight;
        }
        while (f(a, b));

        do
        {
            weight /= 2;

            int adec = ainc * weight;
            int bdec = binc * weight;

            if (!f(a - adec, b - bdec))
            {
                a -= adec;
                b -= bdec;
            }
        }
        while (weight > 1);
    }
}

算法比较表

您可能希望将表格复制到文本编辑器以进行全屏查看。

Accuracy: 1.0E-3      | Stern-Brocot                             OPTIMIZED   | Eppstein                                 | Richards                                 
Input                 | Result           Error       Iterations  Iterations  | Result           Error       Iterations  | Result           Error       Iterations  
======================| =====================================================| =========================================| =========================================
   0                  |       0/1 (zero)   0         0           0           |       0/1 (zero)   0         0           |       0/1 (zero)   0         0           
   1                  |       1/1          0         0           0           |    1001/1000      1.0E-3     1           |       1/1          0         0           
   3                  |       3/1          0         0           0           |    1003/334       1.0E-3     1           |       3/1          0         0           
  -1                  |      -1/1          0         0           0           |   -1001/1000      1.0E-3     1           |      -1/1          0         0           
  -3                  |      -3/1          0         0           0           |   -1003/334       1.0E-3     1           |      -3/1          0         0           
   0.999999           |       1/1         1.0E-6     0           0           |    1000/1001     -1.0E-3     2           |       1/1         1.0E-6     0           
  -0.999999           |      -1/1         1.0E-6     0           0           |   -1000/1001     -1.0E-3     2           |      -1/1         1.0E-6     0           
   1.000001           |       1/1        -1.0E-6     0           0           |    1001/1000      1.0E-3     1           |       1/1        -1.0E-6     0           
  -1.000001           |      -1/1        -1.0E-6     0           0           |   -1001/1000      1.0E-3     1           |      -1/1        -1.0E-6     0           
   0.50 (1/2)         |       1/2          0         1           1           |     999/1999     -5.0E-4     2           |       1/2          0         1           
   0.33... (1/3)      |       1/3          0         2           2           |     999/2998     -3.3E-4     2           |       1/3          0         1           
   0.67... (2/3)      |       2/3          0         2           2           |     999/1498      3.3E-4     3           |       2/3          0         2           
   0.25 (1/4)         |       1/4          0         3           3           |     999/3997     -2.5E-4     2           |       1/4          0         1           
   0.11... (1/9)      |       1/9          0         8           4           |     999/8992     -1.1E-4     2           |       1/9          0         1           
   0.09... (1/11)     |       1/11         0         10          5           |     999/10990    -9.1E-5     2           |       1/11         0         1           
   0.62... (307/499)  |       8/13        2.5E-4     5           5           |     913/1484     -2.2E-6     8           |       8/13        2.5E-4     5           
   0.14... (33/229)   |      15/104       8.7E-4     20          9           |     974/6759     -4.5E-6     6           |      16/111       2.7E-4     3           
   0.05... (33/683)   |       7/145      -8.4E-4     24          10          |     980/20283     1.5E-6     7           |      10/207      -1.5E-4     4           
   0.18... (100/541)  |      17/92       -3.3E-4     11          10          |     939/5080     -2.0E-6     8           |      17/92       -3.3E-4     4           
   0.06... (33/541)   |       5/82       -3.7E-4     19          8           |     995/16312    -1.9E-6     6           |       5/82       -3.7E-4     4           
   0.1                |       1/10         0         9           5           |     999/9991     -1.0E-4     2           |       1/10         0         1           
   0.2                |       1/5          0         4           3           |     999/4996     -2.0E-4     2           |       1/5          0         1           
   0.3                |       3/10         0         5           5           |     998/3327     -1.0E-4     4           |       3/10         0         3           
   0.4                |       2/5          0         3           3           |     999/2497      2.0E-4     3           |       2/5          0         2           
   0.5                |       1/2          0         1           1           |     999/1999     -5.0E-4     2           |       1/2          0         1           
   0.6                |       3/5          0         3           3           |    1000/1667     -2.0E-4     4           |       3/5          0         3           
   0.7                |       7/10         0         5           5           |     996/1423     -1.0E-4     4           |       7/10         0         3           
   0.8                |       4/5          0         4           3           |     997/1246      2.0E-4     3           |       4/5          0         2           
   0.9                |       9/10         0         9           5           |     998/1109     -1.0E-4     4           |       9/10         0         3           
   0.01               |       1/100        0         99          8           |     999/99901    -1.0E-5     2           |       1/100        0         1           
   0.001              |       1/1000       0         999         11          |     999/999001   -1.0E-6     2           |       1/1000       0         1           
   0.0001             |       1/9991      9.0E-4     9990        15          |     999/9990001  -1.0E-7     2           |       1/10000      0         1           
   1E-05              |       1/99901     9.9E-4     99900       18          |    1000/99999999  1.0E-8     3           |       1/99999     1.0E-5     1           
   0.33333333333      |       1/3         1.0E-11    2           2           |    1000/3001     -3.3E-4     2           |       1/3         1.0E-11    1           
   0.3                |       3/10         0         5           5           |     998/3327     -1.0E-4     4           |       3/10         0         3           
   0.33               |      30/91       -1.0E-3     32          8           |     991/3003      1.0E-5     3           |      33/100        0         2           
   0.333              |     167/502      -9.9E-4     169         11          |    1000/3003      1.0E-6     3           |     333/1000       0         2           
   0.7777             |       7/9         1.0E-4     5           4           |     997/1282     -1.1E-5     4           |       7/9         1.0E-4     3           
   0.101              |      10/99        1.0E-4     18          10          |     919/9099      1.1E-6     5           |      10/99        1.0E-4     3           
   0.10001            |       1/10       -1.0E-4     9           5           |       1/10       -1.0E-4     4           |       1/10       -1.0E-4     2           
   0.100000001        |       1/10       -1.0E-8     9           5           |    1000/9999      1.0E-4     3           |       1/10       -1.0E-8     2           
   0.001001           |       1/999       1.0E-6     998         11          |       1/999       1.0E-6     3           |       1/999       1.0E-6     1           
   0.0010000001       |       1/1000     -1.0E-7     999         11          |    1000/999999    9.0E-7     3           |       1/1000     -1.0E-7     2           
   0.11               |      10/91       -1.0E-3     18          9           |    1000/9091     -1.0E-5     4           |      10/91       -1.0E-3     2           
   0.1111             |       1/9         1.0E-4     8           4           |    1000/9001     -1.1E-5     2           |       1/9         1.0E-4     1           
   0.111111111111     |       1/9         1.0E-12    8           4           |    1000/9001     -1.1E-4     2           |       1/9         1.0E-12    1           
   1                  |       1/1          0         0           0           |    1001/1000      1.0E-3     1           |       1/1          0         0           
  -1                  |      -1/1          0         0           0           |   -1001/1000      1.0E-3     1           |      -1/1          0         0           
  -0.5                |      -1/2          0         1           1           |    -999/1999     -5.0E-4     2           |      -1/2          0         1           
   3.14               |      22/7         9.1E-4     6           4           |     964/307       2.1E-5     3           |      22/7         9.1E-4     1           
   3.1416             |      22/7         4.0E-4     6           4           |     732/233       9.8E-6     3           |      22/7         4.0E-4     1           
   3.14... (pi)       |      22/7         4.0E-4     6           4           |     688/219      -1.3E-5     4           |      22/7         4.0E-4     1           
   0.14               |       7/50         0         13          7           |     995/7107      2.0E-5     3           |       7/50         0         2           
   0.1416             |      15/106      -6.4E-4     21          8           |     869/6137      9.2E-7     5           |      16/113      -5.0E-5     2           
   2.72... (e)        |      68/25        6.3E-4     7           7           |     878/323      -5.7E-6     8           |      87/32        1.7E-4     5           
   0.141592653589793  |      15/106      -5.9E-4     21          8           |     991/6999     -7.0E-6     4           |      15/106      -5.9E-4     2           
  -1.33333333333333   |      -4/3         2.5E-15    2           2           |   -1001/751      -3.3E-4     2           |      -4/3         2.5E-15    1           
  -1.3                |     -13/10         0         5           5           |    -992/763       1.0E-4     3           |     -13/10         0         2           
  -1.33               |     -97/73       -9.3E-4     26          8           |    -935/703       1.1E-5     3           |    -133/100        0         2           
  -1.333              |      -4/3         2.5E-4     2           2           |   -1001/751      -8.3E-5     2           |      -4/3         2.5E-4     1           
  -1.33333337         |      -4/3        -2.7E-8     2           2           |    -999/749       3.3E-4     3           |      -4/3        -2.7E-8     2           
  -1.7                |     -17/10         0         5           5           |    -991/583      -1.0E-4     4           |     -17/10         0         3           
  -1.37               |     -37/27        2.7E-4     7           7           |    -996/727       1.0E-5     7           |     -37/27        2.7E-4     5           
  -1.33337            |      -4/3        -2.7E-5     2           2           |    -999/749       3.1E-4     3           |      -4/3        -2.7E-5     2           
   0.047619           |       1/21        1.0E-6     20          6           |    1000/21001    -4.7E-5     2           |       1/21        1.0E-6     1           
  12.125              |      97/8          0         7           4           |     982/81       -1.3E-4     2           |      97/8          0         1           
   5.5                |      11/2          0         1           1           |     995/181      -5.0E-4     2           |      11/2          0         1           
   0.1233333333333    |       9/73       -3.7E-4     16          8           |     971/7873     -3.4E-6     4           |       9/73       -3.7E-4     2           
   0.7454545454545    |      38/51       -4.8E-4     15          8           |     981/1316     -1.9E-5     6           |      38/51       -4.8E-4     4           
   0.01024801004      |       2/195       8.2E-4     98          9           |     488/47619     2.0E-8     13          |       2/195       8.2E-4     3           
   0.99011            |      91/92       -9.9E-4     91          8           |     801/809       1.3E-6     5           |     100/101      -1.1E-5     2           
   0.9901134545       |      91/92       -9.9E-4     91          8           |     601/607       1.9E-6     5           |     100/101      -1.5E-5     2           
   0.19999999         |       1/5         5.0E-8     4           3           |    1000/5001     -2.0E-4     2           |       1/5         5.0E-8     1           
   0.20000001         |       1/5        -5.0E-8     4           3           |    1000/4999      2.0E-4     3           |       1/5        -5.0E-8     2           
   5.0183168565E-05   |       1/19908     9.5E-4     19907       16          |    1000/19927001 -5.0E-8     2           |       1/19927     5.2E-12    1           
   3.909E-07          |       1/2555644   1.0E-3     2555643     23          |       1/1         2.6E6 (!)  1           |       1/2558199   1.1E-8     1           
88900003.001          |88900003/1        -1.1E-11    0           0           |88900004/1         1.1E-8     1           |88900003/1        -1.1E-11    0           
   0.26... (5/19)     |       5/19         0         7           6           |     996/3785     -5.3E-5     4           |       5/19         0         3           
   0.61... (37/61)    |      17/28        9.7E-4     8           7           |     982/1619     -1.7E-5     8           |      17/28        9.7E-4     5           
                      |                                                      |                                          | 
Accuracy: 1.0E-4      | Stern-Brocot                             OPTIMIZED   | Eppstein                                 | Richards                                 
Input                 | Result           Error       Iterations  Iterations  | Result           Error       Iterations  | Result           Error       Iterations  
======================| =====================================================| =========================================| =========================================
   0.62... (307/499)  |     227/369      -8.8E-5     33          11          |    9816/15955    -2.0E-7     8           |     299/486      -6.7E-6     6           
   0.05... (33/683)   |      23/476       6.4E-5     27          12          |    9989/206742    1.5E-7     7           |      23/476       6.4E-5     5           
   0.06... (33/541)   |      28/459       6.6E-5     24          12          |    9971/163464   -1.9E-7     6           |      33/541        0         5           
   1E-05              |       1/99991     9.0E-5     99990       18          |   10000/999999999 1.0E-9     3           |       1/99999     1.0E-5     1           
   0.333              |     303/910      -9.9E-5     305         12          |    9991/30003     1.0E-7     3           |     333/1000       0         2           
   0.7777             |     556/715      -1.0E-4     84          12          |    7777/10000      0         8           |    1109/1426     -1.8E-7     4           
   3.14... (pi)       |     289/92       -9.2E-5     19          8           |    9918/3157     -8.1E-7     4           |     333/106      -2.6E-5     2           
   2.72... (e)        |     193/71        1.0E-5     10          9           |    9620/3539      6.3E-8     11          |     193/71        1.0E-5     7           
   0.7454545454545    |      41/55        6.1E-14    16          8           |    9960/13361    -1.8E-6     6           |      41/55        6.1E-14    5           
   0.01024801004      |       7/683       8.7E-5     101         12          |    9253/902907   -1.3E-10    16          |       7/683       8.7E-5     5           
   0.99011            |     100/101      -1.1E-5     100         8           |     901/910      -1.1E-7     6           |     100/101      -1.1E-5     2           
   0.9901134545       |     100/101      -1.5E-5     100         8           |    8813/8901      1.6E-8     7           |     100/101      -1.5E-5     2           
   0.26... (5/19)     |       5/19         0         7           6           |    9996/37985    -5.3E-6     4           |       5/19         0         3           
   0.61... (37/61)    |      37/61         0         10          8           |    9973/16442    -1.6E-6     8           |      37/61         0         7           

效果比较

我进行了详细的速度测试并绘制了结果。不看质量而只考虑速度:

  • Stern-Brocot 优化最多可减慢2倍,但原来的Stern-Brocot在遇到上述不幸值时会慢几百或几千倍。虽然每次通话仍然只有几微秒。
  • Richards一直很快。
  • Eppstein比其他人快3倍左右。

Stern-Brocot和Richards比较:

  • 两者都返回好分数。
  • 理查兹经常导致较小的错误。它也快一点。
  • Stern-Brocot沿着S-B树走下去。它找到满足所需精度的最小分母的分数,然后停止。

如果你不需要最低分母分数,理查兹是个不错的选择。

答案 2 :(得分:14)

我知道你说你在网上搜索过,但如果你错过了下面的文章,那可能会有所帮助。它包含Pascal中的代码示例。

Algorithm To Convert A Decimal To A Fraction *

或者,作为其标准库的一部分,Ruby具有处理有理数的代码。它可以从浮点数转换为有理数,反之亦然。我相信你也可以查看代码。找到文档here。我知道你没有使用Ruby,但它可能有助于查看算法。

此外,如果使用运行在.net框架之上的IronRuby,则可以从C#调用Ruby代码(甚至在C#代码文件中编写Ruby代码)。

* 更新为新链接,因为原始网址已损坏(http://homepage.smc.edu/kennedy_john/DEC2FRAC.pdf

答案 3 :(得分:9)

我找到了Matt引用的相同文章,我花了一秒钟用Python实现了它。也许在代码中看到相同的想法会使它更清晰。当然,你在C#中请求答案,我用Python给你,但这是一个相当简单的程序,我相信它很容易翻译。参数为num(您要转换为理性的十进制数)和epsilonnum与计算的理性之间允许的最大差值)。一些快速测试运行发现,当epsilon大约为1e-4时,通常只需要两到三次迭代就可以收敛。

def dec2frac(num, epsilon, max_iter=20):
    d = [0, 1] + ([0] * max_iter)
    z = num
    n = 1
    t = 1

    while num and t < max_iter and abs(n/d[t] - num) > epsilon:
        t += 1
        z = 1/(z - int(z))
        d[t] = d[t-1] * int(z) + d[t-2]
        # int(x + 0.5) is equivalent to rounding x.
        n = int(num * d[t] + 0.5)

    return n, d[t]

编辑:我刚刚注意到您希望他们使用重复小数的说明。我不知道任何语法有支持重复小数的语法,所以我不确定如何处理它们,但通过这种方法运行0.6666666和0.166666返回正确的结果(2/3和1/6,分别地)。

另一个编辑(我不认为这会如此有趣!):如果你想进一步了解这个算法背后的理论,Wikipedia has an excellent page on the Euclidian algorithm

答案 4 :(得分:5)

你不能代表.net中的重复小数,所以我会忽略你问题的那一部分。

您只能表示有限且数量相对较少的数字。

有一个非常简单的算法:

  • 取十进制x
  • 计算小数点后的位数;请拨打此n
  • 创建一个分数(10^n * x) / 10^n
  • 从分子和分母中删除常见因子。

所以,如果你有0.44,你会计算2位是小数点 - n = 2,然后写

  • (0.44 * 10^2) / 10^2
  • = 44 / 100
  • 因子分解(去除4的公因子)给出11 / 25

答案 5 :(得分:5)

这是Will Brown的python示例的C#版本。我也改变它来处理单独的整数(例如“2 1/8”而不是“17/8”)。

    public static string DoubleToFraction(double num, double epsilon = 0.0001, int maxIterations = 20)
    {
        double[] d = new double[maxIterations + 2];
        d[1] = 1;
        double z = num;
        double n = 1;
        int t = 1;

        int wholeNumberPart = (int)num;
        double decimalNumberPart = num - Convert.ToDouble(wholeNumberPart);

        while (t < maxIterations && Math.Abs(n / d[t] - num) > epsilon)
        {
            t++;
            z = 1 / (z - (int)z);
            d[t] = d[t - 1] * (int)z + d[t - 2];
            n = (int)(decimalNumberPart * d[t] + 0.5);
        }

        return string.Format((wholeNumberPart > 0 ? wholeNumberPart.ToString() + " " : "") + "{0}/{1}",
                             n.ToString(),
                             d[t].ToString()
                            );
    }

答案 6 :(得分:4)

我写了一个快速的课程,运行得相当快,并给出了我期望的结果。您也可以选择您的精度。从我看到的任何代码都可以更加简单,并且运行得很快。

//Written By Brian Dobony
public static class Fraction
{
    public static string ConvertDecimal(Double NumberToConvert, int DenominatorPercision = 32)
    {
        int WholeNumber = (int)NumberToConvert;
        double DecimalValue = NumberToConvert - WholeNumber;

        double difference = 1;
        int numerator = 1;
        int denominator = 1;

        // find closest value that matches percision
        // Automatically finds Fraction in simplified form
        for (int y = 2; y < DenominatorPercision + 1; y++)
        {
                for (int x = 1; x < y; x++)
                {
                    double tempdif = Math.Abs(DecimalValue - (double)x / (double)y);
                    if (tempdif < difference)
                    {
                        numerator = x;
                        denominator = y;
                        difference = tempdif;
                        // if exact match is found return it
                        if (difference == 0)
                        {
                            return FractionBuilder(WholeNumber, numerator, denominator);
                        }
                    }
                }
        }
        return FractionBuilder(WholeNumber, numerator, denominator);
    }

    private static string FractionBuilder(int WholeNumber, int Numerator, int Denominator)
    {
        if (WholeNumber == 0)
        {
            return Numerator + @"/" + Denominator;
        }
        else
        {
            return WholeNumber + " " + Numerator + @"/" + Denominator;
        }
    }
}

答案 7 :(得分:3)

这是Ian Richards / John Kennedy编写的算法的C#版本。其他答案在这里使用相同的算法:

它不处理无穷大和NaN。

此算法 fast

例如值和与其他算法的比较,请参阅my other answer

public Fraction RealToFraction(double value, double accuracy)
{
    if (accuracy <= 0.0 || accuracy >= 1.0)
    {
        throw new ArgumentOutOfRangeException("accuracy", "Must be > 0 and < 1.");
    }

    int sign = Math.Sign(value);

    if (sign == -1)
    {
        value = Math.Abs(value);
    }

    // Accuracy is the maximum relative error; convert to absolute maxError
    double maxError = sign == 0 ? accuracy : value * accuracy;

    int n = (int) Math.Floor(value);
    value -= n;

    if (value < maxError)
    {
        return new Fraction(sign * n, 1);
    }

    if (1 - maxError < value)
    {
        return new Fraction(sign * (n + 1), 1);
    }

    double z = value;
    int previousDenominator = 0;
    int denominator = 1;
    int numerator;

    do
    {
        z = 1.0 / (z - (int) z);
        int temp = denominator;
        denominator = denominator * (int) z + previousDenominator;
        previousDenominator = temp;
        numerator = Convert.ToInt32(value * denominator);
    }
    while (Math.Abs(value - (double) numerator / denominator) > maxError && z != (int) z);

    return new Fraction((n * denominator + numerator) * sign, denominator);
}

答案 8 :(得分:2)

我想出了一个很晚的答案。代码取自an article from Richards published in 1981并写在c

inline unsigned int richards_solution(double const& x0, unsigned long long& num, unsigned long long& den, double& sign, double const& err = 1e-10){
    sign = my::sign(x0);
    double g(std::abs(x0));
    unsigned long long a(0);
    unsigned long long b(1);
    unsigned long long c(1);
    unsigned long long d(0);
    unsigned long long s;
    unsigned int iter(0);
    do {
        s = std::floor(g);
        num = a + s*c;
        den = b + s*d;
        a = c;
        b = d;
        c = num;
        d = den;
        g = 1.0/(g-s);
        if(err>std::abs(sign*num/den-x0)){ return iter; }
    } while(iter++<1e6);
    std::cerr<<__PRETTY_FUNCTION__<<" : failed to find a fraction for "<<x0<<std::endl;
    return 0;
}

我在这里重写了btilly_solution的实现:

inline unsigned int btilly_solution(double x, unsigned long long& num, unsigned long long& den, double& sign, double const& err = 1e-10){
    sign = my::sign(x);
    num  = std::floor(std::abs(x));
    x = std::abs(x)-num;
    unsigned long long lower_n(0);
    unsigned long long lower_d(1);
    unsigned long long upper_n(1);
    unsigned long long upper_d(1);
    unsigned long long middle_n;
    unsigned long long middle_d;
    unsigned int iter(0);
    do {
        middle_n = lower_n + upper_n;
        middle_d = lower_d + upper_d;
        if(middle_d*(x+err)<middle_n){
            upper_n = middle_n;
            upper_d = middle_d;
        } else if(middle_d*(x-err)>middle_n) {
            lower_n = middle_n;
            lower_d = middle_d;
        } else {
            num = num*middle_d+middle_n;
            den = middle_d;
            return iter;
        }
    } while(iter++<1e6);
    den = 1;
    std::cerr<<__PRETTY_FUNCTION__<<" : failed to find a fraction for "<<x+num<<std::endl;
    return 0;
}

在这里,我提出了一些错误为1e-10

的测试
------------------------------------------------------ |
btilly  0.166667 0.166667=1/6 in 5 iterations          | 1/6
richard 0.166667 0.166667=1/6 in 1 iterations          |
------------------------------------------------------ |
btilly  0.333333 0.333333=1/3 in 2 iterations          | 1/3
richard 0.333333 0.333333=1/3 in 1 iterations          |
------------------------------------------------------ |
btilly  0.142857 0.142857=1/7 in 6 iterations          | 1/7
richard 0.142857 0.142857=1/7 in 1 iterations          |
------------------------------------------------------ |
btilly  0.714286 0.714286=5/7 in 4 iterations          | 5/7
richard 0.714286 0.714286=5/7 in 4 iterations          |
------------------------------------------------------ |
btilly  1e-07 1.001e-07=1/9990010 in 9990009 iteration | 0.0000001
richard 1e-07 1e-07=1/10000000 in 1 iterations         |
------------------------------------------------------ |
btilly  3.66667 3.66667=11/3 in 2 iterations           | 11/3
richard 3.66667 3.66667=11/3 in 3 iterations           |
------------------------------------------------------ |
btilly  1.41421 1.41421=114243/80782 in 25 iterations  | sqrt(2)
richard 1.41421 1.41421=114243/80782 in 13 iterations  |
------------------------------------------------------ |
btilly  3.14159 3.14159=312689/99532 in 317 iterations | pi
richard 3.14159 3.14159=312689/99532 in 7 iterations   |
------------------------------------------------------ |
btilly  2.71828 2.71828=419314/154257 in 36 iterations | e
richard 2.71828 2.71828=517656/190435 in 14 iterations |
------------------------------------------------------ |
btilly  0.390885 0.390885=38236/97819 in 60 iterations | random
richard 0.390885 0.390885=38236/97819 in 13 iterations |

正如你所看到的,这两种方法给出的结果大致相同,但是 理查兹的一种方式更有效,更容易实施。

修改

要编译我的代码,您需要my::sign的定义,这只是一个 返回变量符号的函数。这是我的实现

 namespace my{
    template<typename Type> inline constexpr
        int sign_unsigned(Type x){ return Type(0)<x; }

    template<typename Type> inline constexpr
        int sign_signed(Type x){ return (Type(0)<x)-(x<Type(0)); }

    template<typename Type> inline constexpr
        int sign(Type x) { return std::is_signed<Type>()?sign_signed(x):sign_unsigned(x); }
 }

很抱歉

我猜this answer指的是同一算法。我之前没有看到......

答案 9 :(得分:2)

David Eppstein,UC Irvine的这个算法,基于连续分数的理论,最初是在C中,由我翻译成C#。它生成的分数满足误差范围,但大多数看起来不如我的其他答案中的解决方案。例如。 0.5变为999/1999,而1/2在向用户展示时会首选(如果您需要,请参阅我的other answers)。

有一个重载将错误边距指定为double(相对于值,而不是绝对错误)。对于Fraction类型,请参阅我的其他答案。

顺便说一句,如果您的分数变大,请将相关的int更改为long。与其他算法相比,这个算法很容易溢出。

例如值和与其他算法的比较,请参阅my other answer

public Fraction RealToFraction(double value, int maxDenominator)
{
    // http://www.ics.uci.edu/~eppstein/numth/frap.c
    // Find rational approximation to given real number
    // David Eppstein / UC Irvine / 8 Aug 1993
    // With corrections from Arno Formella, May 2008

    if (value == 0.0)
    {
        return new Fraction(0, 1);
    }

    int sign = Math.Sign(value);

    if (sign == -1)
    {
        value = Math.Abs(value);
    }

    int[,] m = { { 1, 0 }, { 0, 1 } };
    int ai = (int) value;

    // Find terms until denominator gets too big
    while (m[1, 0] * ai + m[1, 1] <= maxDenominator)
    {
        int t = m[0, 0] * ai + m[0, 1];
        m[0, 1] = m[0, 0];
        m[0, 0] = t;
        t = m[1, 0] * ai + m[1, 1];
        m[1, 1] = m[1, 0];
        m[1, 0] = t;

        value = 1.0 / (value - ai);

        // 0x7FFFFFFF = Assumes 32 bit floating point just like in the C implementation.
        // This check includes Double.IsInfinity(). Even though C# double is 64 bits,
        // the algorithm sometimes fails when trying to increase this value too much. So
        // I kept it. Anyway, it works.
        if (value > 0x7FFFFFFF)
        {                    
            break;
        }

        ai = (int) value;
    }

    // Two approximations are calculated: one on each side of the input
    // The result of the first one is the current value. Below the other one
    // is calculated and it is returned.

    ai = (maxDenominator - m[1, 1]) / m[1, 0];
    m[0, 0] = m[0, 0] * ai + m[0, 1];
    m[1, 0] = m[1, 0] * ai + m[1, 1];

    return new Fraction(sign * m[0, 0], m[1, 0]);
}

public Fraction RealToFraction(double value, double accuracy)
{
    if (accuracy <= 0.0 || accuracy >= 1.0)
    {
        throw new ArgumentOutOfRangeException("accuracy", "Must be > 0 and < 1.");
    }

    int maxDenominator = (int) Math.Ceiling(Math.Abs(1.0 / (value * accuracy)));

    if (maxDenominator < 1)
    {
        maxDenominator = 1;
    }

    return RealToFraction(value, maxDenominator);
}

答案 10 :(得分:1)

我最近不得不执行使用存储在SQL Server数据库中的十进制数据类型的任务。在表示层,此值在TextBox中被编辑为小数值。这里的复杂性是使用十进制数据类型,与int或long相比,它包含一些相当大的值。因此,为了减少数据溢出的机会,我在整个转换过程中坚持使用十进制数据类型。

在开始之前,我想评论一下Kirk之前的回答。只要没有做出任何假设,他就绝对正确。但是,如果开发人员只在十进制数据类型的范围内查找重复模式.3333333 ...可以表示为1/3。可以在basic-mathematics.com找到该算法的示例。同样,这意味着您必须根据可用信息进行假设,并且使用此方法仅捕获重复小数的一小部分。但是对于小数字应该没问题。

继续前进,让我简要介绍一下我的解决方案。如果您想阅读包含其他代码的完整示例,我创建了一个更详细的blog post

将十进制数据类型转换为字符串分数

public static void DecimalToFraction(decimal value, ref decimal sign, ref decimal numerator, ref decimal denominator)
{
    const decimal maxValue = decimal.MaxValue / 10.0M;

    // e.g. .25/1 = (.25 * 100)/(1 * 100) = 25/100 = 1/4
    var tmpSign = value < decimal.Zero ? -1 : 1;
    var tmpNumerator = Math.Abs(value);
    var tmpDenominator = decimal.One;

    // While numerator has a decimal value
    while ((tmpNumerator - Math.Truncate(tmpNumerator)) > 0 && 
        tmpNumerator < maxValue && tmpDenominator < maxValue)
    {
        tmpNumerator = tmpNumerator * 10;
        tmpDenominator = tmpDenominator * 10;
    }

    tmpNumerator = Math.Truncate(tmpNumerator); // Just in case maxValue boundary was reached.
    ReduceFraction(ref tmpNumerator, ref tmpDenominator);
    sign = tmpSign;
    numerator = tmpNumerator;
    denominator = tmpDenominator;
}

public static string DecimalToFraction(decimal value)
{
    var sign = decimal.One;
    var numerator = decimal.One;
    var denominator = decimal.One;
    DecimalToFraction(value, ref sign, ref numerator, ref denominator);
    return string.Format("{0}/{1}", (sign * numerator).ToString().TruncateDecimal(), 
        denominator.ToString().TruncateDecimal());
}

这很简单,其中DecimalToFraction(十进制值)只不过是第一个方法的简化入口点,它提供了对组成分数的所有组件的访问。如果您的小数点为.325,则将其除以10除以小数位数的幂。最后减少分数。并且,在此示例中.325 = 325/10 ^ 3 = 325/1000 = 13/40。

接下来,走向另一个方向。

将字符串分数转换为十进制数据类型

static readonly Regex FractionalExpression = new Regex(@"^(?<sign>[-])?(?<numerator>\d+)(/(?<denominator>\d+))?$");
public static decimal? FractionToDecimal(string fraction)
{
    var match = FractionalExpression.Match(fraction);
    if (match.Success)
    {
        // var sign = Int32.Parse(match.Groups["sign"].Value + "1");
        var numerator = Int32.Parse(match.Groups["sign"].Value + match.Groups["numerator"].Value);
        int denominator;
        if (Int32.TryParse(match.Groups["denominator"].Value, out denominator))
            return denominator == 0 ? (decimal?)null : (decimal)numerator / denominator;
        if (numerator == 0 || numerator == 1)
            return numerator;
    }
    return null;
}

转换回小数也很简单。在这里,我们解析小数分量,将它们存储在我们可以使用的东西中(这里是十进制值)并执行我们的分割。

答案 11 :(得分:1)

此问题最常见的解决方案是Richards’ algorithmthe Stern-Brocot algorithm,由btilly和Jay Zed与speed optimalization实施。 Richards的算法是最快的,但不能保证返回最佳分数。

我有一个解决这个问题的方法,它总能提供最好的分数,并且比上面的所有算法都快。这是C#中的算法(下面的解释和速度测试)。

这是一个没有注释的简短算法。最后在源代码中提供了完整版本。

public static Fraction DoubleToFractionSjaak(double value, double accuracy)
{
    int sign = value < 0 ? -1 : 1;
    value = value < 0 ? -value : value;
    int integerpart = (int)value;
    value -=  integerpart;
    double minimalvalue = value - accuracy;
    if (minimalvalue < 0.0) return new Fraction(sign * integerpart, 1);
    double maximumvalue = value + accuracy;
    if (maximumvalue > 1.0) return new Fraction(sign * (integerpart + 1), 1);
    int a = 0;
    int b = 1;
    int c = 1;
    int d = (int)(1 / maximumvalue);
    while (true)
    {
        int n = (int)((b * minimalvalue - a) / (c - d * minimalvalue));
        if (n == 0) break;
        a += n * c;
        b += n * d;
        n = (int)((c - d * maximumvalue) / (b * maximumvalue - a));
        if (n == 0) break;
        c += n * a;
        d += n * b;
    }
    int denominator = b + d;
    return new Fraction(sign * (integerpart * denominator + (a + c)), denominator);
}

其中Fraction是一个存储分数的简单类,如下所示:

public class Fraction
{
    public int Numerator { get; private set; }
    public int Denominator { get; private set; }

    public Fraction(int numerator, int denominator)
    {
        Numerator = numerator;
        Denominator = denominator;
    }     
}

如何运作

与上述其他解决方案一样,我的解决方案基于持续分数。其他解决方案,例如来自Eppstein的解决方案或基于重复小数的解决方案被证明更慢和/或给出次优结果。

续分数
基于连续分数的解决方案主要基于两种算法,两者都在Ian Richards在1981年发表的here文章中描述。他称之为“慢连续分数算法”和“快速连续分数算法”。第一种被称为Stern-Brocot算法,而后者被称为Richards算法。

我的算法(简短说明)
要完全理解我的算法,你需要阅读Ian Richards的文章,或者至少了解Farey对是什么。此外,请阅读本文末尾带有注释的算法。

该算法使用Farey对,包含左和右分数。通过反复取得媒体,它正在接近目标值。这就像慢速算法一样,但有两个主要区别:

  1. 只要媒体停留在目标值的一侧,就会立即执行多次迭代。
  2. 左右分数不能比给定的精度更接近目标值。
  3. 或者,检查目标值的右侧和左侧。如果算法不能产生更接近目标值的结果,则该过程结束。由此产生的媒体是最佳解决方案。

    速度测试

    我使用以下算法在笔记本电脑上进行了一些速度测试:

    1. 通过Kay Zed and btilly
    2. 改进慢速算法
    3. John Kennedy对Fast算法的实现,由Kay Zed
    4. 转换为C#
    5. 我对Fast算法的实现(接近Ian Richards的原文)
    6. Jeremy Herrman’s实施快速算法
    7. 我的算法
    8. 我省略了btilly的原始慢速算法,因为它的最坏情况表现不佳。

      测试集
      我选择一组目标值(非常随意)并用5种不同的精度计算分数100000次。因为某些(未来)算法可能无法处理不正确的分数,所以只测试了从0.0到1.0的目标值。精度取自2至6位小数(0.005至0.0000005)的范围。使用了以下集合:

      0.999999, 0.000001, 0.25
      0.33, 0.333, 0.3333, 0.33333, 0.333333, 0.333333333333, 
      0.666666666666, 0.777777777777, 0.090909090909, 0.263157894737,
      0.606557377049, 0.745454545454, 0.000050183168565,
      pi - 3, e - 2.0, sqrt(2) - 1
      

      结果

      我做了13次测试。结果是整个数据集所需的毫秒数。

          Run 1   Run 2   Run 3   Run 4   Run 5   Run 6   Run 7   Run 8   Run 9   Run 10  Run 11  Run 12  Run 13
      1.  9091    9222    9070    9111    9091    9108    9293    9118    9115    9113    9102    9143    9121
      2.  7071    7125    7077    6987    7126    6985    7037    6964    7023    6980    7053    7050    6999
      3.  6903    7059    7062    6891    6942    6880    6882    6918    6853    6918    6893    6993    6966
      4.  7546    7554    7564    7504    7483    7529    7510    7512    7517    7719    7513    7520    7514
      5.  6839    6951    6882    6836    6854    6880    6846    7017    6874    6867    6828    6848    6864
      

      结论(跳过分析)
      即使没有统计分析,也很容易看出我的算法比其他测试算法更快。然而,与“快速算法”的最快变体的差异小于1%。改进的慢速算法比最快的算法“。

      慢30%-35%

      另一方面,即使是最慢的算法也会在不到一微秒的时间内平均进行计算。所以在正常情况下速度并不是真正的问题。在我看来,最好的算法主要是品味问题,因此请根据其他标准选择任何经过测试的算法。

      • 算法是否提供最佳结果?
      • 算法是否以我最喜欢的语言提供?
      • 算法的代码大小是多少?
      • 算法是否可读,易懂?

      源代码

      下面的源代码包含所有使用的算法。它包括:

      • 我的原始算法(带注释)
      • 我的算法的更快版本(但不太可读)
      • 原始慢速算法
      • 所有经过测试的算法
      public class DoubleToFraction
      {
          // ===================================================
          // Sjaak algorithm - original version
          //
      
          public static Fraction SjaakOriginal(double value, double accuracy)
          {
              // Split value in a sign, an integer part, a fractional part
              int sign = value < 0 ? -1 : 1;
              value = value < 0 ? -value : value;
              int integerpart = (int)value;
              value -= integerpart;
      
              // check if the fractional part is near 0
              double minimalvalue = value - accuracy;
              if (minimalvalue < 0.0) return new Fraction(sign * integerpart, 1);
      
              // check if the fractional part is near 1
              double maximumvalue = value + accuracy;
              if (maximumvalue > 1.0) return new Fraction(sign * (integerpart + 1), 1);
      
              // The left fraction (a/b) is initially (0/1), the right fraction (c/d) is initially (1/1)
              // Together they form a Farey pair.
              // We will keep the left fraction below the minimumvalue and the right fraction above the maximumvalue
              int a = 0;
              int b = 1;
              int c = 1;
              int d = (int)(1 / maximumvalue);
      
              // The first interation is performed above. Calculate maximum n where (n*a+c)/(n*b+d) >= maximumvalue 
              // This is the same as n <= 1/maximumvalue - 1, d will become n+1 = floor(1/maximumvalue)
      
              // repeat forever (at least until we cannot close in anymore)
              while (true)
              {
                  // Close in from the left n times. 
                  // Calculate maximum n where (a+n*c)/(b+n*d) <= minimalvalue
                  // This is the same as n <= (b * minimalvalue - a) / (c-d*minimalvalue)
                  int n = (int)((b * minimalvalue - a) / (c - d * minimalvalue));
      
                  // If we cannot close in from the left (and also not from the right anymore) the loop ends
                  if (n == 0) break;
      
                  // Update left fraction
                  a += n * c;
                  b += n * d;
      
                  // Close in from the right n times.
                  // Calculate maximum n where (n*a+c)/(n*b+d) >= maximumvalue
                  // This is the same as n <= (c - d * maximumvalue) / (b * maximumvalue - a)
                  n = (int)((c - d * maximumvalue) / (b * maximumvalue - a));
      
                  // If we cannot close in from the right (and also not from the left anymore) the loop ends
                  if (n == 0) break;
      
                  // Update right fraction
                  c += n * a;
                  d += n * b;
              }
      
              // We cannot close in anymore
              // The best fraction will be the mediant of the left and right fraction = (a+c)/(b+d)
              int denominator = b + d;
              return new Fraction(sign * (integerpart * denominator + (a + c)), denominator);
      
          }
      
          // ===================================================
          // Sjaak algorithm - faster version
          //
      
          public static Fraction SjaakFaster(double value, double accuracy)
          {
              int sign = value < 0 ? -1 : 1;
              value = value < 0 ? -value : value;
              int integerpart = (int)value;
              value -= integerpart;
              double minimalvalue = value - accuracy;
              if (minimalvalue < 0.0) return new Fraction(sign * integerpart, 1);
              double maximumvalue = value + accuracy;
              if (maximumvalue > 1.0) return new Fraction(sign * (integerpart + 1), 1);
              //int a = 0;
              int b = 1;
              //int c = 1;
              int d = (int)(1 / maximumvalue);
              double left_n = minimalvalue; // b * minimalvalue - a
              double left_d = 1.0 - d * minimalvalue; // c - d * minimalvalue
              double right_n = 1.0 - d * maximumvalue; // c - d * maximumvalue
              double right_d = maximumvalue; // b * maximumvalue - a            
              while (true)
              {
                  if (left_n < left_d) break;
                  int n = (int)(left_n / left_d);
                  //a += n * c;
                  b += n * d;
                  left_n -= n * left_d;
                  right_d -= n * right_n;
                  if (right_n < right_d) break;
                  n = (int)(right_n / right_d);
                  //c += n * a;
                  d += n * b;
                  left_d -= n * left_n;
                  right_n -= n * right_d;
              }
      
      
              int denominator = b + d;
              int numerator = (int)(value * denominator + 0.5);
              return new Fraction(sign * (integerpart * denominator + numerator), denominator);
          }
      
          // ===================================================
          // Original Farley - Implemented by btilly
          //
      
          public static Fraction OriginalFarley(double value, double accuracy)
          {
              // Split value in a sign, an integer part, a fractional part
              int sign = value < 0 ? -1 : 1;
              value = value < 0 ? -value : value;
              int integerpart = (int)value;
              value -= integerpart;
      
              // check if the fractional part is near 0
              double minimalvalue = value - accuracy;
              if (minimalvalue < 0.0) return new Fraction(sign * integerpart, 1);
      
              // check if the fractional part is near 1
              double maximumvalue = value + accuracy;
              if (maximumvalue > 1.0) return new Fraction(sign * (integerpart + 1), 1);
      
              // The lower fraction is 0/1
              int lower_numerator = 0;
              int lower_denominator = 1;
      
              // The upper fraction is 1/1
              int upper_numerator = 1;
              int upper_denominator = 1;
      
              while (true)
              {
                  // The middle fraction is (lower_numerator + upper_numerator) / (lower_denominator + upper_denominator)
                  int middle_numerator = lower_numerator + upper_numerator;
                  int middle_denominator = lower_denominator + upper_denominator;
      
                  if (middle_denominator * maximumvalue < middle_numerator)
                  {
                      // real + error < middle : middle is our new upper
                      upper_numerator = middle_numerator;
                      upper_denominator = middle_denominator;
                  }
                  else if (middle_numerator < minimalvalue * middle_denominator)
                  {
                      // middle < real - error : middle is our new lower
                      lower_numerator = middle_numerator;
                      lower_denominator = middle_denominator;
                  }
                  else
                  {
                      return new Fraction(sign * (integerpart * middle_denominator + middle_numerator), middle_denominator);
                  }
              }
          }
      
          // ===================================================
          // Modified Farley - Implemented by btilly, Kay Zed
          //
      
          public static Fraction ModifiedFarley(double value, double accuracy)
          {
              // Split value in a sign, an integer part, a fractional part
              int sign = value < 0 ? -1 : 1;
              value = value < 0 ? -value : value;
              int integerpart = (int)value;
              value -= integerpart;
      
              // check if the fractional part is near 0
              double minimalvalue = value - accuracy;
              if (minimalvalue < 0.0) return new Fraction(sign * integerpart, 1);
      
              // check if the fractional part is near 1
              double maximumvalue = value + accuracy;
              if (maximumvalue > 1.0) return new Fraction(sign * (integerpart + 1), 1);
      
              // The lower fraction is 0/1
              int lower_numerator = 0;
              int lower_denominator = 1;
      
              // The upper fraction is 1/1
              int upper_numerator = 1;
              int upper_denominator = 1;
      
              while (true)
              {
                  // The middle fraction is (lower_numerator + upper_numerator) / (lower_denominator + upper_denominator)
                  int middle_numerator = lower_numerator + upper_numerator;
                  int middle_denominator = lower_denominator + upper_denominator;
      
                  if (middle_denominator * maximumvalue < middle_numerator)
                  {
                      // real + error < middle : middle is our new upper
                      ModifiedFarleySeek(ref upper_numerator, ref upper_denominator, lower_numerator, lower_denominator, (un, ud) => (lower_denominator + ud) * maximumvalue < (lower_numerator + un));
                  }
                  else if (middle_numerator < minimalvalue * middle_denominator)
                  {
                      // middle < real - error : middle is our new lower
                      ModifiedFarleySeek(ref lower_numerator, ref lower_denominator, upper_numerator, upper_denominator, (ln, ld) => (ln + upper_numerator) < minimalvalue * (ld + upper_denominator));
                  }
                  else
                  {
                      return new Fraction(sign * (integerpart * middle_denominator + middle_numerator), middle_denominator);
                  }
              }
          }
      
          private static void ModifiedFarleySeek(ref int a, ref int b, int ainc, int binc, Func<int, int, bool> f)
          {
              // Binary seek for the value where f() becomes false
              a += ainc;
              b += binc;
      
              if (f(a, b))
              {
                  int weight = 1;
      
                  do
                  {
                      weight *= 2;
                      a += ainc * weight;
                      b += binc * weight;
                  }
                  while (f(a, b));
      
                  do
                  {
                      weight /= 2;
      
                      int adec = ainc * weight;
                      int bdec = binc * weight;
      
                      if (!f(a - adec, b - bdec))
                      {
                          a -= adec;
                          b -= bdec;
                      }
                  }
                  while (weight > 1);
              }
          }
      
          // ===================================================
          // Richards implementation by Jemery Hermann
          //
      
          public static Fraction RichardsJemeryHermann(double value, double accuracy, int maxIterations = 20)
          {
      
              // Split value in a sign, an integer part, a fractional part
              int sign = value < 0 ? -1 : 1;
              value = value < 0 ? -value : value;
              int integerpart = (int)value;
              value -= integerpart;
      
              // check if the fractional part is near 0
              double minimalvalue = value - accuracy;
              if (minimalvalue < 0.0) return new Fraction(sign * integerpart, 1);
      
              // check if the fractional part is near 1
              double maximumvalue = value + accuracy;
              if (maximumvalue > 1.0) return new Fraction(sign * (integerpart + 1), 1);
      
              // Richards - Implemented by Jemery Hermann
              double[] d = new double[maxIterations + 2];
              d[1] = 1;
              double z = value;
              double n = 1;
              int t = 1;
      
              while (t < maxIterations && Math.Abs(n / d[t] - value) > accuracy)
              {
                  t++;
                  z = 1 / (z - (int)z);
                  d[t] = d[t - 1] * (int)z + d[t - 2];
                  n = (int)(value * d[t] + 0.5);
              }
      
              return new Fraction(sign * (integerpart * (int)d[t] + (int)n), (int)d[t]);
          }
      
          // ===================================================
          // Richards implementation by Kennedy
          //
      
          public static Fraction RichardsKennedy(double value, double accuracy)
          {
              // Split value in a sign, an integer part, a fractional part
              int sign = value < 0 ? -1 : 1;
              value = value < 0 ? -value : value;
              int integerpart = (int)value;
              value -= integerpart;
      
              // check if the fractional part is near 0
              double minimalvalue = value - accuracy;
              if (minimalvalue < 0.0) return new Fraction(sign * integerpart, 1);
      
              // check if the fractional part is near 1
              double maximumvalue = value + accuracy;
              if (maximumvalue > 1.0) return new Fraction(sign * (integerpart + 1), 1);
      
              // Richards
              double z = value;
              int previousDenominator = 0;
              int denominator = 1;
              int numerator;
              do
              {
                  z = 1.0 / (z - (int)z);
                  int temp = denominator;
                  denominator = denominator * (int)z + previousDenominator;
                  previousDenominator = temp;
                  numerator = (int)(value * denominator + 0.5);
              }
              while (Math.Abs(value - (double)numerator / denominator) > accuracy && z != (int)z);
      
              return new Fraction(sign * (integerpart * denominator + numerator), denominator);
          }
      
          // ===================================================
          // Richards implementation by Sjaak
          //
      
          public static Fraction RichardsOriginal(double value, double accuracy)
          {
              // Split value in a sign, an integer part, a fractional part
              int sign = value < 0 ? -1 : 1;
              value = value < 0 ? -value : value;
              int integerpart = (int)value;
              value -= integerpart;
      
              // check if the fractional part is near 0
              double minimalvalue = value - accuracy;
              if (minimalvalue < 0.0) return new Fraction(sign * integerpart, 1);
      
              // check if the fractional part is near 1
              double maximumvalue = value + accuracy;
              if (maximumvalue > 1.0) return new Fraction(sign * (integerpart + 1), 1);
      
              // Richards
              double z = value;
              int denominator0 = 0;
              int denominator1 = 1;
              int numerator0 = 1;
              int numerator1 = 0;
              int n = (int)z;
              while (true)
              {
                  z = 1.0 / (z - n);
                  n = (int)z;
      
                  int temp = denominator1;
                  denominator1 = denominator1 * n + denominator0;
                  denominator0 = temp;
      
                  temp = numerator1;
                  numerator1 = numerator1 * n + numerator0;
                  numerator0 = temp;
      
                  double d = (double)numerator1 / denominator1;
                  if (d > minimalvalue && d < maximumvalue) break;
              }
              return new Fraction(sign * (integerpart * denominator1 + numerator1), denominator1);
          }
      
      }
      

答案 12 :(得分:1)

重复的小数可以用两个有限的小数表示:重复前的左侧部分和重复的部分。例如。 1.6181818... = 1.6 + 0.1*(0.18...)。可以将其视为a + b * sum(c * 10**-(d*k) for k in range(1, infinity))(在此处使用Python表示法)。在我的示例中,a=1.6b=0.1c=18d=2c中的位数)。无限和可以简化(sum(r**k for r in range(1, infinity)) == r / (1 - r),如果我正确地回忆),产生a + b * (c * 10**-d) / (1 - c * 10**-d)),有限比率。也就是说,从abcd开始作为有理数,最后你会得到另一个。

(这详细说明了柯克布罗德赫斯特的回答,这是正确的,但不包括重复的小数。我不保证我没有犯过任何错误,但我相信一般方法有效。)< / p>

答案 13 :(得分:1)

我的2美分。这是btilly优秀算法的VB.NET版本:

   Public Shared Sub float_to_fraction(x As Decimal, ByRef Numerator As Long, ByRef Denom As Long, Optional ErrMargin As Decimal = 0.001)
    Dim n As Long = Int(Math.Floor(x))
    x -= n

    If x < ErrMargin Then
        Numerator = n
        Denom = 1
        Return
    ElseIf x >= 1 - ErrMargin Then
        Numerator = n + 1
        Denom = 1
        Return
    End If

    ' The lower fraction is 0/1
    Dim lower_n As Integer = 0
    Dim lower_d As Integer = 1
    ' The upper fraction is 1/1
    Dim upper_n As Integer = 1
    Dim upper_d As Integer = 1

    Dim middle_n, middle_d As Decimal
    While True
        ' The middle fraction is (lower_n + upper_n) / (lower_d + upper_d)
        middle_n = lower_n + upper_n
        middle_d = lower_d + upper_d
        ' If x + error < middle
        If middle_d * (x + ErrMargin) < middle_n Then
            ' middle is our new upper
            upper_n = middle_n
            upper_d = middle_d
            ' Else If middle < x - error
        ElseIf middle_n < (x - ErrMargin) * middle_d Then
            ' middle is our new lower
            lower_n = middle_n
            lower_d = middle_d
            ' Else middle is our best fraction
        Else
            Numerator = n * middle_d + middle_n
            Denom = middle_d
            Return
        End If
    End While
End Sub

答案 14 :(得分:1)

好吧,好像我最后不得不亲自去做。我只需要创建一个程序来模拟我自己解决它的自然方式。我刚刚将代码提交给codeproject,因为这里写出的代码不合适。您可以从此处Fraction_Conversion下载项目,或查看the codeproject page here

以下是它的工作原理:

  1. 查明给定小数是否为负
  2. 将小数转换为绝对值
  3. 获取给定小数的整数部分
  4. 获取小数部分
  5. 检查小数是否重复出现。如果十进制重复出现,那么我们将返回精确的重复小数
  6. 如果十进制没有重复出现,请通过将分子更改为10 ^ no来开始减少。十进制,否则我们从分子中减去1
  7. 然后减少分数
  8. 代码预览:

        private static string dec2frac(double dbl)
        {
            char neg = ' ';
            double dblDecimal = dbl;
            if (dblDecimal == (int) dblDecimal) return dblDecimal.ToString(); //return no if it's not a decimal
            if (dblDecimal < 0)
            {
                dblDecimal = Math.Abs(dblDecimal);
                neg = '-';
            }
            var whole = (int) Math.Truncate(dblDecimal);
            string decpart = dblDecimal.ToString().Replace(Math.Truncate(dblDecimal) + ".", "");
            double rN = Convert.ToDouble(decpart);
            double rD = Math.Pow(10, decpart.Length);
    
            string rd = recur(decpart);
            int rel = Convert.ToInt32(rd);
            if (rel != 0)
            {
                rN = rel;
                rD = (int) Math.Pow(10, rd.Length) - 1;
            }
            //just a few prime factors for testing purposes
            var primes = new[] {41, 43, 37, 31, 29, 23, 19, 17, 13, 11, 7, 5, 3, 2};
            foreach (int i in primes) reduceNo(i, ref rD, ref rN);
    
            rN = rN + (whole*rD);
            return string.Format("{0}{1}/{2}", neg, rN, rD);
        }
    

    感谢@Darius给我一个如何解决重复小数的想法:)

答案 15 :(得分:0)

这是一个用VB实现的算法,用于转换我多年前写的Floating Point Decimal to Integer Fraction

基本上你以分子= 0和分母= 1开头,如果商小于小数输入,则在分子上加1,如果商大于小数输入,则在分母上加1。重复,直到达到所需的精度。

答案 16 :(得分:0)

如果我是你,我会通过以某种方式标记的重复转换字符串来处理“在.NET中没有重复小数”的问题。

E.g。 1/3可以表示为“0.R3”      1/60可以表示为“0.01R6”

我需要从double或decimal显式转换,因为这些值只能转换为接近的分数。来自int的隐式转换是可以的。

您可以使用结构并将分数(f)存储在两个长度p和q中,使得f = p / q,q!= 0和gcd(p,q)== 1.

答案 17 :(得分:0)

在这里,您可以使用将Decimal转换为分数的方法:

/// <summary>
    /// Converts Decimals into Fractions.
    /// </summary>
    /// <param name="value">Decimal value</param>
    /// <returns>Fraction in string type</returns>
    public string DecimalToFraction(double value)
    {
        string result;
        double numerator, realValue = value;
        int num, den, decimals, length;
        num = (int)value;
        value = value - num;
        value = Math.Round(value, 5);
        length = value.ToString().Length;
        decimals = length - 2;
        numerator = value;
        for (int i = 0; i < decimals; i++)
        {
            if (realValue < 1)
            {
                numerator = numerator * 10;
            }
            else
            {
                realValue = realValue * 10;
                numerator = realValue;
            }
        }
        den = length - 2;
        string ten = "1";
        for (int i = 0; i < den; i++)
        {
            ten = ten + "0";
        }
        den = int.Parse(ten);
        num = (int)numerator;
        result = SimplifiedFractions(num, den);
        return result;
    }

    /// <summary>
    /// Converts Fractions into Simplest form.
    /// </summary>
    /// <param name="num">Numerator</param>
    /// <param name="den">Denominator</param>
    /// <returns>Simplest Fractions in string type</returns>
    string SimplifiedFractions(int num, int den)
    {
        int remNum, remDen, counter;
        if (num > den)
        {
            counter = den;
        }
        else
        {
            counter = num;
        }
        for (int i = 2; i <= counter; i++)
        {
            remNum = num % i;
            if (remNum == 0)
            {
                remDen = den % i;
                if (remDen == 0)
                {
                    num = num / i;
                    den = den / i;
                    i--;
                }
            }
        }
        return num.ToString() + "/" + den.ToString();
    }
}

答案 18 :(得分:0)

这是我不久前为一个项目编写的算法。它需要一种不同的方法,这更类似于你手工做的事情。我不能保证它的效率,但它完成了工作。

def nth_prime(n)

  prime_num = 0

  i = 2

  while true
     if is_prime?(i)             
        prime_num += 1           
        if prime_num == n         
           return i                
        end
     end
     i += 1                     
  end
end

答案 19 :(得分:0)

重复小数的简单解决方案/细分。

我认为1-9除以9的数字是重复的。 AKA 7/9 = .77777

我的解决方法是将整数乘以9,加上重复数,再再除以9。

    Ex: 28.66666
    28*9=252
    252+6=258
    258/9=28.66666

这种方法也很容易编程。截断十进制数字,乘以9,加上第一个十进制数,然后除以9。

唯一缺少的是,如果左边的数字可以被分割为3,则可能需要简化分数。

答案 20 :(得分:0)

以下是两个关于此问题的流行答案的Swift 4转换:

public func decimalToFraction(_ d: Double) -> (Int, Int) {
    var df: Double = 1
    var top: Int = 1
    var bot: Int = 1

    while df != d {
        if df < d {
            top += 1
        } else {
            bot += 1
            top = Int(d * bot)
        }
        df = top / bot
    }
    return (top, bot)
}

public func realToFraction(_ value: Double, accuracy: Double = 0.00005) -> (Int, Int)? {
    var value = value
    guard accuracy >= 0 && accuracy <= 1 else {
        Swift.print(accuracy, "Must be > 0 and < 1.")
        return nil
    }
    let theSign = sign(value)
    if theSign == -1 {
        value = abs(value)
    }

    // Accuracy is the maximum relative error; convert to absolute maxError
    let maxError = theSign == 0 ? accuracy : value * accuracy

    let n = floor(value)
    value -= n

    if value < maxError {
        return (Int(theSign * n), 1)
    }

    if 1 - maxError < value {
        return (Int(theSign * (n + 1)), 1)
    }

    // The lower fraction is 0/1
    var lowerN: Double = 0
    var lowerD: Double = 1

    // The upper fraction is 1/1
    var upperN: Double = 1
    var upperD: Double = 1

    while true {
        // The middle fraction is (lowerN + upperN) / (lowerD + upperD)
        let middleN = lowerN + upperN
        let middleD = lowerD + upperD

        if middleD * (value + maxError) < middleN {
            // real + error < middle : middle is our new upper
            upperN = middleN
            upperD = middleD
        } else if middleN < (value - maxError) * middleD {
            // middle < real - error : middle is our new lower
            lowerN = middleN
            lowerD = middleD
        } else {
            // Middle is our best fraction
            return (Int(n * middleD + middleN * theSign), Int(middleD))
        }
    }
}

答案 21 :(得分:0)

我尝试在btilly's answer上进行扩展
更改为: 如果要以小数形式显示它,请更改btilly's answer的最后else部分。因此修改后的代码变为:

def float_to_fraction (x, error=0.000001):
    n = int(math.floor(x))
    x -= n
    if x < error:
        return (n, 1)
    elif 1 - error < x:
        return (n+1, 1)
    # The lower fraction is 0/1
    lower_n = 0
    lower_d = 1
    # The upper fraction is 1/1
    upper_n = 1
    upper_d = 1
    while True:
        # The middle fraction is (lower_n + upper_n) / (lower_d + upper_d)
        middle_n = lower_n + upper_n
        middle_d = lower_d + upper_d
        # If x + error < middle
        if middle_d * (x + error) < middle_n:
            # middle is our new upper
            upper_n = middle_n
            upper_d = middle_d
        # Else If middle < x - error
        elif middle_n < (x - error) * middle_d:
            # middle is our new lower
            lower_n = middle_n
            lower_d = middle_d
        # Else middle is our best fraction
        else:
            #return (n * middle_d + middle_n, middle_d)
            frac = Fraction(n * middle_d + middle_n, middle_d)
            if (frac.numerator // frac.denominator) == 0:
                return(f"{frac.numerator % frac.denominator}/{frac.denominator}")
            elif ((frac.numerator % frac.denominator)/frac.denominator) == 0/1:
                return(f"{frac.numerator // frac.denominator}")
            else:
                return(f"{frac.numerator // frac.denominator} "f"{frac.numerator % frac.denominator}/{frac.denominator}")```

答案 22 :(得分:0)

这是btilly答案的JavaScript版本。我只是想将浮点数显示为分数,所以我返回一个字符串;

function float_to_fraction(x, error = 0.00001) {
 const n = Math.floor(x);
 x -= n;

 if (x < error) {
   return `${n}`;
 } else if (1 - error < x) {
   return `${n + 1}`;
 }

 //  The lower fraction is 0/1
 let lower_n = 0;
 let lower_d = 1;

 // The upper fraction is 1/1
 let upper_n = 1;
 let upper_d = 1;
 while (true) {
   // The middle fraction is (lower_n + upper_n) / (lower_d + upper_d)
   let middle_n = lower_n + upper_n;
   let middle_d = lower_d + upper_d;
   // If x + error < middle
   if (middle_d * (x + error) < middle_n) {
     // middle is our new upper
     upper_n = middle_n;
     upper_d = middle_d;
     // Else If middle < x - error
   } else if (middle_n < (x - error) * middle_d) {
     // middle is our new lower
     lower_n = middle_n;
     lower_d = middle_d;
     //Else middle is our best fraction
   } else {
     return `${n * middle_d + middle_n}/${middle_d}`;
   }
 }
}

答案 23 :(得分:-1)

第一个函数获取摩擦字符串格式“ 1/2” ,第二个函数查找上下部分的 gcd (最大公约数)。

public static string DoubleToFraction(double num)
    {            
        if (Math.Round(num, 6) == Math.Round(num, 0))
            return Math.Round(num, 0).ToString();
        bool minus = (num < 0) ? true : false;
        int up;
        if (minus)
            up = (int)((Math.Round(num, 6) - 0.000001) * 362880);
        else
            up = (int)((Math.Round(num, 6) + 0.000001) * 362880);
        int down = 362880;
        int div = gcd(up, down);
        up /= div;
        down /= div;
        return up + "/" + down;
    }
    public static int gcd(int a, int b)
    {
        if (b == 0)
            return Math.Abs(a);
        return gcd(b, a % b);
    }