web-dev-qa-db-fra.com

Algorithme de simplification des fractions décimales

J'ai essayé d'écrire un algorithme pour simplifier une fraction décimale et me suis rendu compte que ce n'était pas trop simple. Étonnamment, j'ai regardé en ligne et tous les codes que j'ai trouvés étaient trop longs ou ne fonctionnaient pas dans certains cas. Ce qui était encore plus énervant, c’est qu’ils ne travaillaient pas pour les nombres décimaux récurrents. Je me demandais cependant s'il y aurait un mathématicien/programmeur ici qui comprend tous les processus impliqués en simplifiant une décimale à une fraction. N'importe qui?

38
Chibueze Opata

L'algorithme que les autres personnes vous ont donné obtient la réponse en calculant la fraction continue du nombre. Cela donne une séquence fractionnaire dont la convergence est garantie très, très rapidement. cependant, il est ne pas garanti de vous donner la plus petite fraction qui soit dans une distance epsilon d'un nombre réel. Pour découvrir que vous devez marcher le Stern-Brocot tree .

Pour ce faire, vous soustrayez le sol pour obtenir le nombre compris dans l'intervalle [0, 1), votre estimation inférieure est 0 et votre estimation supérieure est 1. Maintenant, effectuez une recherche binaire jusqu'à ce que vous soyez suffisamment proche. A chaque itération, si votre inférieur est a/b et votre supérieur est c/d, votre milieu est (a + c)/(b + d). Testez votre milieu contre x et faites en sorte que le milieu soit supérieur, inférieur ou retourne votre réponse finale.

Voici quelques exemples de Python qui implémente cet algorithme et qui sont très non idiomatiques (et donc, espérons-le, lisibles même si vous ne connaissez pas le langage).

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)
39
btilly

(code amélioré en février 2017 - faites défiler jusqu'à 'l'optimisation' ...)} _

(table de comparaison d'algorithmes à la fin de cette réponse)

J'ai implémenté la réponse de btilly en C # et ...

  • ajout du support pour les nombres négatifs
  • fournissez un paramètre accuracy pour spécifier le nombre max. erreur relative, pas le max. erreur absolue; 0.01 trouverait une fraction à moins de 1% de la valeur.
  • fournir une optimisation
  • Double.NaN et Double.Infinity ne sont pas pris en charge; vous voudrez peut-être gérer ceux-ci ( exemple ici ).
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);
        }
    }
}

Le type Fraction est juste une structure simple. Bien sûr, utilisez votre propre type préféré ... (J'aime celui-ci de Rick Davin.)

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

Février 2017 optimisation

Pour certaines valeurs, telles que 0.01, 0.001, etc., l'algorithme effectue des centaines ou des milliers d'itérations linéaires. Pour résoudre ce problème, j’ai implémenté un moyen binaire de trouver la valeur finale - grâce à btilly pour cette idée. Dans l'instruction if-, remplacez les éléments suivants:

// 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));

et

// 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));

Voici l'implémentation de la méthode 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);
    }
}

Tableau de comparaison d'algorithmes

Vous souhaiterez peut-être copier le tableau dans votre éditeur de texte pour un affichage en plein écran.

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           

Comparaison de performance

J'ai effectué des tests de vitesse détaillés et tracé les résultats. Ne pas regarder la qualité et seulement la vitesse:

  • Stern-Brocot optimisation le ralentit d'au plus un facteur 2, mais le Stern-Brocot d'origine peut être des centaines, voire des milliers de fois plus lent lorsqu'il atteint les valeurs malchanceuses mentionnées. Ce n'est toujours que quelques microsecondes par appel.
  • Richards est toujours rapide.
  • Eppstein est environ 3 fois plus lent que les autres.

Stern-Brocot et Richards ont comparé:

  • Les deux retournent des fractions de Nice.
  • Richards entraîne souvent une erreur plus petite. C'est aussi un peu plus rapide.
  • Stern-Brocot descend l'arbre S-B. Il trouve la fraction du plus petit dénominateur qui répond à la précision requise, puis s'arrête.

Si vous n'avez pas besoin de la fraction de plus petit dénominateur, Richards est un bon choix.

24
Kay Zed

Je sais que vous avez dit que vous aviez cherché en ligne, mais si vous manquiez le papier suivant, cela pourrait vous aider. Il inclut un exemple de code en Pascal.

Algorithme pour convertir une virgule décimale en fraction*

Dans le cadre de sa bibliothèque standard, Ruby a un code qui traite les nombres rationnels. Il peut convertir des floats en rationnels et vice versa. Je crois que vous pouvez également consulter le code. La documentation est trouvée ici . Je sais que vous n'utilisez pas Ruby, mais il pourrait être utile d'examiner les algorithmes. 

De plus, vous pouvez appeler le code Ruby à partir de C # (ou même écrire du code Ruby dans un fichier de code C #) si vous utilisez IronRuby , qui s'exécute au-dessus du framework .net.

*Mis à jour vers un nouveau lien dès qu'il apparaît, l'URL d'origine est cassée ( http://homepage.smc.edu/kennedy_john/DEC2FRAC.pdf )

14
Matt

J'ai trouvé le même article que Matt a mentionné, et j'ai pris une seconde et l'ai implémenté en Python. Peut-être que voir la même idée dans le code le rendra plus clair. Certes, vous avez demandé une réponse en C # et je vous la donne en Python, mais il s’agit d’un programme assez trivial, et je suis sûr qu’il serait facile à traduire. Les paramètres sont num (le nombre décimal que vous souhaitez convertir en rationnel) et epsilon (la différence maximale autorisée entre num et le rationnel calculé). Certains tests rapides concluent qu'il suffit généralement de deux ou trois itérations pour converger lorsque epsilon se situe autour de 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]

Edit: Je viens de remarquer votre remarque selon laquelle ils souhaitent travailler avec des nombres décimaux récurrents. Je ne connais aucune langue dont la syntaxe prend en charge les décimales récurrentes. Je ne suis donc pas sûr de savoir comment s'y prendre. Mais exécuter 0.6666666 et 0.166666 avec cette méthode renvoie les bons résultats (2/3 et 1/6, respectivement).

Une autre édition (je ne pensais pas que cela serait si intéressant!): Si vous voulez en savoir plus sur la théorie derrière cet algorithme, Wikipedia a une excellente page sur l'algorithme d'Euclidien

9
Haldean Brown

Voici une version C # de l'exemple de Will Brown en python. Je l'ai également modifié pour gérer des nombres entiers séparés (par exemple, "2 1/8" au lieu de "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()
                            );
    }
5
Jeremy Herrman

Vous ne pouvez pas représenter un nombre décimal récurrent dans .net, je vais donc ignorer cette partie de votre question. 

Vous ne pouvez représenter qu'un nombre fini et relativement petit de chiffres.

Il y a un algorithme extrêmement simple:

  • prendre une décimale x
  • compter le nombre de chiffres après la virgule décimale; Appelez cette n
  • créer une fraction (10^n * x) / 10^n
  • supprimer les facteurs communs du numérateur et du dénominateur.

donc si vous avez 0,44, vous compteriez 2 positions comme le point décimal - n = 2, puis écrivez 

  • (0.44 * 10^2) / 10^2
  • = 44 / 100
  • factoriser (en supprimant le facteur commun de 4) donne 11 / 25
5
Kirk Broadhurst

J'ai écrit un cours rapide assez rapide qui donne les résultats que j'attendais. Vous pouvez également choisir votre précision. Il est beaucoup plus simple à partir de tout code que j'ai vu et fonctionne aussi rapidement.

//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;
        }
    }
}
4
Brian Dobony

Ceci est la version C # de l'algorithme par Ian Richards / John Kennedy. D'autres réponses utilisent ici le même algorithme:

Il ne gère pas les infinis et NaN.

Cet algorithme est rapide.

Pour des valeurs et une comparaison avec d'autres algorithmes, voir mon autre réponse

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);
}
3
Kay Zed

Cet algorithme de David Eppstein, UC Irvine, basé sur la théorie des fractions continues et originellement en C, a été traduit en C # par moi. Les fractions qu'il génère satisfont à la marge d'erreur mais ne semblent généralement pas aussi bonnes que les solutions de mes autres réponses. Par exemple. 0.5 devient 999/1999 tandis que 1/2 serait préféré lorsqu'il était affiché à un utilisateur (si vous en avez besoin, consultez mon autreréponses ).

Il existe une surcharge pour spécifier la marge d'erreur sous forme de double (par rapport à la valeur, pas à l'erreur absolue). Pour le type Fraction, voir mon autre réponse.

Soit dit en passant, si vos fractions peuvent grossir, changez la ints correspondante en long. Comparé aux autres algorithmes, celui-ci est sujet au débordement.

Pour des valeurs et une comparaison avec d'autres algorithmes, voir mon autre réponse

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);
}
2
Kay Zed

Je viens avec une réponse très tardive. Le code est tiré de un article de Richards publié en 1981 et écrit en 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;
}

Je réécris ici mon implémentation de 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;
}

Et ici je propose des tests avec une erreur de 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 |

Comme vous pouvez le constater, les deux méthodes donnent plus ou moins les mêmes résultats mais celle de Richards est bien plus efficace et facile à mettre en œuvre.

Modifier

Pour compiler mon code, vous avez besoin d'une difinition pour my::sign qui est simplement une fonction Qui retourne le signe d'une variable. Voici ma mise en place

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

Pardon

Je suppose que cette réponse fait référence au même algorithme. Je n'avais pas vu ça avant ...

2
PinkFloyd

Eh bien, il semble que j'ai finalement dû le faire moi-même. Je devais juste créer un programme simulant la manière naturelle de le résoudre moi-même. Je viens de soumettre le code à codeproject, car écrire tout le code ici ne conviendrait pas. Vous pouvez télécharger le projet ici Fraction_Conversion , ou regardez la page codeproject ici .

Voici comment ça fonctionne:

  1. Savoir si le nombre décimal donné est négatif
  2. Convertir le nombre décimal en valeur absolue
  3. Obtenir une partie entière de la décimale donnée
  4. Obtenir la partie décimale
  5. Vérifiez si le nombre décimal est récurrent. Si le nombre décimal est récurrent, nous renvoyons le nombre décimal récurrent exact
  6. Si la décimale n'est pas récurrente, démarrez la réduction en modifiant le numérateur en 10 ^ non. de la décimale, sinon on soustrait 1 du numérateur
  7. Puis réduire la fraction

Aperçu du code:

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

Merci @ Darius de m'avoir donné une idée de la façon de résoudre les nombres décimaux récurrents :)

1
Chibueze Opata

Mes 2 centimes Voici la version VB.NET de l'excellent algorithme de btilly:

   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
1
dotNET

Une décimale récurrente peut être représentée par deux décimales finies: la partie gauche avant la répétition et la partie répétée. Par exemple. 1.6181818... = 1.6 + 0.1*(0.18...). Pensez à ceci comme a + b * sum(c * 10**-(d*k) for k in range(1, infinity)) (en notation Python ici). Dans mon exemple, a=1.6, b=0.1, c=18, d=2 (le nombre de chiffres dans c). La somme infinie peut être simplifiée (sum(r**k for r in range(1, infinity)) == r / (1 - r) si je me souviens bien), donnant a + b * (c * 10**-d) / (1 - c * 10**-d)), un rapport fini. En d’autres termes, commencez par a, b, c et d sous forme de nombres rationnels et vous vous retrouverez avec un autre.

(Ceci élabore la réponse de Kirk Broadhurst, qui est exacte, mais ne couvre pas la répétition de nombres décimaux. Je ne promets pas de n'avoir commis aucune erreur ci-dessus, même si je suis convaincu que l'approche générale fonctionne.)

1
Darius Bacon

Les solutions les plus populaires à ce problème sont algorithme de Richards _ et algorithme de Stern-Brocot , implémenté par btilly avec optimisation de la vitesse par btilly et Jay Zed. L’algorithme de Richards est le plus rapide, mais ne garantit pas le retour de la meilleure fraction.

J'ai une solution à ce problème qui donne toujours la meilleure fraction et est également plus rapide que tous les algorithmes ci-dessus. Voici l'algorithme en C # (explication et test de vitesse ci-dessous).

Ceci est un algorithme court sans commentaires. Une version complète est fournie dans le code source à la fin.

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

Où Fraction est une classe simple pour stocker une fraction, comme suit: 

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

Comment ça marche

Comme les autres solutions mentionnées, ma solution est basée sur la fraction continue. D'autres solutions telles que celle de Eppstein ou des solutions basées sur la répétition de nombres décimaux se sont révélées plus lentes et/ou ont donné des résultats non optimaux.

Fraction continue
Les solutions basées sur la fraction continue reposent principalement sur deux algorithmes, décrits dans un article de Ian Richards publié en 1981 ( here _). Il les appelle «algorithme de fraction continue lente» et «fraction continue rapide». algorithme". Le premier est connu sous le nom d’algorithme de Stern-Brocot tandis que le dernier est connu sous le nom d’algorithme de Richards.

Mon algorithme (brève explication)
Pour bien comprendre mon algorithme, vous devez avoir lu l'article de Ian Richards ou au moins comprendre ce qu'est une paire de Farey. De plus, lisez l’algorithme avec des commentaires à la fin de cet article. 

L'algorithme utilise une paire de Farey, contenant une fraction gauche et une fraction droite. En prenant plusieurs fois le médiant, il se rapproche de la valeur cible. C'est comme l'algorithme lent mais il y a deux différences majeures:

  1. Plusieurs itérations sont effectuées en même temps tant que le médiant reste sur un côté de la valeur cible.
  2. Les fractions gauche et droite ne peuvent pas être plus proches de la valeur cible que la précision donnée.

Alternativement, les côtés droit et gauche de la valeur cible sont vérifiés. Si l'algorithme ne peut pas produire un résultat plus proche de la valeur cible, le processus se termine. Le mediant résultant est la solution optimale.

Test de rapidité

J'ai effectué des tests de vitesse sur mon ordinateur portable avec les algorithmes suivants:

  1. Algorithme lent amélioré de Kay Zed et btilly
  2. L’implémentation de l’algorithme Fast par John Kennedy, convertie en C # par Kay Zed
  3. Mon implémentation de l'algorithme Fast (proche de l'original par Ian Richards)
  4. _ { Jeremy Herrman) implémentation de l'algorithme Fast
  5. Mon algorithme ci-dessus

J'ai omis l'algorithme lent d'origine de btilly , en raison de ses mauvaises performances dans le pire des cas.

Ensemble d'essai
Je choisis un ensemble de valeurs cibles (très arbitraire) et calcule la fraction 100 000 fois avec 5 précisions différentes. Certains algorithmes (futurs) ne pouvant pas gérer des fractions incorrectes, seules les valeurs cibles comprises entre 0.0 et 1.0 ont été testées. La précision a été prise dans la plage allant de 2 à 6 décimales (0,005 à 0,0000005). Le set suivant a été utilisé:

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

Résultats 

J'ai fait 13 essais. Le résultat, exprimé en millisecondes, est nécessaire pour l'ensemble du jeu de données.

    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

Conclusion (en sautant l'analyse)
Même sans analyse statistique, il est facile de voir que mon algorithme est plus rapide que les autres algorithmes testés. La différence avec la variante la plus rapide de «l'algorithme rapide» est toutefois inférieure à 1%. L'algorithme lent amélioré est 30% à 35% plus lent que l'algorithme le plus rapide ».

D'autre part, même l'algorithme le plus lent effectue un calcul en moins d'une microseconde. Donc, dans des circonstances normales, la vitesse n'est pas vraiment un problème. À mon avis, le meilleur algorithme est principalement une question de goût, alors choisissez l'un des algorithmes testés sur d'autres critères.

  • Est-ce que l'algorithme donne le meilleur résultat?
  • L'algorithme est-il disponible dans ma langue préférée?
  • Quelle est la taille de code de l'algorithme?
  • L'algorithme est-il lisible, compréhensible?

Code source

Le code source ci-dessous contient tous les algorithmes utilisés. Il comprend:

  • Mon algorithme original (avec commentaires)
  • Une version encore plus rapide de mon algorithme (mais moins lisible)
  • L'algorithme lent d'origine
  • Tous les algorithmes testés
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);
    }

}
1
Sjaak

J'ai récemment dû effectuer cette tâche même avec un type de données décimal qui est stocké dans notre base de données SQL Server. Au niveau de la présentation, cette valeur a été modifiée sous forme de valeur fractionnelle dans une zone de texte. La complexité ici consistait à utiliser le type de données décimal, qui contient des valeurs assez grandes par rapport à int ou long. Donc, pour réduire les risques de dépassement des données, je me suis contenté du type de données décimal tout au long de la conversion.

Avant de commencer, je souhaite commenter la réponse précédente de Kirk. Il a absolument raison tant qu'aucune hypothèse n'est faite. Toutefois, si le développeur ne recherche que des motifs répétés dans les limites du type de données décimales, 3333333 ... peut être représenté par 1/3. Un exemple de l'algorithme est disponible à l'adresse basic-mathematics.com . Encore une fois, cela signifie que vous devez émettre des hypothèses sur la base des informations disponibles et que cette méthode ne capture qu'un très petit sous-ensemble de nombres décimaux répétés. Cependant, pour les petits nombres, ça devrait aller.

Pour aller de l'avant, laissez-moi vous donner un aperçu de ma solution. Si vous voulez lire un exemple complet avec du code supplémentaire, j'ai créé un article de blog } avec beaucoup plus de détails.

Convertir le type de données décimal en fraction de chaîne

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

C'est assez simple, où DecimalToFraction (valeur décimale) n'est rien de plus qu'un point d'entrée simplifié pour la première méthode qui donne accès à tous les composants qui composent une fraction. Si vous avez une décimale de .325, divisez-la par 10 à la puissance du nombre de décimales. Enfin réduire la fraction. Et, dans cet exemple, .325 = 325/10 ^ 3 = 325/1000 = 13/40.

Ensuite, aller dans l'autre sens.

Convertir une fraction de chaîne en type de données décimal

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

La reconversion en décimale est également très simple. Ici, nous analysons les composants fractionnaires, les stockons dans quelque chose avec lequel nous pouvons travailler (ici des valeurs décimales) et effectuons notre division.

1
Jeff Willener

Voici deux conversions Swift 4 de réponses populaires à ce problème:

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))
        }
    }
}
0
Ryan Francesconi

Voici un algorithme que j'ai écrit pour un projet il n'y a pas si longtemps. Elle adopte une approche différente, qui s'apparente davantage à quelque chose que vous feriez à la main. Je ne peux pas garantir son efficacité, mais le travail est fait.

    public static string toFraction(string exp) {
        double x = Convert.ToDouble(exp);
        int sign = (Math.Abs(x) == x) ? 1 : -1;
        x = Math.Abs(x);
        int n = (int)x; // integer part
        x -= n; // fractional part
        int mult, nm, dm;
        int decCount = 0;

        Match m = Regex.Match(Convert.ToString(x), @"([0-9]+?)\1+.?$");
        // repeating fraction
        if (m.Success) {
            m = Regex.Match(m.Value, @"([0-9]+?)(?=\1)");
            mult = (int)Math.Pow(10, m.Length);

            // We have our basic fraction
            nm = (int)Math.Round(((x * mult) - x));
            dm = mult - 1;
        }
        // get the number of decimal places
        else {
            double t = x;
            while (t != 0) {
                decCount++;
                t *= 10;
                t -= (int)t;
            }
            mult = (int)Math.Pow(10, decCount);

            // We have our basic fraction
            nm = (int)((x * mult));
            dm = mult;
        }
        // can't be simplified
        if (nm < 0 || dm < 0) return exp;

        //Simplify
        Stack factors = new Stack();
        for (int i = 2; i < nm + 1; i++) {
            if (nm % i == 0) factors.Push(i);  // i is a factor of the numerator
        }
        // check against the denominator, stopping at the highest match
        while(factors.Count != 0) {
            // we have a common factor
            if (dm % (int)factors.Peek() == 0) {
                int f = (int)factors.Pop();
                nm /= f;
                dm /= f;
                break;
            }
            else factors.Pop();
        }
        nm += (n * dm);
        nm *= sign;
        if (dm == 1) return Convert.ToString(nm);
        else return Convert.ToString(nm) + "/" + Convert.ToString(dm);
    }
0
Michael

Si j'étais vous, je réglerais le problème de "non-répétition de nombres décimaux dans .NET" en le faisant convertir les chaînes avec la récurrence marquée d'une manière ou d'une autre.

Par exemple. 1/3 pourrait être représenté "0.R3" 1/60 pourrait être représenté "0.01R6"

J'aurais besoin d'une conversion explicite à partir de doubles ou de décimales, car de telles valeurs ne peuvent être converties qu'en une fraction proche. La conversion implicite de int est ok.

Vous pouvez utiliser une structure et stocker votre fraction (f) dans deux entiers longs p et q tels que f = p/q, q! = 0 et gcd (p, q) == 1.

0
Chris Susie

Voici un algorithme implémenté dans VB qui convertit Décimale en virgule flottante en fraction entière que j’ai écrit il ya de nombreuses années.

En gros, vous commencez avec un numérateur = 0 et un dénominateur = 1, puis si le quotient est inférieur à la décimale, ajoutez 1 au numérateur et si le quotient est supérieur à la décimale, ajoutez 1 au dénominateur. Répétez l'opération jusqu'à obtenir la précision souhaitée.

0
oosterwal

Solution simple/décomposition du nombre décimal répété.

J'ai pris la logique que les nombres 1-9 divisés par 9 se répètent. AKA 7/9 = .77777

Ma solution serait de multiplier le nombre entier par 9, d’ajouter le nombre de répétitions, puis de diviser à nouveau par 9.

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

Cette méthode est également assez facile à programmer. Tronquer le chiffre décimal, multiplier par 9, ajouter la première décimale, puis diviser par 9.

Il ne manque que la simplification de la fraction si le nombre de gauche est divisible par 3.

0
lastlink

Ici, vous pouvez avoir la méthode pour convertir Decimal en fractions:

/// <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();
    }
}
0
Syed Faizan Ali