Math et Python : histoire d’un bug – épisode 1

J’ai développé en Python, il y a un moment déjà un petit utilitaire qui produit des exercices de trigonométrie (https://site2wouf.fr/exercices_trigonometrie.php) qui les corrige et qui fabrique par la même occasion un pdf avec exercices et corrections.

Oui mais voilà, en récoltant les fruits de mon travail en classe aujourd’hui je tombe sur cet exercice et sa correction et remarque une erreur :

Exercice 1

Dans le triangle RBN rectangle en R, on sait que :

  • BN = 3,7 cm
  • BNR = 30°

Après avoir fait un schéma, calcule la longueur du segment [RB]. (Arrondir au dixième)

Correction

Exercice 1

image/svg+xml R B N ? 3,7 cm 30°

Dans le triangle RBN rectangle en R, on cherche une relation entre l’angle aigu RNB son coté opposé et l’hypoténuse du triangle.

RB / BN

= sin(RNB)

d’où

RB / 3,7

= sin(30°)

On a donc RB = 3,7 × sin(30°) ≈ 1.8 cm

Voyez-vous l’erreur ?

sin(30°)=0.5

3.7sin(30°)=1.85

Python aurait du arrondir à 1,9cm

Lu sur la doc Python :

https://docs.python.org/fr/3.7/library/functions.html?highlight=round#round

Le comportement de round() avec les nombres à virgule flottante peut être surprenant : par exemple round(2.675, 2) donne 2.67 au lieu de 2.68. Ce n’est pas un bug, mais dû au fait que la plupart des fractions de décimaux ne peuvent pas être représentés exactement en nombre a virgule flottante. Voir Arithmétique en nombres à virgule flottante : problèmes et limites pour plus d’information.

Pour Python, avec le module math importé :

>>> sin((radians(30)))
0.49999999999999994

donc en multipliant par 3.7, Python trouvera un résultat très proche de 1,85 mais strictement inférieur donc dont le développement décimal commencera par 1,84… et il arrondira effectivement à 1,8.

En arrondissant (round(sin((radians(30)),5) j’évite le souci mais ne vais-je pas tombé parfois sur le comportement évoqué de round() ?

Mes solutions :

Ma fonction round2

Elle est basée sur l’écriture décimale en tant que chaine de caractère.

def round2(x,nbdigit=0):
    """x est un nombre, la fonction renvoie l'arrondi à nbdigit près
       sans riquer des erreurs d'arrondi comme avec la fonction native   
    
    """
    if not isinstance(x, (int,float)):
        raise NameError("x must be int or float")
        return
    if not isinstance(nbdigit,int):
        raise NameError("nbdigit must be integer")
        return
    if nbdigit<0 :
        raise NameError("error nbdigit <0 ")
        return
    if x<0:
        return -round2(-x)
    #x est un nombre positif, et nbdigit un entier positif
    partie1=str(x).split(".")[0][:-1]
    partie2=str(x).split(".")[0][-1]
    try:
        partie2+=str(x).split(".")[1]
    except:
        pass
    pcorrige=""
    n=0
    while n<=nbdigit:
        try:
            pcorrige+=partie2[n]
            
        except:
            pass
        n+=1
    try:
        suivant=int(partie2[n])
    except:
        suivant =0
    p=partie1+pcorrige[0]
    if len (pcorrige)>1:
            p+="."+pcorrige[1:]
  
    nb=[x for x in p]
    if suivant>4:
        nb=["0"]+nb
        ok=False
        x=len(nb)
        while not ok:
            x-=1
            if nb[x]!=".":
                if int(nb[x])<9:
                    nb[x]=str(int(nb[x])+1)
                    ok=True
                else:
                    nb[x]="0"
    nb=float("".join(nb))
    if nb==int(nb):
        nb=int(nb)
    return nb

On peut comparer avec la fonction native :

round2(2.675,2)
2.68
round(2.675,2)
2.67

Pour le souci avec sinus, j’arrondi simplement (à 15 digits) les retours des lignes trigonométriques.

Un problème subsiste néanmoins : On pourrait parfois (avec sin(30), cos(60) et tan(45) par exemple ) travailler avec des valeurs exactes, et remplacer le signe “environ égal” par un “vrai égal”…

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *