[Swift 3] Double - Précision pas vraiment précise ..
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
Avec le type Float, l'anomalie se manifeste différemment :
a
Et la valeur 6 n'est toujours pas détectée ??? !
a
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:
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.
Comme dit ifou, pour afficher les nombres Double/Float et les comparer, il faut, au moins, qqch. comme ci :
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.
Représentation binaire de quelques valeurs :
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.
Salut,
Un lien assez sympa pour comprendre que les nombres à virgules et l'informatique ça fait deux (pas trop compliqué et en français ) :
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.
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.
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...
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.