Java >> Tutoriel Java >  >> Java

Méfiez-vous de la division par zéro en Java

Comme pour la plupart des articles de cette série, un problème étrange est survenu dans l'un de mes cours, alors je voulais en parler. Le problème aujourd'hui est de savoir ce qui se passe lorsque vous divisez par zéro en Java.

Contexte

Pour un peu de contexte, j'ai pensé que je partagerais pourquoi j'écris cet article. Chaque semestre, j'enseigne un cours sur les composants logiciels. En tant qu'enseignant, je considère que c'est mon travail de m'assurer que les élèves reçoivent un soutien et des conseils appropriés. Une grande partie de ce soutien prend la forme d'anticipation des problèmes que les étudiants pourraient rencontrer.

Comme j'ai enseigné le cours à quelques reprises, j'ai remarqué des tendances dans la façon dont les étudiants se débattent avec le matériel. Par exemple, très tôt dans le cours, on demande aux étudiants de calculer une racine carrée par itération de Newton. Le processus ressemble à ceci :

  1. Découvrez, g , à la racine carrée d'un nombre, x (par exemple, x lui-même est un excellent point de départ)
  2. Carré g et soustrayez x à partir de cela. Ensuite, divisez le résultat par x . Cela nous donne une erreur, e
  3. Si e est suffisamment proche de 0, alors nous savons que nous avons le bon g . Nous avons terminé !
  4. Si e n'est pas assez proche de 0, alors nous devons faire une autre supposition.
  5. Pour calculer un nouveau g , nous pouvons prendre g et ajoutez-le au ratio de x supérieur à g . Cette somme peut ensuite être divisée par deux pour nous donner notre nouveau g .
  6. Répétez les étapes 2 à 5 si nécessaire.

Pour voir comment cela fonctionne en pratique, essayons de prédire la racine carrée de 9. Pour commencer, nous supposons 9. Notre erreur est de 8 (c'est-à-dire (9 * 9 - 9) / 9). Ce n'est pas assez proche de 0. Notre estimation mise à jour est 5 (c'est-à-dire (9 + 9 / 9) / 2). L'erreur pour 5 est de 1,78. Beaucoup mieux, mais on peut faire mieux. Notre estimation mise à jour est de 3,4, ce qui nous donne une erreur de 0,28. Encore une fois, nous nous rapprochons. Après cela, notre estimation devient 3,02, moment auquel nous pourrions nous arrêter (si nous le jugeons suffisamment proche).

Maintenant, la raison pour laquelle je vous montre cela est que ce processus implique une division potentielle par 0 lorsque x est 0. En conséquence, nous demandons généralement aux étudiants de gérer cela. Malheureusement, ce qui finit par arriver, c'est que les élèves remarqueront que leur code fonctionne même lorsque cette division par 0 se produit. Comment est-ce possible? C'est le sujet de l'article d'aujourd'hui !

La division par zéro erreur en Java

Si vous avez déjà joué avec l'algèbre, vous savez probablement que la division par zéro est un grand non-non. Je n'ai pas les compétences en mathématiques pour expliquer pourquoi, mais cela a un sens quelque peu intuitif, non ? Que signifie diviser quelque chose en zéro partie ?

Parce que la division par zéro cause tant de problèmes, les langages de programmation ont leurs propres façons de le gérer. Par exemple, en Java, la division entière par zéro provoquera une ArithmeticException. Voici un exemple utilisant JDoodle :

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at MyClass.main(MyClass.java:6)

Personnellement, je suis un grand fan d'erreurs comme celles-ci, car elles me donnent un endroit où regarder quand les choses tournent mal. Cela dit, je comprends pourquoi les développeurs les évitent parfois en raison de la complexité qu'ils introduisent.

Présentation de NaN

Malheureusement, Java ne fournit pas toujours cette belle ArithmeticException dans tous les cas, en particulier lorsque vous travaillez avec des doubles. Dans l'exemple que j'ai mentionné en arrière-plan, nous calculons la racine carrée en utilisant des doubles. Comme vous l'avez vu, cela se passe plus ou moins bien, mais il y a un scénario où ce n'est pas le cas :lorsque x =0.

Pour illustrer cela, essayons de parcourir la même liste d'étapes ci-dessus. Par exemple, nous allons commencer à calculer la racine carrée de 0 en faisant une supposition, g , de 0. Pour être clair, les deux x et g sont des doubles. Par conséquent, lorsqu'il s'agit de calculer l'erreur, nous obtenons l'expression suivante :(0 * 0 - 0) / 0 . Une fois simplifié, on se retrouve avec l'expression suivante :0 / 0 . S'il s'agissait d'entiers, notre programme planterait comme prévu. Au lieu de cela, notre expression est évaluée à NaN .

NaN est une valeur un peu bizarre. Cela signifie littéralement "pas un nombre", mais il peut être stocké dans une double variable. En conséquence, c'est un peu espiègle . Pour aggraver les choses, cela ne causera pas de problèmes évidents lors du calcul. Par exemple, NaN peut être utilisé dans des expressions relationnelles comme n'importe quel double, alors ne vous attendez pas à ce qu'il provoque des erreurs lors de sa propagation.

Dans notre cas, lorsque NaN est généré, il est ensuite immédiatement vérifié s'il est suffisamment proche de x en utilisant un certain seuil (par exemple, NaN >= .0001 ). Parce que NaN n'est pas un nombre, cette expression renvoie toujours faux. Jusqu'à ce point, faux signifierait que notre condition était remplie, nous pourrions donc renvoyer notre estimation sous forme de racine carrée. Curieusement, car nous avons défini notre première estimation sur x , nous renverrons x . Et depuis x se trouve être sa propre racine carrée, nous pourrions dire que le code fonctionne.

Mais la question est :le code fonctionne-t-il ? C'est un peu une question philosophique. Après tout, quand j'enseigne, je définis généralement l'exactitude comme une fonction dont l'ensemble des sorties existe dans l'ensemble des sorties attendues. En utilisant cette définition de boîte noire de l'exactitude, nous pourrions ne pas nous soucier que notre fonction racine carrée tombe accidentellement sur la bonne réponse. Et pour nos amis du code golf, on pourrait même préférer ce « bug » au calcul des racines carrées. Cela dit, il y a quelque chose de mal à l'aise dans la façon dont les choses se passent.

Mais ça marche !

Chaque jour, les gens passent par des processus de révision de code tout en recevant des commentaires comme "c'est un peu un hack" et "cela a une mauvaise odeur", et je commence à me demander si des commentaires comme celui-ci sont valides. Après tout, le code racine carré fonctionne ! En conséquence, j'ai commencé à remettre en question certaines des nombreuses hypothèses que nous faisons sur le codage. Par exemple, qu'est-ce qui rend le code hacky ? Qu'est-ce qui fait que le code a une mauvaise odeur ? Voici quelques discussions que j'ai réussi à susciter :

  • Qu'est-ce qu'un hack ou un code hacky ?
  • Qu'est-ce qu'un code "hacky" ?
  • Le problème de l'odeur du code et les secrets d'une refactorisation efficace

Peut-être que dans un prochain article, je pourrais descendre dans ce terrier de lapin philosophique. Pour l'instant cependant, je dois l'appeler un jour! Comme toujours, voici quelques autres tangentes de codage que vous pourriez apprécier :

  • Le else if Le mot-clé n'existe pas en Java
  • Le comportement de i = i++ en Java
  • La différence entre les déclarations et les expressions

Cela dit, merci d'être resté. A la prochaine !


Balise Java