Wir haben alle in der Schule etwas über Gleitkommazahlen gelernt. Im Gegensatz zu ganzen Zahlen, die keine Dezimalstellen haben.
Man muss kein Mathe-Experte sein, um zu wissen, dass die Zahl fünf eine ganze Zahl ist, während 5,43 eine Gleitkommazahl ist. Natürlich kennen wir alle den Dezimalpunkt, der ein klares Unterscheidungsmerkmal zwischen den beiden ist.
Natürlich ist es keine Überraschung, dass Gleitkommazahlen in Programmiersprachen verwendet werden und aufgrund ihrer Notwendigkeit für die tägliche Berechnung sogar als eigenständiger primitiver Datentyp klassifiziert werden.
Wenn Sie anfangen, Python oder Java zu lernen, verwenden Sie den Datentyp float. Insbesondere, wenn Sie die Divisionsoperation zwischen zwei Gleitkommazahlen selbst durchführen.
Selbst wenn Sie in Python keine Gleitkommazahl deklarieren müssen, kann die Division zweier dieser Zahlen zu einer Zahl desselben Datentyps führen, wie unten gezeigt:
Das gleiche Ergebnis können wir auch in Java erzielen, auch wenn wir den primitiven Datentyp „float“ für alle verwendeten Variablen explizit deklarieren müssen.
Wie man sieht, ist diese Art der Berechnung für eine Reihe von Anwendungen notwendig und genau deshalb stehen Operationen mit Gleitkommazahlen als Feature in beiden Programmiersprachen zur Verfügung.
Ganz anders: Wenn Sie Solidity gelernt haben, dürfte Ihnen aufgefallen sein, dass es den Datentyp Gleitkommazahl nicht enthält.
Wenn Sie Code in Solidity geschrieben haben, verwenden Sie stattdessen Zahlen vom Typ Integer mit oder ohne Vorzeichen.
Wenn Sie also die gleiche Berechnung – fünf dividiert durch zwei – wie im vorherigen Abschnitt durchführen möchten, sieht sie als Teil eines Smart Contracts folgendermaßen aus:
Das Ergebnis, das Sie erhalten, ist jedoch nicht dasselbe, da Solidity den Float-Datentyp nicht unterstützt. Zumindest noch nicht.
Insbesondere rundet Solidity das Ergebnis gegen Null. Was in diesem Fall und wie oben gezeigt zu einem Wert von zwei führt. Stellen Sie sich dies als Modulo-Operation für beide Zahlen vor.
Unter normalen Umständen sollte dies zwar keine große Rolle spielen, es kann jedoch vorkommen, dass das Ergebnis zu einem Berechnungsfehler führt. Aus diesem Grund wird empfohlen, die Teilungsoperation so weit wie möglich zu vermeiden oder zu verschieben.
Auch wenn die BODMAS-Regel in der Schule vorschreibt, dass alle Mathematikschüler die Divisionsberechnung vor der Multiplikation durchführen müssen, wird dies nicht empfohlen, wenn Sie in Solidity eine ganzzahlige Division durchführen müssen.
Lassen Sie uns anhand dieses einfachen Beispiels herausfinden, warum das Multiplikation und Division mit drei Zahlen durchführt:
Wenn Sie beim Bereitstellen des Smart Contracts die Zahlen eins, drei und fünf eingeben, sollten Sie für die Funktionen getResult und getResult2 den gleichen Wert erhalten, oder?
Wenn Sie einen einfachen Taschenrechner verwenden, sollten Sie einen Float-Wert von 1,666 erhalten, was aufgrund des Fehlens von Float-Werten in Solidity einem Wert entspricht.
Leider passiert dies nicht, wenn Sie die Ergebnisse der Funktionen getResult und getResult2 überprüfen, wie unten gezeigt:
Wenn wir zuerst die Division durchführen, erhalten wir ein Endergebnis von Null. Im Gegensatz zum erwarteten Wert von eins, wenn Sie diesen Vorgang in der getResult-Funktion bis zum Ende verschieben.
Wie Sie sehen, tritt immer noch ein Fehler auf, der zu einem Verlust an Präzision führen kann, selbst wenn wir diesen Wert unter Annahme des Fehlens von Float-Werten berechnet haben. Was wiederum zu einem wirtschaftlichen Verlust führen kann, der leicht umgangen werden kann.
Wie verhindern wir also den Fehler bei der Ganzzahldivision? Noch wichtiger: Wie erhöhen wir die Präzision unserer Berechnungen? Lassen Sie uns die drei häufigsten Möglichkeiten herausfinden, dies zu tun.
Da es eine Reihe von Ansätzen zur Umgehung dieses Fehlers gibt, beginnen wir mit der einfachsten Lösung und schauen uns noch ein paar weitere an, bevor wir damit Schluss machen.
Methode Nr. 1: Verwenden Sie einen Multiplikator
Neben der Platzierung der Division an letzter Stelle besteht eine Möglichkeit, um sicherzustellen, dass keine Fehler oder ungenauen Werte entstehen, darin, einen Multiplikator zu verwenden. Im folgenden Beispiel verwenden wir einen Multiplikator von 100 zusammen mit denselben drei zuvor verwendeten Zahlen.
Wenn Sie nun den Vertrag mit dem folgenden Code bereitstellen und beide Funktionen aufrufen, ist dies die Ausgabe:
Da die gewünschte Ausgabe 1,666 oder 166/100 ist, können wir sehen, dass der getResult2-Wert uns die nötige Genauigkeit liefert, wenn der Multiplikator in Verbindung mit den drei Zahlen arbeitet. Wenn Sie den Multiplikator nicht wie in der getResult-Funktion verwenden, erhalten Sie natürlich 1.
Dabei wird 0,666 vom Ergebnis abgeschnitten, wie standardmäßig erwartet, wenn Sie Solidity verwenden. Um diesen Wert zu erhalten, müssen Sie lediglich das Ergebnis durch den Multiplikator dividieren.
Wie Sie vielleicht wissen, bewegt sich Solidity beim Runden sowohl bei vorzeichenbehafteten als auch bei vorzeichenlosen Ganzzahlen in Richtung Null. Daher funktioniert dieser Fix auch bei vorzeichenbehafteten Ganzzahlen, sobald Sie den folgenden Code mit den Argumenten minus eins bereitstellen und ausführen , drei und fünf:
Die von den Funktionen für vorzeichenbehaftete Ganzzahlen generierten Werte sind hier:
Offensichtlich können wir mithilfe eines Multiplikators die Genauigkeit auch für ganze Zahlen mit Vorzeichen beibehalten. Allerdings gibt es eine präzise Möglichkeit, ganze Zahlen mit Vorzeichen abzurunden, die wir uns als Nächstes ansehen werden.
Methode Nr. 2: Verwenden Sie die Bodendivision für ganze Zahlen mit Vorzeichen
Wenn man sich nun den Zahlenstrahl unten ansieht, wird das Ergebnis der Ganzzahldivision zwischen zwei vorzeichenlosen Ganzzahlen näher an Null gerundet. Wie im Fall des Erhaltens von 1,666 rundet Solidity es auf 1 ab, was eine kleinere Zahl ist.
Bei vorzeichenbehafteten Ganzzahlen wird jedoch ein Ergebnis von -1,6666 auf -1 gerundet, was die größere der beiden Zahlen ist. Daher muss hier die Bodenteilung angewendet werden, im Gegensatz zur auf Null gerundeten Teilung, die in Solidity standardmäßig implementiert ist. Der Präzision halber natürlich.
Wenn der Datentyp float verfügbar wäre, würde der Wert -1,666 berechnet werden. Während Solidity dies auf -1 abrunden würde, wird es durch die Anwendung der Bodendivision auf vorzeichenbehaftete Ganzzahlen auf -2 reduziert.
Wenn Sie die Funktionen getResult und getResult2 aufrufen, erhalten wir den unten gezeigten Wert für die Argumente minus eins, drei und fünf:
Wie Sie sehen können, berechnet getResult den Wert mithilfe des auf Null gerundeten Ansatzes, während getResult2 ihn hier aufgrund der Bodendivision auf die kleinste Ganzzahl rundet.
Methode Nr. 3: Verwenden Sie die ABDKMath64x64-Bibliothek
Für die letzte Methode verwenden wir nun die ABDKMath64x64-Bibliothek, die das Ergebnis der Divisionsoperationen in Festkommazahlen umwandelt.
Auch hier soll die Verwendung dieser Bibliothek die Genauigkeit verbessern, im Gegensatz zur Rundungsmethode gegen Null, die standardmäßig in Solidity verfügbar ist. Um die Ausgabe zu verstehen, vergleichen wir die Ergebnisse der Add-Funktion mit der Div-Funktion, wie unten gezeigt:
Wenn Sie beim Bereitstellen des Smart Contracts die Argumente 1, 1 und 1 hinzufügen, erhalten Sie die folgenden Werte:
Es sollte nicht überraschen, dass die Add-Funktion einen ganzzahligen Wert von zwei zurückgibt, wobei die Summe der drei Argumente diesen Wert ergibt. Für die div-Funktion wird ein int 128-Wert zurückgegeben, der eine vorzeichenbehaftete 64,64-Bit-Festkommazahl darstellt und mit dem Sie die üblichen Zahlenoperationen durchführen können.
Natürlich ist die ABDKMath64x64-Bibliothek nicht die einzige, die neben der Verhinderung des Ganzzahldivisionsfehlers auch zur Verbesserung der Genauigkeit verwendet werden kann.
Es gibt mehrere andere, einige Beispiele sind Fixidity , DSMath und die BANKEX- Bibliotheken, die unterschiedliche Zahlenformate verwenden. Zahlenformate, die sich vom im obigen Beispiel verwendeten 64,64-Bit-Festkommazahlenformat unterscheiden. Auch wenn die Erkundung dieser Bibliotheken nützlich erscheinen mag, denken Sie bitte daran, dass ihre Zahlenformate mit keiner der anderen verfügbaren Bibliotheken funktionieren.