[Swift 3] Double - Précision pas vraiment précise ..

DrakenDraken Membre

Je viens de tomber sur quelque chose qui m'étonne un peu, en incrémentant des valeurs de 0.1, pour faire un chronomètre.



class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

var chrono:Double = 0.0
for _ in 0...400 {
chrono += 0.1
print (chrono)
}
}
}



 


0.1


0.2


0.3


0.4


0.5


... (j'ai zappé les chiffres sans intéret)


5.7


5.8


5.9


5.99999999999999


6.09999999999999


6.19999999999999


6.29999999999999


6.39999999999999


6.49999999999999


6.59999999999999


6.69999999999999


6.79999999999999


6.89999999999999


6.99999999999999


7.09999999999999


7.19999999999999


7.29999999999999


7.39999999999999


7.49999999999999


7.59999999999999


7.69999999999999


7.79999999999999


7.89999999999999


7.99999999999999


8.09999999999999


8.19999999999999


8.29999999999999


8.39999999999999


8.49999999999999


8.59999999999999


8.69999999999999


8.79999999999998


8.89999999999998


8.99999999999998


9.09999999999998


9.19999999999998


9.29999999999998


9.39999999999998


9.49999999999998


9.59999999999998


9.69999999999998


9.79999999999998


9.89999999999998


9.99999999999998


10.1


10.2


10.3


10.4


 


 


Les valeurs sont légérement erronées entre 6 et 10.


 


Je me suis dit que c'était peut-être une erreur d'affichage, mais non. Le chiffre 6 n'est jamais détecté, par exemple.



class ViewController: UIViewController {


override func viewDidLoad() {
super.viewDidLoad()

var chrono:Double = 0.0
for _ in 0...400 {
chrono += 0.1
if chrono == 6.0 {
print ("VALEUR 6.0 ICI !")
}
print (chrono)
}
}

}


Le 4 non plus d'ailleurs, alors qu'il semble être affiché correctement.


 


 


Je sais que le type Double est une valeur arrondie, mais sa précision est censé être de 15 chiffres significatifs. Bref, j'ai comme un problème de compréhension.


 

 


 


 


 


 


 


Réponses

  • DrakenDraken Membre
    mai 2017 modifié #2

    Avec le type Float, l'anomalie se manifeste différemment :



    override func viewDidLoad() {
    super.viewDidLoad()

    var chrono:Float = 0.0
    for _ in 0...400 {
    chrono += 0.1

    if chrono == 6.0 {
    print ("VALEUR 6 ICI !")
    }
    print (chrono)
    }
    }

    }


    a


    Et la valeur 6 n'est toujours pas détectée  ??? !


     



     


    5.8


    5.9


    6.0


    6.1


    6.2


    6.3


    6.4


    6.5


    6.6


    6.7


    6.8


    6.9


    7.0


    7.1


    7.2


    7.3


    7.4


    7.5


    7.6


    7.7


    7.79999


    7.89999


    7.99999


    8.09999


    8.2


    8.3


    8.4


     


    a



     


    22.4


    22.5


    22.6


    22.7001


    22.8001


    22.9001


    23.0001


    23.1001


    23.2001


    23.3001


    23.4001


    23.5001


    23.6001


    23.7001


    23.8001


    23.9001


    24.0001


    24.1001


    24.2001


    24.3001


    24.4001


    24.5001


    24.6001


    24.7001


    24.8001


    24.9001


    25.0001


    25.1001


    25.2001


    25.3001


    25.4001


    25.5001


    25.6001


    25.7001


    25.8001


    25.9001


    26.0001


    26.1001


    26.2001


    26.3001


    26.4001


    26.5001


    26.6001


    26.7001


    26.8001


    26.9001


    27.0001


    27.1001


    27.2001


    27.3001


    27.4001


    27.5001


    27.6001


    27.7001


    27.8001


    27.9001


    28.0001


    28.1001


    28.2001


    28.3001


    28.4001


    28.5001


    28.6001


    28.7001


    28.8001


    28.9001


    29.0001


    29.1001


    29.2001


    29.3001


    29.4001


    29.5001


    29.6001


    29.7001


    29.8001


    29.9001


    30.0001


    30.1001


    30.2001


    30.3001


    30.4001


    30.5001


    30.6001


    30.7001


    30.8001


    30.9001


    31.0001


    31.1001


    31.2001


    31.3001


    31.4001


    31.5001


    31.6001


    31.7001


    31.8001


    31.9001


    32.0001


    32.1001


    32.2001


    32.3001


    32.4001


    32.5001


    32.6001


    32.7001


    32.8001


    32.9001


    33.0001


    33.1001


    33.2001


    33.3001


    33.4001


    33.5001


    33.6001


    33.7001


    33.8001


    33.9001


    34.0001


    34.1001


    34.2001


    34.3


    34.4


    34.5



    a

    EDIT : les résultats sont identiques sur Simulateur et device (iPod Touch iOS 9.3)

    a

  • ifouifou Membre

    Hello Draken.


     


    J'ai déjà  vu ce genre de problématique par le passé dans des boulots précédents. C'est un problème très connu et "systématique" avec les double et les float. Il y a un papier connu qu'il faut lire pour comprendre ce qu'il se passe: "What every computer scientist should know about floating-point arithmetic.". Tu peux le télécharger sur Google, car il me semble qu'il est libre de droit. Je ne l'ai jamais lu car il est assez compliqué à  suivre. Mais peut-être le connaissais-tu déjà ?


     


    Dans mes boulots précédents, on essayer d'abord d'éviter tout ce qui est double/float pour représenter des données. Quand on en avait la liberté, on représentait les nombres sous la forme d'entier. Et le nombre de chiffre après la virgule était "implicite". Par exemple un prix, de 104,03€ était représenté par un entier: var price: Int = 10403. Dans cet exemple le nombre de deux chiffres après la virgule est implicite.


     


    Autrement quand une variable double/float est imposé, les comparaisons se font de cette manière: 



    bool AreSame(double a, double b)
    {
    return fabs(a - b) < EPSILON;
    }

    Ce code fait la différence entre les deux valeurs à  comparer, en prends la valeur absolue, puis vérifie que cette valeur est négligeable. Comment vérifie-t-on que cette valeur est négligeable? On le vérifie en comparant à  une valeur très petite. Par exemple dans l'exemple ci-dessus, la valeur EPSILON pourrait être égale à  0.001. C'est une heuristique.


     


    J'ai tendance à  être plus confiant avec la première méthode: utiliser exclusivement des entiers. 


     


    Voilà  ce que je peux ajouter sur le sujet. En espérant que cela aide in-fine. :)

  • Joanna CarterJoanna Carter Membre, Modérateur
    mai 2017 modifié #4

    Comme dit ifou, pour afficher les nombres Double/Float et les comparer, il faut, au moins, qqch. comme ci :



    var chrono = 0.0

    for _ in 0..<400
    {
    chrono += 0.1

    if fabs(chrono.distance(to: 6.0)) < 0.01
    {
    print("VALEUR 6.0 ICI !")
    }

    print(String(format: "%.1f", chrono))
    }

    Mais, pour l'affichage des nombres selon la région de l'utilisateur, il faut utiliser un NumberFormatter avec un locale de Locale.autoupdatingCurrent pour avoir les bons séparateurs.


  • Joanna CarterJoanna Carter Membre, Modérateur
    mai 2017 modifié #5

    Représentation binaire de quelques valeurs :



    0,1 - 0.000110011001100110011001100110011001100110011001100110011001 = 0,09999999999999999947
    0,2 - 0.001100110011001100110011001100110011001100110011001100110011 = 0,19999999999999999982
    0,3 - 0.010011001100110011001100110011001100110011001100110011001100 = 0,2999999999999999993
    0,4 - 0.011001100110011001100110011001100110011001100110011001100110 = 0,39999999999999999965
    0,5 - 0.1 = 0,5
    0,6 - 0.100110011001100110011001100110011001100110011001100110011001 = 0,59999999999999999947
    0,7 - 0.101100110011001100110011001100110011001100110011001100110011 = 0,69999999999999999982
    0,8 - 0.110011001100110011001100110011001100110011001100110011001100 = 0,7999999999999999993
    0,9 - 0.111001100110011001100110011001100110011001100110011001100110 = 0,89999999999999999965
    1,0 - 1.0 = 1,0

    Seulement les nombres décimaux qui peuvent être précisément divisés par 2 seront représentés précisément en binaire.


     


    Et, si on ajoutais 0,1 plusieurs fois, les erreurs pourrait accumuler.


     


    De mon avis, il vaut mieux de faire les incréments en nombres entiers, puis les diviser par 10 au moment d'affichage, pour éviter les erreurs cumulatives.


  • CirdoCirdo Membre

    Salut,


     


    Un lien assez sympa pour comprendre que les nombres à  virgules et l'informatique ça fait deux (pas trop compliqué et en français :D ) :


    http://www.positron-libre.com/cours/electronique/systeme-numeration/nombre-virgule-flottante.php


     


    Du coup comme l'a très bien dit ifou ou Joanna, les int sont une bonne solution.

  • DrakenDraken Membre

    Merci à  tous.


     


    Oui, les Int sont la meilleure solution. J'étais arrivé à  la même conclusion.


    Je savais que les Flottants pouvaient poser des problèmes, mais je m'étais imaginé que cela n'arrivait que dans des cas particuliers, à  la limite du nombre de chiffre significatifs. Vous me rassurez. J'étais vraiment surpris ce matin quand un simple compteur, sensé afficher les 1/10 de seconde dans un coin de l'écran, s'est mis à  me balancer des chiffres à  rallonges. 

  • CéroceCéroce Membre, Modérateur
    mai 2017 modifié #8
    La réalité de l'informatique revient à  intervalle régulier: non la puissance de traitement et le stockage ne sont pas infinis, et oui, il faut encore savoir comment ça fonctionne sous le capot.

    Tu t'en rends particulièrement compte quand tu travailles sur des tâches qui "poussent" les machines: 3D, gros volumes de données, recherche opérationnelle, intelligence artificielle...
  • DrakenDraken Membre

    On ne peut vraiment dire qu'un compteur affichant des 1/10° de secondes, soit une tâche très intense.. Mais je comprend ce que tu veux dire.

Connectez-vous ou Inscrivez-vous pour répondre.