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?
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)
(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 ...
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.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 et Richards ont comparé:
Si vous n'avez pas besoin de la fraction de plus petit dénominateur, Richards est un bon choix.
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 )
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
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()
);
}
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:
x
n
(10^n * x) / 10^n
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
11 / 25
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;
}
}
}
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);
}
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 int
s 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);
}
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.
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); }
}
Je suppose que cette réponse fait référence au même algorithme. Je n'avais pas vu ça avant ...
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:
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 :)
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
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.)
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;
}
}
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:
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.
J'ai effectué des tests de vitesse sur mon ordinateur portable avec les algorithmes suivants:
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.
Le code source ci-dessous contient tous les algorithmes utilisés. Il comprend:
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);
}
}
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.
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))
}
}
}
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);
}
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.
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.
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.
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();
}
}