Par exemple, je veux afficher une liste de boutons de 0,0,5, ... 5, qui saute pour chaque 0,5. J'utilise une boucle for pour cela, et j'ai une couleur différente au bouton STANDARD_LINE:
var MAX=5.0;
var DIFF=0.5
var STANDARD_LINE=1.5;
for(var i=0;i<=MAX;i=i+DIFF){
button.text=i+'';
if(i==STANDARD_LINE){
button.color='red';
}
}
Dans ce cas, il ne devrait pas y avoir d'erreurs d'arrondi car chaque valeur est exacte dans IEEE 754. Mais je me bats si je dois la changer pour éviter la comparaison d'égalité en virgule flottante:
var MAX=10;
var STANDARD_LINE=3;
for(var i=0;i<=MAX;i++){
button.text=i/2.0+'';
if(i==STANDARD_LINE/2.0){
button.color='red';
}
}
D'une part, le code d'origine est plus simple et me parvient. Mais il y a une chose que j'envisage: est-ce que i == STANDARD_LINE trompe ses coéquipiers juniors? Couvre-t-il le fait que les nombres à virgule flottante peuvent avoir des erreurs d'arrondi? Après avoir lu les commentaires de ce post:
il semble que de nombreux développeurs ne savent pas que certains nombres flottants sont exacts. Dois-je éviter les comparaisons d'égalité de nombres flottants même si elles sont valables dans mon cas? Ou est-ce que j'y réfléchis trop?
i
ne sera que des nombres entiers dans la deuxième liste. Essayez de supprimer le second/2.0
.button
cela ne change nulle part dans votre boucle. Comment accéder à la liste des boutons? Via un index dans un tableau ou un autre mécanisme? Si c'est par accès à un index dans un tableau, c'est un autre argument en faveur du passage aux entiers.Réponses:
J'éviterais toujours les opérations successives en virgule flottante à moins que le modèle que je calcule ne les nécessite. L'arithmétique en virgule flottante n'est pas intuitive pour la plupart et constitue une source majeure d'erreurs. Et distinguer les cas dans lesquels il provoque des erreurs de ceux où il ne l'est pas est une distinction encore plus subtile!
Par conséquent, l'utilisation de flottants comme compteurs de boucles est un défaut qui attend de se produire et nécessiterait au moins un gros commentaire de fond expliquant pourquoi il est correct d'utiliser 0,5 ici, et que cela dépend de la valeur numérique spécifique. À ce stade, la réécriture du code pour éviter les compteurs flottants sera probablement l'option la plus lisible. Et la lisibilité est à côté de l'exactitude dans la hiérarchie des exigences professionnelles.
la source
DIFF must be an exactly-representable double that evenly divides STANDARD_LINE
. Si vous ne voulez pas écrire ce commentaire (et compter sur tous les futurs développeurs pour en savoir suffisamment sur la virgule flottante binaire64 IEEE754 pour le comprendre), alors n'écrivez pas le code de cette façon. c'est-à-dire n'écrivez pas le code de cette façon. Surtout parce qu'il n'est probablement pas encore plus efficace: l'addition FP a une latence plus élevée que l'addition entière, et c'est une dépendance portée par la boucle. De plus, les compilateurs (même les compilateurs JIT?) Font probablement mieux à faire des boucles avec des compteurs entiers.En règle générale, les boucles doivent être écrites de manière à penser à faire quelque chose n fois. Si vous utilisez des indices à virgule flottante, il ne s'agit plus de faire quelque chose n fois mais de courir jusqu'à ce qu'une condition soit remplie. Si cette condition se trouve être très similaire à celle à
i<n
laquelle tant de programmeurs s'attendent, alors le code semble faire une chose alors qu'il en fait une autre qui peut être facilement mal interprétée par les programmeurs en écrémant le code.C'est quelque peu subjectif, mais à mon humble avis, si vous pouvez réécrire une boucle pour utiliser un index entier pour boucler un nombre fixe de fois, vous devriez le faire. Considérez donc l'alternative suivante:
La boucle fonctionne en termes de nombres entiers. Dans ce cas,
i
est un entier etSTANDARD_LINE
est également contraint à un entier. Cela changerait bien sûr la position de votre ligne standard s'il se produisait un arrondi et de même pourMAX
, vous devriez donc vous efforcer d'empêcher l'arrondi pour un rendu précis. Cependant, vous avez toujours l'avantage de changer les paramètres en termes de pixels et non de nombres entiers sans avoir à vous soucier de la comparaison des virgules flottantes.la source
i
etSTANDARD_LINE
ne ressemble donc qu'à des entiers. Il n'y a aucune contrainte du tout, etDIFF
,MAX
et ce neSTANDARD_LINE
sont que desNumber
art.Number
Les s utilisés comme entiers doivent être sûrs ci2**53
- dessous , mais ils sont toujours des nombres à virgule flottante.Je suis d'accord avec toutes les autres réponses selon lesquelles l'utilisation d'une variable de boucle non entière est généralement un mauvais style même dans des cas comme celui-ci où cela fonctionnera correctement. Mais il me semble qu'il y a une autre raison pour laquelle c'est un mauvais style ici.
Votre code "sait" que les largeurs de ligne disponibles sont précisément les multiples de 0,5 de 0 à 5,0. Devrait-il? Il semble que ce soit une décision d'interface utilisateur qui pourrait facilement changer (par exemple, vous voulez peut-être que les écarts entre les largeurs disponibles deviennent plus grands comme le font les largeurs. 0,25, 0,5, 0,75, 1,0, 1,5, 2,0, 2,5, 3,0, 4,0, 5,0 ou quelque chose).
Votre code "sait" que les largeurs de ligne disponibles ont toutes de "belles" représentations à la fois sous forme de nombres à virgule flottante et sous forme de décimales. Cela semble également quelque chose qui pourrait changer. (Vous voudrez peut-être 0,1, 0,2, 0,3, ... à un moment donné.)
Votre code "sait" que le texte à mettre sur les boutons est simplement ce en quoi Javascript transforme ces valeurs à virgule flottante. Cela semble également quelque chose qui pourrait changer. (Par exemple, peut-être qu'un jour vous voudrez des largeurs comme 1/3, que vous ne voudriez probablement pas afficher comme 0.33333333333333 ou autre. Ou peut-être que vous voulez voir "1.0" au lieu de "1" pour la cohérence avec "1.5" .)
Tout cela me semble être les manifestations d'une seule faiblesse, qui est une sorte de mélange de couches. Ces nombres à virgule flottante font partie de la logique interne du logiciel. Le texte affiché sur les boutons fait partie de l'interface utilisateur. Ils devraient être plus séparés que dans le code ici. Des notions telles que "laquelle est la valeur par défaut à mettre en évidence?" sont des questions d'interface utilisateur, et elles ne devraient probablement pas être liées à ces valeurs à virgule flottante. Et votre boucle ici est vraiment (ou du moins devrait être) une boucle sur des boutons , pas sur des largeurs de ligne . Écrit de cette façon, la tentation d'utiliser une variable de boucle prenant des valeurs non entières disparaît: vous utiliseriez simplement des entiers successifs ou une boucle for ... in / for ... of.
Mon sentiment est que la plupart des cas où l'on pourrait être tenté de parcourir des nombres non entiers sont comme ceci: il y a d'autres raisons, totalement indépendantes des problèmes numériques, pour lesquelles le code devrait être organisé différemment. (Pas tous les cas; je peux imaginer que certains algorithmes mathématiques pourraient être exprimés de manière plus nette en termes de boucle sur des valeurs non entières.)
la source
Une odeur de code utilise des flotteurs en boucle comme ça.
Le bouclage peut se faire de nombreuses façons, mais dans 99,9% des cas, vous devriez vous en tenir à un incrément de 1 ou il y aura certainement de la confusion, non seulement par les développeurs juniors.
la source
Oui, vous voulez éviter cela.
Les nombres à virgule flottante sont l'un des plus grands pièges pour le programmeur sans méfiance (ce qui signifie, selon mon expérience, presque tout le monde). De dépendre des tests d'égalité en virgule flottante à représenter l'argent en virgule flottante, tout est un gros bourbier. Ajouter un flotteur sur l'autre est l'un des plus grands contrevenants. Il y a des volumes entiers de littérature scientifique sur des choses comme ça.
Utilisez des nombres à virgule flottante exactement aux endroits où ils sont appropriés, par exemple lorsque vous effectuez des calculs mathématiques réels là où vous en avez besoin (comme la trigonométrie, les graphiques de fonction de traçage, etc.) et soyez très prudent lorsque vous effectuez des opérations en série. L'égalité est au rendez-vous. La connaissance de quel ensemble particulier de nombres est exact selon les normes IEEE est très mystérieuse et je n'en dépendrais jamais.
Dans votre cas, il y aura , par la loi Murphys, le moment où la direction voudra que vous n'ayez pas 0,0, 0,5, 1,0 ... mais 0,0, 0,4, 0,8 ... ou autre; vous vous serez immédiatement borked, et votre programmeur junior (ou vous-même) déboguera longtemps et dur jusqu'à ce que vous trouviez le problème.
Dans votre code particulier, j'aurais en effet une variable de boucle entière. Il représente le
i
bouton e, pas le numéro courant.Et je voudrais probablement, dans un souci de clarté supplémentaire, ne pas écrire
i/2
maisi*0.5
ce qui rend très clair ce qui se passe.Remarque: comme indiqué dans les commentaires, JavaScript n'a pas de type distinct pour les entiers. Mais les entiers jusqu'à 15 chiffres sont garantis pour être précis / sûr (voir https://www.ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer ), donc pour des arguments comme celui-ci ("est-ce plus source de confusion / d’erreurs susceptibles de fonctionner avec des entiers ou des non-entiers "), il est tout à fait approprié d’avoir un type distinct" dans l’esprit "; dans l'utilisation quotidienne (boucles, coordonnées d'écran, indices de tableau, etc.) il n'y aura pas de surprise avec des nombres entiers représentés
Number
comme JavaScript.la source
Je ne pense pas que vos suggestions soient bonnes. Au lieu de cela, j'introduirais une variable pour le nombre de boutons en fonction de la valeur maximale et de l'espacement. Ensuite, il est assez simple de parcourir les index du bouton eux-mêmes.
Il peut s'agir de plus de code, mais il est également plus lisible et plus robuste.
la source
Vous pouvez éviter tout cela en calculant la valeur que vous affichez plutôt qu'en utilisant le compteur de boucle comme valeur:
la source
L'arithmétique à virgule flottante est lente et l'arithmétique à nombres entiers est rapide, donc lorsque j'utilise des virgules flottantes, je ne les utiliserais pas inutilement là où des entiers peuvent être utilisés. Il est utile de toujours considérer les nombres à virgule flottante, même les constantes, comme approximatifs, avec quelques petites erreurs. Il est très utile lors du débogage de remplacer les nombres à virgule flottante natifs par des objets à virgule flottante plus / moins où vous traitez chaque nombre comme une plage au lieu d'un point. De cette façon, vous découvrez des inexactitudes croissantes progressives après chaque opération arithmétique. Ainsi, "1.5" doit être considéré comme "un nombre compris entre 1.45 et 1.55" et "1.50" doit être considéré comme "un nombre compris entre 1.495 et 1.505".
la source