Nous avons tous appris les nombres à virgule flottante à l'école. Contrairement aux nombres entiers qui n'ont pas de points décimaux.
Il n'est pas nécessaire d'être un expert en mathématiques pour savoir que le nombre cinq est un nombre entier alors que 5,43 est un nombre à virgule flottante. De toute évidence, nous connaissons tous le point décimal qui est un point de différenciation clair entre les deux.
Bien sûr, il n'est pas surprenant que les flottants soient utilisés dans les langages de programmation et soient même classés comme un type de données primitif distinct en raison de leur nécessité pour les calculs quotidiens.
Lorsque vous commencerez à apprendre Python ou Java, vous utiliserez le type de données float. En particulier, lorsque vous effectuez l'opération de division entre deux nombres à virgule flottante eux-mêmes.
En Python, même si vous n'avez pas à déclarer un nombre à virgule flottante, la division de deux de ces nombres peut entraîner un nombre du même type de données, comme indiqué ci-dessous :
Nous pouvons également produire le même résultat en Java, même si nous devons déclarer explicitement le type de données primitif 'float' pour toutes les variables utilisées.
Comme on peut le voir, ce type de calcul est nécessaire pour un certain nombre d'applications, et c'est précisément pourquoi les opérations avec des nombres à virgule flottante sont disponibles en tant que fonctionnalité dans ces deux langages de programmation.
Tout à fait différemment, si vous avez appris Solidity, vous avez dû remarquer qu'il n'inclut pas le type de données de nombre à virgule flottante.
Au lieu de cela, si vous avez écrit du code dans Solidity, vous utiliserez des nombres de type entier signé ou non signé.
Donc, si vous souhaitez effectuer le même calcul - cinq divisé par deux - comme indiqué dans la section précédente, voici à quoi cela ressemblera, dans le cadre d'un contrat intelligent :
Cependant, le résultat que vous obtiendrez ne sera pas le même, car Solidity ne prend pas en charge le type de données float. Du moins, pas encore.
Plus précisément, Solidity arrondira le résultat vers zéro. Ce qui, dans ce cas, et comme indiqué ci-dessus, se traduira par une valeur de deux. Considérez cela comme l'opération modulo sur les deux nombres.
Alors que dans des circonstances normales, cela ne devrait pas avoir beaucoup d'importance, il y a des moments où le résultat peut conduire à une erreur de calcul. C'est pourquoi il est recommandé d'éviter ou de différer au maximum l'opération de division.
Même si la règle BODMAS à l'école a exigé que tous les étudiants en mathématiques effectuent le calcul de la division avant la multiplication, cela n'est pas recommandé si vous devez effectuer une division entière dans Solidity.
Découvrons pourquoi, avec cet exemple simple, qui effectue une multiplication et une division avec trois nombres :
Si vous entrez les chiffres un, trois et cinq lors du déploiement du contrat intelligent, vous devriez obtenir la même valeur pour les fonctions getResult et getResult2, n'est-ce pas ?
Si vous utilisez une simple calculatrice, vous devriez obtenir une valeur flottante de 1,666, ce qui se traduit par un dans Solidity, grâce à l'absence des valeurs flottantes.
Malheureusement, ce n'est pas ce qui se passe lorsque vous vérifiez les résultats des fonctions getResult et getResult2, comme indiqué ci-dessous :
Si nous effectuons d'abord la division, nous obtenons un résultat final de zéro. Contrairement à la valeur attendue de un, lorsque vous reportez cette opération à la fin dans la fonction getResult.
Comme vous pouvez le constater, même si nous avons calculé cette valeur en anticipant l'absence de valeurs flottantes, il y a toujours une erreur qui survient et qui peut entraîner une perte de précision. Ce qui, à son tour, peut se traduire par une perte économique qui peut être facilement contournée.
Alors, comment éviter l'erreur de division entière ? Plus important encore, comment pouvons-nous augmenter la précision de nos calculs ? Découvrons les trois façons les plus courantes de le faire.
Étant donné qu'il existe un certain nombre d'approches pour contourner cette erreur, commençons par la solution la plus simple et examinons-en quelques autres avant de l'appeler un jour.
Méthode #1 : Utiliser un multiplicateur
Maintenant, en plus de placer l'opération de division en dernier, une façon de s'assurer que vous ne vous retrouvez pas avec des erreurs ou des valeurs imprécises est d'utiliser un multiplicateur. Dans l'exemple ci-dessous, nous utiliserons un multiplicateur de 100 avec les trois mêmes nombres utilisés précédemment.
Maintenant, lorsque vous déployez le contrat avec le code suivant et appelez les deux fonctions, voici le résultat :
Étant donné que la sortie souhaitée est de 1,666 ou 166/100, nous pouvons voir que la valeur getResult2 nous fournit la précision nécessaire lorsque le multiplicateur fonctionne en conjonction avec les trois nombres. Bien sûr, si vous n'utilisez pas le multiplicateur comme dans la fonction getResult, vous obtiendrez 1.
Où 0,666 est tronqué du résultat, comme prévu par défaut lorsque vous utilisez Solidity. Donc, pour récupérer cette valeur, il suffit de diviser le résultat par le multiplicateur.
Comme vous le savez peut-être, Solidity se rapproche de zéro lorsqu'il s'agit d'arrondir dans le cas d'entiers signés et non signés, donc ce correctif fonctionne également dans le cas d'entiers signés, une fois que vous avez déployé et exécuté le code ci-dessous avec les arguments moins un , trois et cinq :
En ce qui concerne les valeurs générées par les fonctions pour les entiers signés, les voici :
De toute évidence, nous sommes également en mesure de maintenir la précision pour les entiers signés, en utilisant un multiplicateur. Cependant, il existe un moyen précis d'arrondir les entiers signés que nous examinerons ensuite.
Méthode #2 : Utilisez la division de plancher pour les entiers signés
Maintenant, si l'on regarde la droite numérique ci-dessous, le résultat de la division entière entre deux entiers non signés est arrondi plus près de zéro. Comme dans le cas de l'obtention de 1,666, Solidité l'arrondit à 1, qui est un nombre plus petit.
Cependant, lorsqu'il s'agit d'entiers signés, un résultat de -1,6666 sera arrondi à -1, qui est le plus grand des deux nombres. Ainsi, la division au sol doit être appliquée ici par opposition à la division arrondie à zéro qui est implémentée dans Solidity par défaut. Par souci de précision, bien sûr.
Si le type de données flottant était disponible, la valeur de -1,666 serait calculée. Alors que Solidity arrondirait cette valeur à -1, l'application de la division de plancher aux entiers signés la réduirait à -2.
Lorsque vous appelez les fonctions getResult et getResult2, nous obtenons la valeur comme indiqué ci-dessous pour les arguments moins un, trois et cinq :
Comme vous pouvez le constater, getResult calcule la valeur en utilisant l'approche arrondie vers zéro tandis que getResult2 l'arrondit au plus petit entier ici, en vertu de la division du plancher.
Méthode #3 : Utiliser la bibliothèque ABDKMath64x64
Maintenant, pour la dernière méthode, nous allons utiliser la bibliothèque ABDKMath64x64, qui convertit le résultat des opérations de division en nombres à virgule fixe.
Encore une fois, on dit que l'utilisation de cette bibliothèque améliore la précision par opposition à la méthode d'arrondi vers zéro qui est disponible par défaut dans Solidity. Afin de comprendre la sortie, comparons les résultats de l'ajout avec la fonction div, comme indiqué ci-dessous :
Lorsque vous ajoutez les arguments 1, 1 et 1 lors du déploiement du contrat intelligent, vous obtenez les valeurs, comme indiqué ci-dessous :
Il ne faut pas s'étonner que la fonction add renvoie une valeur entière de deux, les trois arguments s'additionnant pour autant. Comme pour la fonction div, une valeur int 128 est renvoyée qui représente un nombre à virgule fixe signé de 64,64 bits et avec lequel vous pouvez effectuer les opérations habituelles sur les nombres.
Bien sûr, la bibliothèque ABDKMath64x64 n'est pas la seule à pouvoir être utilisée pour améliorer la précision en plus d'empêcher l'erreur de division entière.
Il y en a plusieurs autres avec quelques exemples comme Fixidity , DSMath et les bibliothèques BANKEX qui utilisent différents formats de nombres. Formats de nombre différents du format de nombre à virgule fixe 64,64 bits utilisé dans l'exemple ci-dessus. Ainsi, bien que ces bibliothèques puissent sembler utiles à explorer, n'oubliez pas que leurs formats de nombres ne fonctionneront avec aucune des autres bibliothèques disponibles.