2010-11-24 23 views
7

Je voudrais savoir un bon moyen de vérifier si un nombre x est rationnel (deux entiers n, m existent pour que x = n/m) en python.Vérifie si un nombre est rationnel en Python

Dans Mathematica, cela se fait par la fonction

Rationalize[6.75] 
27/4 

Je suppose que cette question a une réponse pour une précision donnée. Existe-t-il un algorithme commun d'obtention de ces deux entiers?

Répondre

13

en Python> = 2.6 il existe une méthode as_integer_ratio sur flotteurs:

>>> a = 6.75 
>>> a.as_integer_ratio() 
(27, 4) 
>>> import math 
>>> math.pi.as_integer_ratio() 
(884279719003555, 281474976710656) 

Cependant, en raison de la manière flotteurs sont définis dans les langages de programmation il n'y a pas des nombres irrationnels.

+0

Les irrationnels ne peuvent pas non plus être représentés par le ratio (nal) s - que la définition même et la source du nom! – delnan

+0

@delnan, FWIW, ils peuvent être représentés comme rationnels. Les deux méthodes couramment utilisées pour le faire formellement (coupes de Dedekind et séries de Cauchy) utilisent des rationnels pour le faire. Ils utilisent juste une quantité infinie de rationnels :) – aaronasterling

+0

Pour un vrai plaisir, cherchez des fractions continues. Pour mettre en œuvre une solution vous-même, vous faites la fraction continue jusqu'à ce qu'il en résulte une fraction exacte ou le dénominateur devient trop grand à votre goût. – phkahler

5

Un nombre quelconque avec une décimale finie est un nombre rationnel. Vous pouvez toujours résoudre par exemple

5.195181354985216 

en disant qu'il correspond à

5195181354985216/1000000000000000 

Ainsi, depuis chars et doubles ont une précision finie, ils sont tous rationals.

+1

Notez "donné la précision" dans la question d'origine. –

1

Comme vous l'avez noté tout nombre à virgule flottante peut être converti en un nombre rationnel en déplaçant le point décimal et en divisant par la puissance appropriée de dix.

Vous pouvez ensuite supprimer le plus grand diviseur commun du dividende et du diviseur et vérifier si ces deux nombres correspondent au type de données de votre choix.

+0

FWIW, vous pouvez déterminer facilement le plus grand commun diviseur en Python en utilisant 'fractions.gcd()'. – martineau

4

Python utilise une représentation en virgule flottante plutôt que des nombres rationnels. Jetez un oeil à the standard library fractions module pour quelques détails sur les nombres rationnels.

Observer, par exemple, cela, pour voir pourquoi il ne va pas:

>>> from fractions import Fraction 
>>> 1.1 # Uh oh. 
1.1000000000000001 
>>> Fraction(1.1) # Will only work in >= Python 2.7, anyway. 
Fraction(2476979795053773, 2251799813685248) 
>>> Fraction(*1.1.as_integer_ratio()) # Python 2.6 compatible 
Fraction(2476979795053773, 2251799813685248) 

(Oh, vous voulez voir un cas où cela fonctionne?)

>>> Fraction('1.1') 
Fraction(11, 10) 
+0

Y at-il des langages de programmation qui "utilisent" des nombres rationnels? – SilentGhost

+2

@SilentGhost: ABC le fait. Voir les expériences de Guido et pourquoi Python ne le fait pas: http://python-history.blogspot.com/2009/03/problem-with-integer-division.html –

+0

@SilentGhost, Common Lisp et (je crois) Scheme faire aussi bien. – aaronasterling

0

Le problème réel Dans les langages de programmation, les nombres sont généralement définis comme des fonctions renvoyant une représentation finie avec une précision (par exemple, une fonction qui prend n comme argument et renvoie un nombre à virgule flottante avec une précision de 2^-n).

Vous pouvez définitivement transformer un rationnel/entier en un réel, mais même comparer des réels pour l'égalité est indécidable (c'est essentiellement le problème d'arrêt).

Vous ne pouvez pas dire si un nombre réel x est rationnel: même en mathématiques, c'est généralement difficile, car vous devez trouver p et q tels que x = p/q, ce qui est souvent non constructif. Cependant, à l'aide d'une fenêtre de précision, vous pouvez trouver la «meilleure» approximation rationnelle pour cette précision en utilisant par exemple une expansion continue de la fraction. Je crois que c'est essentiellement ce que fait Mathematica. Mais dans votre exemple, 6,75 est déjà rationnel. Essayez avec Pi à la place.

8

La nature des nombres à virgule flottante signifie qu'il n'a pas de sens de vérifier si un nombre à virgule flottante est rationnel, puisque tous les nombres à virgule flottante sont vraiment des fractions de la forme n/2 e. Cependant, vous pourriez vouloir savoir s'il y a une fraction simple (une avec un petit dénominateur plutôt qu'une grande puissance de 2) qui se rapproche étroitement d'un nombre à virgule flottante donné.

Donald Knuth traite de ce dernier problème dans L'art de la programmation par ordinateur volume II. Voir la réponse à l'exercice 4.53-39. L'idée est de rechercher la fraction ayant le plus petit dénominateur dans une fourchette, en élargissant les extrémités de la fourchette en tant que fractions continues tant que leurs coefficients sont égaux, puis, lorsqu'elles diffèrent, de prendre la valeur la plus simple entre elles. Voici une implémentation assez simple en Python:

from fractions import Fraction 
from math import modf 

def simplest_fraction_in_interval(x, y): 
    """Return the fraction with the lowest denominator in [x,y].""" 
    if x == y: 
     # The algorithm will not terminate if x and y are equal. 
     raise ValueError("Equal arguments.") 
    elif x < 0 and y < 0: 
     # Handle negative arguments by solving positive case and negating. 
     return -simplest_fraction_in_interval(-y, -x) 
    elif x <= 0 or y <= 0: 
     # One argument is 0, or arguments are on opposite sides of 0, so 
     # the simplest fraction in interval is 0 exactly. 
     return Fraction(0) 
    else: 
     # Remainder and Coefficient of continued fractions for x and y. 
     xr, xc = modf(1/x); 
     yr, yc = modf(1/y); 
     if xc < yc: 
      return Fraction(1, int(xc) + 1) 
     elif yc < xc: 
      return Fraction(1, int(yc) + 1) 
     else: 
      return 1/(int(xc) + simplest_fraction_in_interval(xr, yr)) 

def approximate_fraction(x, e): 
    """Return the fraction with the lowest denominator that differs 
    from x by no more than e.""" 
    return simplest_fraction_in_interval(x - e, x + e) 

Et voici quelques résultats:

>>> approximate_fraction(6.75, 0.01) 
Fraction(27, 4) 
>>> approximate_fraction(math.pi, 0.00001) 
Fraction(355, 113) 
>>> approximate_fraction((1 + math.sqrt(5))/2, 0.00001) 
Fraction(377, 233) 
+0

+1 pour fournir l'algorithme. Nitpick: cela devrait être "si x == y: return x" puisque vous semblez considérer l'intervalle fermé [x, y]. –

+0

Je ne fais pas cela parce que 'x' et' y' sont censés être flottants alors que la fonction est censée retourner une 'Fraction'. Je pense qu'il est préférable de faire une erreur dans ce cas. –

+0

Cela aurait du sens de renvoyer 'Fraction (* x.as_integer_ratio())', je suppose. –