Erwachsene Posit Herausforderungen

Habré hatte bereits mehrere Artikel ( eins , zwei , zweieinhalb ) über das neue Posit-Gleitkommaformat, dessen Autoren es in jeder Hinsicht dem Standard-Float nach IEEE 754 überlegen präsentieren. Das neue Format fand auch Kritiker ( eins , zwei ), die behaupten, dass die Fehler von Posit die Vorzüge überwiegen. Aber was ist, wenn wir wirklich ein neues revolutionäres Format haben und die Kritik einfach durch den Neid und die Inkompetenz derjenigen verursacht wird, die kritisieren? Der beste Weg, dies herauszufinden, besteht darin, sich selbst zu berechnen.

Einführung


Die Vorteile des neuen Formats werden anhand einfacher Beispiele mit Addition / Multiplikation von Zahlen ähnlicher Ordnung demonstriert, die zu einer um ein oder zwei Stellen höheren Genauigkeit führen. Bei realen Berechnungen spielt jedoch das Plus / Minus-Bit des Fehlers einer einzelnen Operation keine Rolle, da wir ohnehin mit begrenzter Genauigkeit berechnen. Die Anhäufung von Fehlern infolge der Abfolge von Operationen ist wichtig, wenn nach einiger Zeit die Fehler in den unteren Ziffern zu Fehlern in den älteren führen. Das werden wir versuchen zu erleben.

Vorbereitung


Zum Testen habe ich die Posit-Implementierung mit einem Pathos-Titel von hier genommen . Um es in Visual Studio zu kompilieren, mussten wir die Zeile #define CLZ (n) __lzcnt (n) in die Datei util.h einfügen und 0.f / 0.f in der Datei posit.cpp durch std :: numeric_limits <float> :: quiet_NaN ersetzen (). Übrigens wurden in dieser Bibliothek auch keine Implementierungen für elementare mathematische Funktionen (mit Ausnahme der Wurzel) gefunden - dies ist ein weiterer Grund für den Verdacht, dass etwas nicht stimmte.

Test 1. Multiplikation komplexer Zahlen


Die mit komplexer Arithmetik berechnete Fourier-Transformation wird nach Möglichkeit verwendet. Zuerst wollte ich Posit auf der Fourier-Transformation testen; Da die Genauigkeit jedoch stark von der Implementierung abhängt, müssen Sie für korrekte Tests alle grundlegenden Algorithmen berücksichtigen, was etwas zeitaufwändig ist. Daher können Sie mit einer einfacheren Operation beginnen - der Multiplikation komplexer Zahlen.

Wenn wir einen bestimmten Vektor nehmen und ihn 360 Mal um 1 ° drehen, sollten wir am Ende den gleichen Originalvektor erhalten. Tatsächlich wird das Ergebnis aufgrund der Anhäufung von Fehlern geringfügig abweichen - und je größer die Anzahl der Windungen ist, desto größer ist der Fehler. Verwenden Sie also diesen einfachen Code

complex<T> rot(cos(a), sin(a)); complex<T> vec(length, 0); for (long i = 0; i < count; i++) { vec *= rot; } cout << "error: " << stdev(vec.real() - length, vec.imag()) << endl; 

Wir drehen einen Vektor mit verschiedenen Datentypen und betrachten den Fehler als mittlere quadratische Abweichung des resultierenden Vektors vom Original (die auch als Länge des Differenzvektors interpretiert werden kann).

Nehmen Sie zunächst den Einheitsvektor als den unterstützendsten von Posit:
Iterationen4100100010.000100.000
doppelt00000
float00,000000360,000010380,00018580,0001961
posit00,000000730,000005340,00004110,0004468

Hier gibt es noch keinen offensichtlichen Anführer - der Vorteil ist doppelt so groß wie der des einen oder anderen. Erhöhen Sie die Länge des gedrehten Vektors auf 1000:
Iterationen4100100010.000100.000
doppelt00000
float00,000280,01030,180,19
posit00,002130,01880,162.45

Die Fehlerwerte sind nahezu gleich. Gehen Sie voran - 1.000.000:
Iterationen4100100010.000100.000
doppelt000,000000020,000000420,0000036
float00,3312.0185,8198.1
posit08.1271,0769.210706.8

Hier bleibt Posit bereits souverän zurück und der Doppelfehler beginnt sich in den Schwimmer zu schleichen. Nehmen wir eine noch längere Länge - 10 10 , um die Vorteile von Gleitkommaformaten voll auszuschöpfen:
Iterationen4100100010.000100.000
doppelt0,000002450,000015360,00020410,00409510,03621497
float0,000002456003.888111.81 836 254.01965083.0
posit9216.01287208.714443543,7202630144.41784050328.2

Hier das Interessanteste am Anfang, bei 4 Iterationen - wenn der Float einen Fehler ergibt, der dem Double entspricht, und Posit bereits ein völlig falsches Ergebnis hat.

Test 2. Berechnung eines rationalen Polynoms


Da die ursprüngliche Bibliothek keine mathematischen Funktionen enthielt, werden wir versuchen, etwas selbst zu tun. Viele Funktionen werden durch die Erweiterung in einer Taylor-Reihe schlecht approximiert, und sie sind bequemer durch Approximation durch ein rationales Polynom zu berechnen. Diese Annäherung kann auf verschiedene Arten erhalten werden, auch rein analytisch - durch die Padé-Näherung . Wir werden es außerdem zum Testen mit ausreichend großen Koeffizienten verwenden, so dass sie vor der Berechnung ebenfalls gerundet werden.

Verwenden von Wolfram Mathematica und des PadeApproximant-Befehls [Sin [x], {x, 0, {11, 11}}]
Wir erhalten ein solches rationales Polynom zur Approximation des Sinus, das eine doppelte Genauigkeit im Bereich von etwa -2 bis 2 liefert:

\ frac {- \ frac {481959816488503 x ^ {11}} {363275871831577908403200} + \ frac {23704595077729 x ^ 9} {42339845201815607040} - \ frac {2933434889971 x ^ 7} {33603051747472704} } {617703157122660} - \ frac {109061004303 x ^ 3} {722459832892} + x} {\ frac {37291724011 x ^ {10}} {11008359752472057830400} + \ frac {3924840709 x ^ 8} {20161831048483640 x ^ 6} {168015258737363520} + \ frac {1679739379 x ^ 4} {13726736824948} + \ frac {34046903537 x ^ 2} {2167379498676} +1}


Das Horner-Schema wird normalerweise direkt für Berechnungen verwendet, um Berechnungen zu sparen. In unserem Fall (mit HornerForm) sieht es so aus

 template< typename T > T padesin(T x) { T xx = x*x; return (x*(T(363275871831577908403200.) + xx*(-T(54839355237791393068800.) + xx*(T(2120649063015013090560.) + xx*(-T(31712777908498486800.) + xx*(T(203385425766914820.) - T(481959816488503.) * xx)))))) / (T(363275871831577908403200.) + xx*(T(5706623400804924998400.) + xx*(T(44454031219351353600.) + xx* (T(219578286347980560.) + xx*(T(707177798947620.) + T(1230626892363.) * xx))))); } 


Mal sehen:
x = 0,5x = 1x = 2
Sünde (x)0,4794255386042030,84147098480789650,9092974268256817
doppelt0,4794255386042030,84147098480789650,909297426825681 6
float0,4794255 4950714110,84147095680 236820,9092974 066734314
posit0,47 889610379934310,84 244372695684430,9 110429435968399

x = 3x = 4x = 5
Sünde (x)0,1411200080598672-0,7568024953079282-0,9589242746631385
doppelt0,14112000805 85958-0,75680249 60833886-0,958924 3758030122
float0,1411200 165748596-0,7568024 396896362-0,9589243 531227112
posit0,14 44759201258421-0,7 614213190972805-0,9 691629931330681

Wie Sie sehen können, sieht die Situation mit Posit hier bedauerlich aus - kaum zwei signifikante Nummern werden gewählt.

Fazit


Leider ist kein Wunder geschehen und die Revolution ist abgesagt. Der Vorteil von Posit, der bei Einzelberechnungen demonstriert wurde, ist nichts anderes als ein Trick, dessen Preis eine katastrophale Abnahme der Genauigkeit bei "schweren" realen Berechnungen darstellt. Der einzige Grund, warum es sinnvoll ist, Posit anstelle von IEEE 754 Float oder Fixpunkt zu verwenden, ist religiös. Die Verwendung des magischen Formats, dessen Genauigkeit durch den heiligen Glauben seiner Schöpfer gewährleistet wird, kann viele Wunder in Ihre Programme bringen!

PS- Quellcode zur Überprüfung und Kritik .

PPS Fortsetzung .

Source: https://habr.com/ru/post/de467735/


All Articles