Sind Variablen in MATLAB standardmäßig WIRKLICH doppelt genau?

74

Diese Frage entstand aus etwas Seltsamem, das mir auffiel, nachdem ich diese Frage weiter untersucht hatte ...

Ich habe MATLAB-Variablen immer standardmäßig als doppelt genau verstanden . Wenn ich also so etwas wie eine Variable mit 20 Nachkommastellen deklarieren würde:

>> num = 2.71828182845904553488;
>> class(num)  % Display the variable type
ans =
double

Ich würde erwarten, dass die letzten 4 Ziffern ignoriert werden, da die relative Genauigkeit des Gleitkommas in der Größenordnung von 10-16 liegt :

>> eps(num)
ans =
    4.440892098500626e-016

Wenn ich versuche, die Zahl mit mehr als 16 Nachkommastellen anzuzeigen (entweder mit fprintfoder sprintf), erhalte ich das, was ich erwartet habe:

>> fprintf('%0.20f\n', num)
2.71828182845904550000
>> sprintf('%0.20f', num)
ans =
2.71828182845904550000

Mit anderen Worten, die Ziffern 17 bis 20 sind alle 0.

Aber es wird merkwürdig, wenn ich numzur arithmetischen Funktion mit variabler Genauigkeit in der Symbolic Toolbox übergebe und sie auffordere , die Zahl mit 21 Stellen Genauigkeit darzustellen:

>> vpa(num, 21)
ans =
2.71828182845904553488

WAS?! Diese letzten 4 Ziffern sind wieder aufgetaucht! Sollten sie nicht verloren gegangen sein, als die von mir eingegebene ursprüngliche Nummer als Variable mit doppelter Genauigkeit gespeichert wurde num? Da numeine doppelte Genauigkeit Variable ist , wenn es um übergeben wird vpa, wie hat vpaweiß , was sie waren?

numIch gehe davon aus, dass MATLAB intern genauer als ein Doppel darstellt, da ich es mit einer Zahl initialisiert habe, deren Ziffern über dem Dezimalpunkt liegen, als eine Variable mit doppelter Genauigkeit verarbeiten könnte. Passiert das wirklich oder ist etwas anderes los?



BONUS: Und hier ist eine zusätzliche Quelle der Verwirrung, wenn Sie noch keine Migräne von oben haben ...

>> num = 2.71828182845904553488;  % Declare with 20 digits past the decimal
>> num = 2.718281828459045531;    % Re-declare with 18 digits past the decimal
>> vpa(num, 21)
ans =
2.71828182845904553488  % It's the original 20-digit number!!!
gnovice
quelle

Antworten:

66

Sie sind Doppel. vpa()ist die Wahl einfach über den Gleitkomma- relative Genauigkeit nicht signifikante Ziffern anzuzeigen, wo printf()und disp()Trunkat oder sie Null aus.

Sie erhalten nur Ihre ursprünglichen vier Ziffern zurück, weil das Literal, mit dem Sie initialisiert haben, numzufällig die exakte Dezimalerweiterung eines binären Doppelwerts ist, da es aus der Ausgabe der Erweiterung eines tatsächlichen Doppelwerts kopiert und eingefügt wurde von der anderen Frage. Es funktioniert nicht für andere Werte in der Nähe, wie Sie in Ihrem Nachtrag "BONUS" zeigen.

Genauer gesagt erzeugen alle numerischen Literale in Matlab Werte vom Typ double. Sie werden in den binären Doppelwert konvertiert, der dem von ihnen dargestellten Dezimalwert am nächsten kommt. Tatsächlich werden Ziffern in einem Literal, die über die Genauigkeitsgrenze des Doppeltyps hinausgehen, stillschweigend gelöscht. Wenn Sie die Ausgabe von kopieren und einfügen vpa, um eine neue Variable zu erstellen, wie es das Poster der anderen Frage mit der e = ...Anweisung getan hat , initialisieren Sie einen Wert aus einem Literal, anstatt sich direkt mit dem Ergebnis eines vorherigen Ausdrucks zu befassen.

Die Unterschiede liegen hier nur in der Ausgabeformatierung. Ich denke, was passiert ist, dass vpa()man dieses binäre Doppel mit doppelter Genauigkeit nimmt und es als exakten Wert behandelt. Für einen gegebenen binären Mantissen-Exponenten-Wert können Sie das Dezimaläquivalent zu beliebig vielen Dezimalstellen berechnen. Wenn der Binärwert wie bei jedem Datentyp mit fester Größe eine begrenzte Genauigkeit ("Breite") aufweist, sind nur so viele dieser Dezimalstellen von Bedeutung. printf()Die Standardanzeige von Matlab behandelt dies, indem sie die Ausgabe abschneidet oder nicht signifikante Ziffern als 0 anzeigt. Dabei werden vpa()die Genauigkeitsgrenzen ignoriert und es werden weiterhin so viele Dezimalstellen berechnet, wie Sie anfordern.

Diese zusätzlichen Ziffern sind in dem Sinne falsch, dass sie alle auf denselben binären Doppelwert "gerundet" würden, wenn sie durch andere Werte ersetzt würden, um einen nahe gelegenen Dezimalwert zu erzeugen.

Hier ist eine Möglichkeit, dies zu zeigen. Diese Werte von x sind alle gleich, wenn sie im Doppel gespeichert werden, und werden alle gleich durch dargestellt vpa().

x = [
    2.7182818284590455348848081484902650117874145507812500
    2.7182818284590455348848081484902650117874145507819999
    2.7182818284590455348848
    2.71828182845904553488485555555555555555555555555555
    exp(1)
    ]
unique(x)

Hier ist eine andere Möglichkeit, dies zu demonstrieren. Hier sind zwei Doppel, die sehr nahe beieinander liegen.

x0 = exp(1)
x1 = x0 + eps(x0)

vpa(x0)und vpa(x1)sollte Ausgaben erzeugen, die sich stark über die 16. Ziffer hinaus unterscheiden. Sie sollten jedoch nicht in der Lage sein, einen doppelten Wert zu erstellen x, vpa(x)der eine Dezimaldarstellung erzeugt, die zwischen vpa(x0)und liegt vpa(x1).

(UPDATE: Amro weist darauf hin, dass Sie fprintf('%bx\n', x)eine genaue Darstellung des zugrunde liegenden Binärwerts im Hex-Format anzeigen können. Sie können dies verwenden, um die Literalzuordnung zu demselben Double zu bestätigen.)

Ich vermute vpa(), dass es sich so verhält, weil es seine Eingaben als exakte Werte behandelt und andere Matlab-Typen aus der Symbolic Toolbox polymorph unterstützt, die präziser als doppelt sind. Diese Werte müssen mit anderen Mitteln als numerischen Literalen initialisiert werden, weshalb sym()eine Zeichenfolge als Eingabe verwendet wird und vpa(exp(1))sich von dieser unterscheidet vpa(sym('exp(1)')).

Sinn ergeben? Entschuldigung für die Langeweile.

(Hinweis: Ich habe keine symbolische Toolbox, daher kann vpa()ich mich nicht selbst testen .)

Andrew Janke
quelle
1
Aha! Also habe ich zufällig die exakte Dezimalerweiterung eines Binärwerts als meine Testnummer verwendet. Es macht jetzt alles Sinn! Ich bin mir nicht sicher, wie ich das übersehen habe ... vielleicht lag es an Schlafmangel, da meine Tochter zahnt und mich die ganze Nacht wach hält. ;)
Gnovice
@Mikhail: Nein, obwohl es einige echte MathWorks-Leute gibt, die herumhängen, wie Loren und MatlabDoug. Ich habe gerade Zeit damit verbracht, Matlab-Entwicklungsplattformen zu entwickeln, die C, Java und COM mithilfe der Unterstützung für externe Schnittstellen von Matlab integrieren. Gute Möglichkeit, sich mit Ihren Datentypen und Matlab-Interna vertraut zu machen.
Andrew Janke
4
Sie können auch die hexadezimale Darstellung von Variablen mit doppelter Genauigkeit überprüfen. Versuchen Sie fprintf('%bx\n', exp(1), 2.7182818284590455, 2.71828182845904559999999999)und sie werden alle die gleiche 64-Bit-Darstellung zurückgeben4005bf0a8b14576a
Amro
@ Amro: Schön! Ich wusste nichts über% bx. Dies bestätigt auch, dass eps () einen Ein-Bit-Unterschied ergibt. fprintf('%bx\n', exp(1), exp(1)+eps(exp(1)))(zumindest für diesen Wert). Dies in meiner Antwort beiseite zu nehmen.
Andrew Janke
1
@ AndrewJanke: Es tut mir leid, diesen alten Thread wiederzubeleben, aber es scheint, dass VPA (oder genau SYM, das von VPA aufgerufen wird) noch schwieriger war als wir dachten. SYM versucht standardmäßig, Gleitkommazahlen in eine "rationale" Form umzuwandeln, um Rundungsfehler bei Zwischenbewertungen zu kompensieren ... Mehr dazu in der Diskussion hier
Amro
3

zuerst :

Es scheint, dass sprintf und fprintf in verschiedenen Versionen von MATLAB ein unterschiedliches Verhalten aufweisen, beispielsweise in MATLAB 2018 a

num=2.7182818284590666666666;    
sprintf('%0.70f', num)
ans =
'2.7182818284590668511668809514958411455154418945312500000000000000000000'

zweite :

Gleitkommazahlen

MATLAB® repräsentiert Gleitkommazahlen im Format mit doppelter oder einfacher Genauigkeit. Die Standardeinstellung ist doppelte Genauigkeit. Sie können jedoch mit einer einfachen Konvertierungsfunktion eine beliebige einfache Genauigkeit festlegen.

Gleitkomma mit doppelter Präzision

MATLAB erstellt den Datentyp mit doppelter Genauigkeit (oder doppelter Genauigkeit) gemäß IEEE® Standard 754 für doppelte Genauigkeit. Jeder als Double gespeicherte Wert erfordert 64 Bit, formatiert wie in der folgenden Tabelle gezeigt:

Bits: 63
Verwendung: Vorzeichen (0 = positiv, 1 = negativ)

Bits: 62 bis 52 Verwendung: Exponent, voreingenommen um 1023

Bits: 51 bis 0 Verwendung: Bruch f der Zahl 1.f.

Weitere Informationen finden Sie unter diesem Link

Zwischen 252 = 4.503.599.627.370.496 und 253 = 9.007.199.254.740.992 sind die darstellbaren Zahlen genau die ganzen Zahlen. Für den nächsten Bereich von 253 bis 254 wird alles mit 2 multipliziert, sodass die darstellbaren Zahlen die geraden usw. sind. Umgekehrt beträgt der Abstand für den vorherigen Bereich von 2 ^ 51 bis 2 ^ 52 0,5 usw.

Der Abstand als Bruchteil der Zahlen im Bereich von 2 ^ n bis 2 ^ n + 1 beträgt 2 ^ n - 52. Der maximale relative Rundungsfehler beim Runden einer Zahl auf die nächste darstellbare Zahl (das Maschinen-Epsilon) beträgt daher 2 ^ −53.

In Ihrem Fall, in dem n = 1 (2 ^ 1 <= num <= 2 ^ 2) ist, beträgt der Abstand 2 ^ -51.

Ich denke, es ist sicher anzunehmen, dass Sprintf- und Sprintf-Algorithmen zum Anzeigen von Zahlen schwierig sind und der MATLAB Double-Typ auf dem IEEE-Standard basiert.


über VPA:

vpa verwendet Guard Digits, um die Präzision aufrechtzuerhalten

Der Wert der Ziffernfunktion gibt die Mindestanzahl der verwendeten signifikanten Ziffern an. Intern kann vpa mehr Ziffern verwenden, als angegeben. Diese zusätzlichen Ziffern werden als Schutzziffern bezeichnet, da sie in nachfolgenden Berechnungen vor Rundungsfehlern schützen.

Numerisch ungefähr 1/3 mit vier signifikanten Ziffern.

a = vpa(1/3, 4)
a =
0.3333

Schätzen Sie das Ergebnis mit 20 Ziffern ein. Das Ergebnis zeigt, dass die Toolbox bei der Berechnung von a intern mehr als vier Ziffern verwendet hat. Die letzten Ziffern im Ergebnis sind aufgrund des Rundungsfehlers falsch.

vpa(a, 20)
ans =
0.33333333333303016843

Das Problem, auf das Sie möglicherweise stoßen, ist auf den Abstand, den Gaurd-Ziffern-Algorithmus und das Rundungsproblem zurückzuführen.

zum Beispiel mit matlab 2018 a:

 sprintf('%0.28f', 8.0)
 ans =
 '8.0000000000000000000000000000'

aber:

sprintf('%0.28f', 8.1)
ans =
'8.0999999999999996447286321199'

Da die Zahl zwischen 2 ^ 3 und 2 ^ 4 liegt, beträgt der Abstand 2 ^ -49 (= 1,77 e-15), sodass die Zahl bis zur 15. Dezimalstelle und gültig ist

sprintf('%0.28f', 64.1)
ans =
'64.0999999999999943156581139192'

Da die Zahl zwischen 2 ^ 6 und 2 ^ 7 liegt, beträgt der Abstand 2 ^ -46 (= 1,42 e-14), sodass die Zahl bis zur 14. Dezimalstelle gültig ist

Hadi
quelle