Comment convertir des flotteurs en fractions lisibles par l'homme?


103

Disons que nous l'avons fait 0.33, nous devons produire 1/3.
Si c'est le cas 0.4, nous devons produire 2/5.

L'idée est de le rendre lisible par l'homme pour que l'utilisateur comprenne « x parties sur y » comme une meilleure façon de comprendre les données.

Je sais que les pourcentages sont un bon substitut, mais je me demandais s'il y avait un moyen simple de le faire?


L' exemple .33=> "1/3"me concerne; Je m'attendrais à .33=> "33/100". Je suppose que vous vouliez dire .33..., bien sûr, mais cela expose un problème avec la question - avant de pouvoir nous installer sur un algorithme, nous devons décider du comportement attendu. La réponse Python de @ Debilski utilise .limit_denominator()ce qui par défaut est un dénominateur maximum de 10 ^ 7; probablement une bonne valeur par défaut dans la pratique, mais cela peut encore introduire des bugs si vous ne faites pas attention, et ne le retour "33/100"dans le .33cas.
dimo414

Avec toutes les fonctionnalités spécifiques à la langue disponibles. Vous ne savez pas ce que vous demandez, si en effet ce n'est pas une simple contradiction dans les termes.
Marquis of Lorne

Réponses:


70

J'ai trouvé que l' approximation rationnelle de recherche de David Eppstein au code réel C donné était exactement ce que vous demandiez. Son basé sur la théorie des fractions continues et très rapide et assez compact.

J'ai utilisé des versions de ce personnalisé pour des limites de numérateur et de dénominateur spécifiques.

/*
** find rational approximation to given real number
** David Eppstein / UC Irvine / 8 Aug 1993
**
** With corrections from Arno Formella, May 2008
**
** usage: a.out r d
**   r is real number to approx
**   d is the maximum denominator allowed
**
** based on the theory of continued fractions
** if x = a1 + 1/(a2 + 1/(a3 + 1/(a4 + ...)))
** then best approximation is found by truncating this series
** (with some adjustments in the last term).
**
** Note the fraction can be recovered as the first column of the matrix
**  ( a1 1 ) ( a2 1 ) ( a3 1 ) ...
**  ( 1  0 ) ( 1  0 ) ( 1  0 )
** Instead of keeping the sequence of continued fraction terms,
** we just keep the last partial product of these matrices.
*/

#include <stdio.h>

main(ac, av)
int ac;
char ** av;
{
    double atof();
    int atoi();
    void exit();

    long m[2][2];
    double x, startx;
    long maxden;
    long ai;

    /* read command line arguments */
    if (ac != 3) {
        fprintf(stderr, "usage: %s r d\n",av[0]);  // AF: argument missing
        exit(1);
    }
    startx = x = atof(av[1]);
    maxden = atoi(av[2]);

    /* initialize matrix */
    m[0][0] = m[1][1] = 1;
    m[0][1] = m[1][0] = 0;

    /* loop finding terms until denom gets too big */
    while (m[1][0] *  ( ai = (long)x ) + m[1][1] <= maxden) {
        long t;
        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;
        if(x==(double)ai) break;     // AF: division by zero
        x = 1/(x - (double) ai);
        if(x>(double)0x7FFFFFFF) break;  // AF: representation failure
    } 

    /* now remaining x is between 0 and 1/ai */
    /* approx as either 0 or 1/m where m is max that will fit in maxden */
    /* first try zero */
    printf("%ld/%ld, error = %e\n", m[0][0], m[1][0],
           startx - ((double) m[0][0] / (double) m[1][0]));

    /* now try other possibility */
    ai = (maxden - 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];
    printf("%ld/%ld, error = %e\n", m[0][0], m[1][0],
           startx - ((double) m[0][0] / (double) m[1][0]));
}

6
Pour ceux d'entre vous qui recherchent une solution en Ruby, nous avons de la chance! Christopher Lord a implémenté l'algorithme ci-dessus dans une gemme Ruby. Voir christopher.lord.ac/fractions-in-ruby et rubygems.org/gems/fraction
shedd le

6
Sachez qu'il y a des cas extrêmes que ce code ne gère pas très bien l: lorsqu'il est donné -1,3333333 avec un dénominateur maximum de 4, il renvoie 4 / -3 avec une erreur de 3,333333e-08 et -5/4 avec une erreur = -8.333330e-02, ce qui est correct. Mais lorsqu'on lui donne -1,33333337 avec le même dénominateur maximum, il tourne 12121211 / -9090908 avec une erreur d'erreur = 4,218847e-15 et -4/3 avec une erreur de -3,666667e-08, ce qui n'est pas correct. C'est un problème en particulier lors de la présentation de l'algorithme avec des nombres à virgule flottante calculés tels que -4/3, ce qui donne des résultats incorrects comme ceux-ci.
edsko

27

À partir de Python 2.6, il y a le fractionsmodule.

(Citant les documents.)

>>> from fractions import Fraction
>>> Fraction('3.1415926535897932').limit_denominator(1000)
Fraction(355, 113)

>>> from math import pi, cos
>>> Fraction.from_float(cos(pi/3))
Fraction(4503599627370497, 9007199254740992)
>>> Fraction.from_float(cos(pi/3)).limit_denominator()
Fraction(1, 2)

6
Notes de mise en œuvre et d'algorithme sur hg.python.org/cpython/file/822c7c0d27d1/Lib/fractions.py#l211
piro

2
@Debilski auquel des OP language agnosticet des algorithmbalises votre réponse satisfait-elle?
vladr du

2
@vladr Eh bien, étant donné que j'ai écrit cette réponse il y a presque 6 ans (et plus d'un an après que la question ait été posée), je suppose que je ne sais plus quel était mon raisonnement à l'époque. Très probablement, je faisais référence à ce commentaire: stackoverflow.com/questions/95727/… OTOH Il se pourrait aussi que cette réponse ait été fusionnée à partir d'une autre question. Qui peut dire après toutes ces années…
Debilski

Vous pouvez ajouter quelques phrases sur l'algorithme utilisé par le module des fractions (et mettre à jour votre réponse pour Python3 peut-être).
einpoklum

21

Si la sortie doit donner à un lecteur humain une impression rapide de l'ordre du résultat, cela n'a aucun sens de renvoyer quelque chose comme "113/211", donc la sortie devrait se limiter à l'utilisation de nombres à un chiffre (et peut-être 1 / 10 et 9/10). Si c'est le cas, vous pouvez observer qu'il n'y a que 27 fractions différentes .

Puisque le calcul sous-jacent pour générer la sortie ne changera jamais, une solution pourrait être de simplement coder en dur un arbre de recherche binaire, de sorte que la fonction effectue au plus log (27) ~ = 4 3/4 comparaisons. Voici une version C testée du code

char *userTextForDouble(double d, char *rval)
{
    if (d == 0.0)
        return "0";

    // TODO: negative numbers:if (d < 0.0)...
    if (d >= 1.0)
        sprintf(rval, "%.0f ", floor(d));
    d = d-floor(d); // now only the fractional part is left

    if (d == 0.0)
        return rval;

    if( d < 0.47 )
    {
        if( d < 0.25 )
        {
            if( d < 0.16 )
            {
                if( d < 0.12 ) // Note: fixed from .13
                {
                    if( d < 0.11 )
                        strcat(rval, "1/10"); // .1
                    else
                        strcat(rval, "1/9"); // .1111....
                }
                else // d >= .12
                {
                    if( d < 0.14 )
                        strcat(rval, "1/8"); // .125
                    else
                        strcat(rval, "1/7"); // .1428...
                }
            }
            else // d >= .16
            {
                if( d < 0.19 )
                {
                    strcat(rval, "1/6"); // .1666...
                }
                else // d > .19
                {
                    if( d < 0.22 )
                        strcat(rval, "1/5"); // .2
                    else
                        strcat(rval, "2/9"); // .2222...
                }
            }
        }
        else // d >= .25
        {
            if( d < 0.37 ) // Note: fixed from .38
            {
                if( d < 0.28 ) // Note: fixed from .29
                {
                    strcat(rval, "1/4"); // .25
                }
                else // d >=.28
                {
                    if( d < 0.31 )
                        strcat(rval, "2/7"); // .2857...
                    else
                        strcat(rval, "1/3"); // .3333...
                }
            }
            else // d >= .37
            {
                if( d < 0.42 ) // Note: fixed from .43
                {
                    if( d < 0.40 )
                        strcat(rval, "3/8"); // .375
                    else
                        strcat(rval, "2/5"); // .4
                }
                else // d >= .42
                {
                    if( d < 0.44 )
                        strcat(rval, "3/7"); // .4285...
                    else
                        strcat(rval, "4/9"); // .4444...
                }
            }
        }
    }
    else
    {
        if( d < 0.71 )
        {
            if( d < 0.60 )
            {
                if( d < 0.55 ) // Note: fixed from .56
                {
                    strcat(rval, "1/2"); // .5
                }
                else // d >= .55
                {
                    if( d < 0.57 )
                        strcat(rval, "5/9"); // .5555...
                    else
                        strcat(rval, "4/7"); // .5714
                }
            }
            else // d >= .6
            {
                if( d < 0.62 ) // Note: Fixed from .63
                {
                    strcat(rval, "3/5"); // .6
                }
                else // d >= .62
                {
                    if( d < 0.66 )
                        strcat(rval, "5/8"); // .625
                    else
                        strcat(rval, "2/3"); // .6666...
                }
            }
        }
        else
        {
            if( d < 0.80 )
            {
                if( d < 0.74 )
                {
                    strcat(rval, "5/7"); // .7142...
                }
                else // d >= .74
                {
                    if(d < 0.77 ) // Note: fixed from .78
                        strcat(rval, "3/4"); // .75
                    else
                        strcat(rval, "7/9"); // .7777...
                }
            }
            else // d >= .8
            {
                if( d < 0.85 ) // Note: fixed from .86
                {
                    if( d < 0.83 )
                        strcat(rval, "4/5"); // .8
                    else
                        strcat(rval, "5/6"); // .8333...
                }
                else // d >= .85
                {
                    if( d < 0.87 ) // Note: fixed from .88
                    {
                        strcat(rval, "6/7"); // .8571
                    }
                    else // d >= .87
                    {
                        if( d < 0.88 ) // Note: fixed from .89
                        {
                            strcat(rval, "7/8"); // .875
                        }
                        else // d >= .88
                        {
                            if( d < 0.90 )
                                strcat(rval, "8/9"); // .8888...
                            else
                                strcat(rval, "9/10"); // .9
                        }
                    }
                }
            }
        }
    }

    return rval;
}

3
C'est le genre de réflexion latérale dont nous avons le plus besoin! Excellente suggestion.
edsko

1
C'est un peu moche mais très rapide et pratique
Bosak

1
C'est une approche intéressante qui est merveilleusement simple. Pour économiser de l'espace, vous pouvez à la place effectuer une recherche binaire dans un tableau ou créer un arbre binaire, mais votre approche est probablement un peu plus rapide (vous pouvez économiser de l'espace en utilisant un seul appel à strcat avant de revenir et attribuer une var là où elle est maintenant appelée). J'aurais aussi inclus 3/10 et 7/10, mais c'est peut-être juste moi.
jimhark

1
Inspiré par cette solution, j'ai créé un code court (mais totalement non optimisé). Il peut facilement être étendu pour couvrir une plus large gamme de fractions. jsfiddle.net/PdL23/1
Deepak Joy

1
Notez que 1/1000c'est également très lisible humainement, mais l'algorithme ci-dessus ne produirait qu'une 1/10approximation très grossière ; Je crois que des améliorations peuvent être apportées en termes dont dénominateurs lisible par l' homme on peut choisir, et / ou l'ajout de <, >, <<, >>préfixes pour donner une idée de la grossièreté de l'approximation.
vladr du

16

Voici un lien expliquant les mathématiques derrière la conversion d'un décimal en fraction:

http://www.webmath.com/dec2fract.html

Et voici un exemple de fonction pour savoir comment le faire en utilisant VB (à partir de www.freevbcode.com/ShowCode.asp?ID=582):

Public Function Dec2Frac(ByVal f As Double) As String

   Dim df As Double
   Dim lUpperPart As Long
   Dim lLowerPart As Long

   lUpperPart = 1
   lLowerPart = 1

   df = lUpperPart / lLowerPart
   While (df <> f)
      If (df < f) Then
         lUpperPart = lUpperPart + 1
      Else
         lLowerPart = lLowerPart + 1
         lUpperPart = f * lLowerPart
      End If
      df = lUpperPart / lLowerPart
   Wend
Dec2Frac = CStr(lUpperPart) & "/" & CStr(lLowerPart)
End Function

(À partir des recherches Google: convertissez le décimal en fraction, convertissez le code décimal en fraction)


2
Notez que cet algorithme prend un temps Ω (m) lorsque f = n / m. Et cela pourrait être beaucoup, même si vous ne l'aviez pas prévu (pensez à 0.66666666667).
einpoklum

10

Vous voudrez peut-être lire ce que tout informaticien devrait savoir sur l'arithmétique à virgule flottante .

Vous devrez spécifier une certaine précision en multipliant par un grand nombre:

3.141592 * 1000000 = 3141592

alors vous pouvez faire une fraction:

3 + (141592 / 1000000)

et réduire via GCD ...

3 + (17699 / 125000)

mais il n'y a aucun moyen d'obtenir la fraction voulue . Vous voudrez peut-être toujours utiliser des fractions dans tout votre code à la place - n'oubliez pas de réduire les fractions lorsque vous le pouvez pour éviter le débordement!


9

Voici les versions Perl et Javascript du code VB suggérées par devinmoore:

Perl:

sub dec2frac {
    my $d = shift;

    my $df  = 1;
    my $top = 1;
    my $bot = 1;

    while ($df != $d) {
      if ($df < $d) {
        $top += 1;
      }
      else {
         $bot += 1;
         $top = int($d * $bot);
      }
      $df = $top / $bot;
   }
   return "$top/$bot";
}

Et le javascript presque identique:

function dec2frac(d) {

    var df = 1;
    var top = 1;
    var bot = 1;

    while (df != d) {
        if (df < d) {
            top += 1;
        }
        else {
            bot += 1;
            top = parseInt(d * bot);
        }
        df = top / bot;
    }
    return top + '/' + bot;
}

9

Implémentation AC #

/// <summary>
/// Represents a rational number
/// </summary>
public struct Fraction
{
    public int Numerator;
    public int Denominator;

    /// <summary>
    /// Constructor
    /// </summary>
    public Fraction(int numerator, int denominator)
    {
        this.Numerator = numerator;
        this.Denominator = denominator;
    }

    /// <summary>
    /// Approximates a fraction from the provided double
    /// </summary>
    public static Fraction Parse(double d)
    {
        return ApproximateFraction(d);
    }

    /// <summary>
    /// Returns this fraction expressed as a double, rounded to the specified number of decimal places.
    /// Returns double.NaN if denominator is zero
    /// </summary>
    public double ToDouble(int decimalPlaces)
    {
        if (this.Denominator == 0)
            return double.NaN;

        return System.Math.Round(
            Numerator / (double)Denominator,
            decimalPlaces
        );
    }


    /// <summary>
    /// Approximates the provided value to a fraction.
    /// http://stackoverflow.com/questions/95727/how-to-convert-floats-to-human-readable-fractions
    /// </summary>
    private static Fraction ApproximateFraction(double value)
    {
        const double EPSILON = .000001d;

        int n = 1;  // numerator
        int d = 1;  // denominator
        double fraction = n / d;

        while (System.Math.Abs(fraction - value) > EPSILON)
        {
            if (fraction < value)
            {
                n++;
            }
            else
            {
                d++;
                n = (int)System.Math.Round(value * d);
            }

            fraction = n / (double)d;
        }

        return new Fraction(n, d);
    }
}


6

Une partie du problème est que tant de fractions ne sont pas facilement interprétées comme des fractions. Par exemple, 0,33 n'est pas 1/3, c'est 33/100. Mais si vous vous souvenez de votre formation à l'école élémentaire, il existe un processus de conversion des valeurs décimales en fractions, mais il est peu probable que vous donniez ce que vous voulez car la plupart du temps, les nombres décimaux ne sont pas stockés à 0,33, mais à 0,329999999999998 ou quelque chose du genre.

Faites-vous une faveur et ne vous embêtez pas avec cela, mais si vous en avez besoin, vous pouvez faire ce qui suit:

Multipliez la valeur d'origine par 10 jusqu'à ce que vous supprimiez la partie fractionnaire. Conservez ce nombre et utilisez-le comme diviseur. Faites ensuite une série de simplifications en recherchant des dénominateurs communs.

Donc 0.4 serait 4/10. Vous recherchez alors des diviseurs communs commençant par des valeurs faibles, probablement des nombres premiers. En commençant par 2, vous verriez si 2 divise le numérateur et le dénominateur de manière égale en vérifiant si le plancher de division est le même que la division elle-même.

floor(5/2) = 2
5/2 = 2.5

Donc 5 ne divise pas 2 uniformément. Alors vous vérifiez le nombre suivant, disons 3. Vous faites cela jusqu'à ce que vous atteigniez ou au-dessus de la racine carrée du plus petit nombre.

Après avoir fait cela, vous avez besoin


1
Je suggère d'utiliser l'algorithme euclidien pour cette dernière étape
Graphics Noob


4

"Disons que nous avons 0,33, nous devons sortir" 1/3 "."

Quelle précision attendez-vous de la «solution»? 0,33 n'est pas égal à 1/3. Comment reconnaissez-vous une «bonne» réponse (facile à lire)?

Quoi qu'il en soit, un algorithme possible pourrait être:

Si vous prévoyez de trouver une fraction la plus proche sous une forme X / Y où Y est inférieur à 10, vous pouvez alors parcourir les 9 Y possibles, pour chaque Y calculer X, puis sélectionner le plus précis.


3

Je pense que la meilleure façon de faire est de convertir d'abord votre valeur flottante en représentation ascii. En C ++, vous pouvez utiliser ostringstream ou en C, vous pouvez utiliser sprintf. Voici à quoi cela ressemblerait en C ++:

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

Une approche similaire pourrait être adoptée en ligne droite C.

Ensuite, vous devrez vérifier que la fraction est dans les termes les plus bas. Cet algorithme donnera une réponse précise, c'est-à-dire que 0,33 produirait «33/100» et non «1/3». Cependant, 0,4 donnerait «4/10», qui, réduit aux termes les plus bas, serait «2/5». Ce n'est peut-être pas aussi puissant que la solution d'EppStein, mais je pense que c'est plus simple.


8 ans plus tard, je suis tombé sur votre solution, j'ai testé et cela fonctionne parfaitement jusqu'à présent, mais vous avez dit que ce n'est pas aussi puissant que la solution d'EppStein et je me demande pourquoi. Puisque votre solution est beaucoup plus simple ne devrait-elle pas être la solution de choix, ne sommes-nous pas censés faire le code le plus simple possible tant que cela fonctionne et qu'il est sûr?
HBatalha

3

Une solution intégrée dans R:

library(MASS)
fractions(0.666666666)
## [1] 2/3

Cela utilise une méthode de fraction continue et a optionnel cycleset des max.denominatorarguments pour ajuster la précision.


Aussi library(numbers)et contFrac(0.6666); pour obtenir la sortie de chaîne comme vous le souhaitez:paste(contFrac(0.666, tol=1e-03)$rat, collapse="/")
rbatt

2

Vous devrez déterminer le niveau d'erreur que vous êtes prêt à accepter. Toutes les fractions décimales ne se réduiront pas à une simple fraction. Je choisirais probablement un nombre facilement divisible, comme 60, et déterminerais combien de 60e est le plus proche de la valeur, puis je simplifierais la fraction.


2

Vous pouvez le faire dans n'importe quel langage de programmation en suivant les étapes suivantes:

  1. Multipliez et divisez par 10 ^ x où x est la puissance de 10 requise pour s'assurer qu'il ne reste plus de décimales au nombre. Exemple: multipliez 0,33 par 10 ^ 2 = 100 pour en faire 33 et divisez-le par la même chose pour obtenir 33/100
  2. Réduisez le numérateur et le dénominateur de la fraction résultante par factorisation, jusqu'à ce que vous ne puissiez plus obtenir des entiers à partir du résultat.
  3. La fraction réduite qui en résulte devrait être votre réponse.

Exemple: 0,2 = 0,2 x 10 ^ 1/10 ^ 1 = 2/10 = 1/5

Donc, cela peut être lu comme «1 partie sur 5»


2

Une solution consiste simplement à stocker tous les nombres sous forme de nombres rationnels en premier lieu. Il existe des bibliothèques pour l'arithmétique des nombres rationnels (par exemple GMP ). Si vous utilisez un langage OO, vous pourrez peut-être simplement utiliser une bibliothèque de classes de nombres rationnels pour remplacer votre classe de nombres.

Les programmes financiers, entre autres, utiliseraient une telle solution pour être en mesure de faire des calculs exacts et de préserver la précision qui peut être perdue en utilisant un flotteur simple.

Bien sûr, ce sera beaucoup plus lent, donc ce ne sera peut-être pas pratique pour vous. Cela dépend de la quantité de calculs à faire et de l'importance de la précision pour vous.

a = rational(1);
b = rational(3);
c = a / b;

print (c.asFraction)  --->  "1/3"
print (c.asFloat) ----> "0.333333"

2

Disons que nous avons 0,33, nous devons afficher "1/3". Si nous avons "0.4", nous devons sortir "2/5".

C'est faux dans le cas courant, à cause de 1/3 = 0,33333333 = 0. (3) De plus, il est impossible de trouver à partir des solutions suggérées ci-dessus est décimal peut être converti en fraction avec une précision définie, car la sortie est toujours une fraction.

MAIS, je suggère ma fonction complète avec de nombreuses options basées sur l'idée d' une série géométrique infinie , en particulier sur la formule:

entrez la description de l'image ici

Au début, cette fonction essaie de trouver une période de fraction dans la représentation sous forme de chaîne. Après cela, la formule décrite ci-dessus est appliquée.

Le code des nombres rationnels est emprunté à l' implémentation des nombres rationnels de Stephen M. McKamey en C #. J'espère qu'il n'est pas très difficile de porter mon code sur d'autres langues.

/// <summary>
/// Convert decimal to fraction
/// </summary>
/// <param name="value">decimal value to convert</param>
/// <param name="result">result fraction if conversation is succsess</param>
/// <param name="decimalPlaces">precision of considereation frac part of value</param>
/// <param name="trimZeroes">trim zeroes on the right part of the value or not</param>
/// <param name="minPeriodRepeat">minimum period repeating</param>
/// <param name="digitsForReal">precision for determination value to real if period has not been founded</param>
/// <returns></returns>
public static bool FromDecimal(decimal value, out Rational<T> result, 
    int decimalPlaces = 28, bool trimZeroes = false, decimal minPeriodRepeat = 2, int digitsForReal = 9)
{
    var valueStr = value.ToString("0.0000000000000000000000000000", CultureInfo.InvariantCulture);
    var strs = valueStr.Split('.');

    long intPart = long.Parse(strs[0]);
    string fracPartTrimEnd = strs[1].TrimEnd(new char[] { '0' });
    string fracPart;

    if (trimZeroes)
    {
        fracPart = fracPartTrimEnd;
        decimalPlaces = Math.Min(decimalPlaces, fracPart.Length);
    }
    else
        fracPart = strs[1];

    result = new Rational<T>();
    try
    {
        string periodPart;
        bool periodFound = false;

        int i;
        for (i = 0; i < fracPart.Length; i++)
        {
            if (fracPart[i] == '0' && i != 0)
                continue;

            for (int j = i + 1; j < fracPart.Length; j++)
            {
                periodPart = fracPart.Substring(i, j - i);
                periodFound = true;
                decimal periodRepeat = 1;
                decimal periodStep = 1.0m / periodPart.Length;
                var upperBound = Math.Min(fracPart.Length, decimalPlaces);
                int k;
                for (k = i + periodPart.Length; k < upperBound; k += 1)
                {
                    if (periodPart[(k - i) % periodPart.Length] != fracPart[k])
                    {
                        periodFound = false;
                        break;
                    }
                    periodRepeat += periodStep;
                }

                if (!periodFound && upperBound - k <= periodPart.Length && periodPart[(upperBound - i) % periodPart.Length] > '5')
                {
                    var ind = (k - i) % periodPart.Length;
                    var regroupedPeriod = (periodPart.Substring(ind) + periodPart.Remove(ind)).Substring(0, upperBound - k);
                    ulong periodTailPlusOne = ulong.Parse(regroupedPeriod) + 1;
                    ulong fracTail = ulong.Parse(fracPart.Substring(k, regroupedPeriod.Length));
                    if (periodTailPlusOne == fracTail)
                        periodFound = true;
                }

                if (periodFound && periodRepeat >= minPeriodRepeat)
                {
                    result = FromDecimal(strs[0], fracPart.Substring(0, i), periodPart);
                    break;
                }
                else
                    periodFound = false;
            }

            if (periodFound)
                break;
        }

        if (!periodFound)
        {
            if (fracPartTrimEnd.Length >= digitsForReal)
                return false;
            else
            {
                result = new Rational<T>(long.Parse(strs[0]), 1, false);
                if (fracPartTrimEnd.Length != 0)
                    result = new Rational<T>(ulong.Parse(fracPartTrimEnd), TenInPower(fracPartTrimEnd.Length));
                return true;
            }
        }

        return true;
    }
    catch
    {
        return false;
    }
}

public static Rational<T> FromDecimal(string intPart, string fracPart, string periodPart)
{
    Rational<T> firstFracPart;
    if (fracPart != null && fracPart.Length != 0)
    {
        ulong denominator = TenInPower(fracPart.Length);
        firstFracPart = new Rational<T>(ulong.Parse(fracPart), denominator);
    }
    else
        firstFracPart = new Rational<T>(0, 1, false);

    Rational<T> secondFracPart;
    if (periodPart != null && periodPart.Length != 0)
        secondFracPart =
            new Rational<T>(ulong.Parse(periodPart), TenInPower(fracPart.Length)) *
            new Rational<T>(1, Nines((ulong)periodPart.Length), false);
    else
        secondFracPart = new Rational<T>(0, 1, false);

    var result = firstFracPart + secondFracPart;
    if (intPart != null && intPart.Length != 0)
    {
        long intPartLong = long.Parse(intPart);
        result = new Rational<T>(intPartLong, 1, false) + (intPartLong == 0 ? 1 : Math.Sign(intPartLong)) * result;
    }

    return result;
}

private static ulong TenInPower(int power)
{
    ulong result = 1;
    for (int l = 0; l < power; l++)
        result *= 10;
    return result;
}

private static decimal TenInNegPower(int power)
{
    decimal result = 1;
    for (int l = 0; l > power; l--)
        result /= 10.0m;
    return result;
}

private static ulong Nines(ulong power)
{
    ulong result = 9;
    if (power >= 0)
        for (ulong l = 0; l < power - 1; l++)
            result = result * 10 + 9;
    return result;
}

Il existe quelques exemples d'utilisations:

Rational<long>.FromDecimal(0.33333333m, out r, 8, false);
// then r == 1 / 3;

Rational<long>.FromDecimal(0.33333333m, out r, 9, false);
// then r == 33333333 / 100000000;

Votre valise avec coupe droite zéro pièce:

Rational<long>.FromDecimal(0.33m, out r, 28, true);
// then r == 1 / 3;

Rational<long>.FromDecimal(0.33m, out r, 28, true);
// then r == 33 / 100;

Démonstration de la période minimale:

Rational<long>.FromDecimal(0.123412m, out r, 28, true, 1.5m));
// then r == 1234 / 9999;
Rational<long>.FromDecimal(0.123412m, out r, 28, true, 1.6m));
// then r == 123412 / 1000000; because of minimu repeating of period is 0.1234123 in this case.

Arrondi à la fin:

Rational<long>.FromDecimal(0.8888888888888888888888888889m, out r));
// then r == 8 == 9;

Le cas le plus intéressant:

Rational<long>.FromDecimal(0.12345678m, out r, 28, true, 2, 9);
// then r == 12345678 / 100000000;

Rational<long>.FromDecimal(0.12345678m, out r, 28, true, 2, 8);
// Conversation failed, because of period has not been founded and there are too many digits in fraction part of input value.

Rational<long>.FromDecimal(0.12121212121212121m, out r, 28, true, 2, 9));
// then r == 4 / 33; Despite of too many digits in input value, period has been founded. Thus it's possible to convert value to fraction.

D'autres tests et code que tout le monde peut trouver dans ma bibliothèque MathFunctions sur github .


2

Ruby a déjà une solution intégrée:

0.33.rationalize.to_s # => "33/100"
0.4.rationalize.to_s # => "2/5"

Dans Rails, les attributs numériques ActiveRecord peuvent également être convertis:

product.size = 0.33
product.size.to_r.to_s # => "33/100"

2

Répondez en C ++, en supposant que vous avez une classe «BigInt», qui peut stocker des entiers de taille illimitée.

Vous pouvez utiliser «unsigned long long» à la place, mais cela ne fonctionnera que pour certaines valeurs.

void GetRational(double val)
{
    if (val == val+1) // Inf
        throw "Infinite Value";
    if (val != val) // NaN
        throw "Undefined Value";

    bool sign = false;
    BigInt enumerator = 0;
    BigInt denominator = 1;

    if (val < 0)
    {
        val = -val;
        sign = true;
    }

    while (val > 0)
    {
        unsigned int intVal = (unsigned int)val;
        val -= intVal;
        enumerator += intVal;
        val *= 2;
        enumerator *= 2;
        denominator *= 2;
    }

    BigInt gcd = GCD(enumerator,denominator);
    enumerator /= gcd;
    denominator /= gcd;

    Print(sign? "-":"+");
    Print(enumerator);
    Print("/");
    Print(denominator);

    // Or simply return {sign,enumerator,denominator} as you wish
}

BTW, GetRational (0.0) renverra "+0/1", donc vous voudrez peut-être gérer ce cas séparément.

PS: J'utilise ce code dans ma propre classe 'RationalNum' depuis plusieurs années, et il a été testé à fond.


Votre exemple semble se décomposer sur des valeurs telles que 1.333333 .. il entre dans une très longue boucle en essayant de trouver la valeur et ne semble pas fonctionner ... fonctionne bien avec d'autres valeurs simples telles que 1.25
Adamski

@Adamski: Merci. La période de «convergence» de la whileboucle est limitée par la taille de double, qui est généralement de 64 bits. Cela ne dépend donc pas de la valeur initiale de input ( val). La GCDfonction, cependant, dépend de cette valeur, bien qu'elle converge généralement vers une solution assez rapidement. Est-il possible que vous n'ayez pas implémenté correctement cette fonction?
barak manos

@Adamski: De plus, comme je l'ai mentionné au début de la réponse, si vous utilisez à la unsigned long longplace de BigInt, cela ne donnera pas nécessairement le résultat correct pour chaque valeur d'entrée ... Mais même dans ce scénario, le code n'est pas censé "entrer dans une très longue boucle".
barak manos

Ah ok oui, c'est tout à fait possible, la fonction GCD que j'utilisais fait partie de la classe BigInteger de la bibliothèque Juce. Merci pour l'information!
Adamski

@Adamski: Cela n'a donc pas de sens que leGCD fonction ne soit pas implémentée correctement. Avez-vous vérifié si le code s'exécute pendant une longue période pendant whileou après la boucle? Je vais vérifier la valeur de 1.33333, pour voir ce qui se cache derrière cela. Merci.
barak manos

2

Cet algorithme d' Ian Richards / John Kennedy renvoie non seulement de belles fractions, mais il fonctionne également très bien en termes de vitesse. Ceci est le code C # tiré de cette réponse par moi.

Il peut tout gérer double valeurs à l'exception des valeurs spéciales comme NaN et +/- infinity, que vous devrez ajouter si nécessaire.

Il renvoie un new Fraction(numerator, denominator) . Remplacez par votre propre type.

Pour plus d'exemples de valeurs et une comparaison avec d'autres algorithmes, allez 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);
    }

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

Exemples de valeurs renvoyées par cet algorithme:

Accuracy: 1.0E-3      | Richards                     
Input                 | Result           Error       
======================| =============================
   3                  |       3/1          0         
   0.999999           |       1/1         1.0E-6     
   1.000001           |       1/1        -1.0E-6     
   0.50 (1/2)         |       1/2          0         
   0.33... (1/3)      |       1/3          0         
   0.67... (2/3)      |       2/3          0         
   0.25 (1/4)         |       1/4          0         
   0.11... (1/9)      |       1/9          0         
   0.09... (1/11)     |       1/11         0         
   0.62... (307/499)  |       8/13        2.5E-4     
   0.14... (33/229)   |      16/111       2.7E-4     
   0.05... (33/683)   |      10/207      -1.5E-4     
   0.18... (100/541)  |      17/92       -3.3E-4     
   0.06... (33/541)   |       5/82       -3.7E-4     
   0.1                |       1/10         0         
   0.2                |       1/5          0         
   0.3                |       3/10         0         
   0.4                |       2/5          0         
   0.5                |       1/2          0         
   0.6                |       3/5          0         
   0.7                |       7/10         0         
   0.8                |       4/5          0         
   0.9                |       9/10         0         
   0.01               |       1/100        0         
   0.001              |       1/1000       0         
   0.0001             |       1/10000      0         
   0.33333333333      |       1/3         1.0E-11    
   0.333              |     333/1000       0         
   0.7777             |       7/9         1.0E-4     
   0.11               |      10/91       -1.0E-3     
   0.1111             |       1/9         1.0E-4     
   3.14               |      22/7         9.1E-4     
   3.14... (pi)       |      22/7         4.0E-4     
   2.72... (e)        |      87/32        1.7E-4     
   0.7454545454545    |      38/51       -4.8E-4     
   0.01024801004      |       2/195       8.2E-4     
   0.99011            |     100/101      -1.1E-5     
   0.26... (5/19)     |       5/19         0         
   0.61... (37/61)    |      17/28        9.7E-4     
                      | 
Accuracy: 1.0E-4      | Richards                     
Input                 | Result           Error       
======================| =============================
   0.62... (307/499)  |     299/486      -6.7E-6     
   0.05... (33/683)   |      23/476       6.4E-5     
   0.06... (33/541)   |      33/541        0         
   1E-05              |       1/99999     1.0E-5     
   0.7777             |    1109/1426     -1.8E-7     
   3.14... (pi)       |     333/106      -2.6E-5     
   2.72... (e)        |     193/71        1.0E-5     
   0.61... (37/61)    |      37/61         0         

1

Vous allez avoir deux problèmes de base qui rendront cela difficile:

1) La virgule flottante n'est pas une représentation exacte, ce qui signifie que si vous avez une fraction de "x / y" qui aboutit à une valeur de "z", votre algorithme de fraction peut renvoyer un résultat autre que "x / y".

2) Il existe une infinité de nombres plus irrationnels que rationnels. Un nombre rationnel est celui qui peut être représenté sous forme de fraction. Irrationnel étant ceux qui ne le peuvent pas.

Cependant, d'une manière peu coûteuse, puisque la virgule flottante a une précision limite, vous pouvez toujours la représenter comme une forme de faction. (Je pense...)


4
Un float (ou double) est une fraction. Son dénominateur est une puissance de 2. C'est pourquoi ils ne peuvent pas représenter exactement des nombres rationnels.
erickson

1

A complété le code ci-dessus et l'a converti en as3

public static function toFrac(f:Number) : String
    {
        if (f>1)
        {
            var parte1:int;
            var parte2:Number;
            var resultado:String;
            var loc:int = String(f).indexOf(".");
            parte2 = Number(String(f).slice(loc, String(f).length));
            parte1 = int(String(f).slice(0,loc));
            resultado = toFrac(parte2);
            parte1 *= int(resultado.slice(resultado.indexOf("/") + 1, resultado.length)) + int(resultado.slice(0, resultado.indexOf("/")));
            resultado = String(parte1) +  resultado.slice(resultado.indexOf("/"), resultado.length)
            return resultado;
        }
        if( f < 0.47 )
            if( f < 0.25 )
                if( f < 0.16 )
                    if( f < 0.13 )
                        if( f < 0.11 )
                            return "1/10";
                        else
                            return "1/9";
                    else
                        if( f < 0.14 )
                            return "1/8";
                        else
                            return "1/7";
                else
                    if( f < 0.19 )
                        return "1/6";
                    else
                        if( f < 0.22 )
                            return "1/5";
                        else
                            return "2/9";
            else
                if( f < 0.38 )
                    if( f < 0.29 )
                        return "1/4";
                    else
                        if( f < 0.31 )
                            return "2/7";
                        else
                            return "1/3";
                else
                    if( f < 0.43 )
                        if( f < 0.40 )
                            return "3/8";
                        else
                            return "2/5";
                    else
                        if( f < 0.44 )
                            return "3/7";
                        else
                            return "4/9";
        else
            if( f < 0.71 )
                if( f < 0.60 )
                    if( f < 0.56 )
                        return "1/2";
                    else
                        if( f < 0.57 )
                            return "5/9";
                        else
                            return "4/7";
                else
                    if( f < 0.63 )
                        return "3/5";
                    else
                        if( f < 0.66 )
                            return "5/8";
                        else
                            return "2/3";
            else
                if( f < 0.80 )
                    if( f < 0.74 )
                        return "5/7";
                    else
                        if(f < 0.78 )
                            return "3/4";
                        else
                            return "7/9";
                else
                    if( f < 0.86 )
                        if( f < 0.83 )
                            return "4/5";
                        else
                            return "5/6";
                    else
                        if( f < 0.88 )
                            return "6/7";
                        else
                            if( f < 0.89 )
                                return "7/8";
                            else
                                if( f < 0.90 )
                                    return "8/9";
                                else
                                    return "9/10";
    }

Merci, j'ai utilisé ceci pour Delphi, plus facile à porter que tout ce truc bouclé
Peter Turner

1

Voici une implémentation rapide et sale en javascript qui utilise une approche de force brute. Pas du tout optimisé, il fonctionne dans une plage de fractions prédéfinie: http://jsfiddle.net/PdL23/1/

/* This should convert any decimals to a simplified fraction within the range specified by the two for loops. Haven't done any thorough testing, but it seems to work fine.

I have set the bounds for numerator and denominator to 20, 20... but you can increase this if you want in the two for loops.

Disclaimer: Its not at all optimized. (Feel free to create an improved version.)
*/

decimalToSimplifiedFraction = function(n) {

    for(num = 1; num < 20; num++) {  // "num" is the potential numerator
        for(den = 1; den < 20; den++) {  // "den" is the potential denominator
            var multiplyByInverse = (n * den ) / num;

            var roundingError = Math.round(multiplyByInverse) - multiplyByInverse;

            // Checking if we have found the inverse of the number, 
            if((Math.round(multiplyByInverse) == 1) && (Math.abs(roundingError) < 0.01)) {
                return num + "/" + den;
            }
        }
    }
};

//Put in your test number here.
var floatNumber = 2.56;

alert(floatNumber + " = " + decimalToSimplifiedFraction(floatNumber));

Ceci est inspiré de l'approche utilisée par JPS.


0

Comme beaucoup de gens l'ont dit, vous ne pouvez vraiment pas convertir une virgule flottante en une fraction (à moins que ce soit extrêmement exact comme 0,25). Bien sûr, vous pouvez créer un type de recherche pour un large éventail de fractions et utiliser une sorte de logique floue pour produire le résultat que vous recherchez. Encore une fois, cela ne serait pas exact et vous auriez besoin de définir une limite inférieure de la taille que vous voulez que le dénominateur aille.

.32 <x <.34 = 1/3 ou quelque chose comme ça.



0

Je suis tombé sur une solution Haskell particulièrement élégante utilisant un anamorphisme. Cela dépend du package récursivité-schémas .

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE FlexibleContexts    #-}

import           Control.Applicative   (liftA2)
import           Control.Monad         (ap)
import           Data.Functor.Foldable
import           Data.Ratio            (Ratio, (%))

isInteger :: (RealFrac a) => a -> Bool
isInteger = ((==) <*>) (realToFrac . floor)

continuedFraction :: (RealFrac a) => a -> [Int]
continuedFraction = liftA2 (:) floor (ana coalgebra)
    where coalgebra x
              | isInteger x = Nil
              | otherwise = Cons (floor alpha) alpha
                  where alpha = 1 / (x - realToFrac (floor x))

collapseFraction :: (Integral a) => [Int] -> Ratio a
collapseFraction [x]    = fromIntegral x % 1
collapseFraction (x:xs) = (fromIntegral x % 1) + 1 / collapseFraction xs

-- | Use the nth convergent to approximate x
approximate :: (RealFrac a, Integral b) => a -> Int -> Ratio b
approximate x n = collapseFraction $ take n (continuedFraction x)

Si vous essayez ceci dans ghci, cela fonctionne vraiment!

λ:> approximate pi 2
22 % 7
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.