Ist es möglich, den Exponenten oder den Signifikanten eines Floats zu einem anderen Float (Python) zu erzwingen?

8

Das ist eine interessante Frage, die ich neulich durcharbeiten wollte. Ist es möglich, den Signifikanden oder Exponenten von einem float zu einem anderen float in Python zu machen?

Die Frage tritt auf, weil ich versucht habe, einige Daten neu zu skalieren, so dass Min und Max mit einem anderen Datensatz übereinstimmen. Allerdings waren meine neu skalierten Daten etwas aus (nach etwa 6 Dezimalstellen) und es war genug, um Probleme auf der ganzen Linie zu verursachen.

Um eine Idee zu geben, habe ich f1 und f2 ( type(f1) == type(f2) == numpy.ndarray ). Ich möchte np.max(f1) == np.max(f2) and np.min(f1) == np.min(f2) . Um dies zu erreichen, mache ich:

%Vor%

Das Ergebnis (nur als Beispiel) wäre:

%Vor%

Mein erster Gedanke ist, dass das Erzwingen des Exponenten von float die richtige Lösung wäre. Ich konnte nicht viel finden, also habe ich einen Workaround für meine Bedürfnisse gemacht:

%Vor%

jetzt np.max(f2) == np.max(f1)

Aber gibt es einen besseren Weg? Habe ich etwas falsch gemacht? Ist es möglich, ein float so umzuformen, dass es einem anderen float ähnlich ist (Exponent oder andere Mittel)?

EDIT: wie vorgeschlagen, verwende ich jetzt:

%Vor%

Während meine obige Lösung funktioniert (für meine Anwendung), interessiert mich, ob es eine Lösung gibt, die die float irgendwie dazu bringen kann, den gleichen Exponenten und / oder Signifikanten zu haben, so dass die Zahlen identisch werden.

    
Jason 28.01.2016, 08:37
quelle

4 Antworten

2

TL; DR

Verwenden Sie

%Vor%

und stellen Sie sicher, dass Sie doppelte Genauigkeit verwenden, vergleichen Sie Gleitkommazahlen mit absoluten oder relativen Unterschieden, vermeiden Sie Rundungen zum Anpassen (oder Vergleichen) von Gleitkommazahlen und legen Sie die zugrunde liegenden Komponenten von Gleitkommazahlen nicht manuell fest .

Details

Dies ist kein sehr einfacher zu reproduzierender Fehler, wie Sie festgestellt haben. Beim Arbeiten mit Gleitkommazahlen besteht jedoch ein Fehler. Wenn Sie beispielsweise 1 000 000 000 + 0 . 000 000 000 1 addieren, erhalten Sie 1 000 000 000 . 000 000 000 1 , aber das sind zu viele signifikante Zahlen, selbst bei doppelter Genauigkeit (was ungefähr 15 signifikante Zahlen ), so dass die Nachkommastelle entfällt. Darüber hinaus können einige "kurze" Zahlen nicht genau dargestellt werden, wie in @ Kevins Antwort erwähnt. Siehe z. B. hier , um weitere Informationen zu erhalten. (Suchen Sie nach etwas wie "Floating Point Trunkation Roundoff Error" für noch mehr.)

Hier ist ein Beispiel, das ein Problem zeigt:

%Vor%

Ausgabe

%Vor%

Nach @Mark Dickinsons Kommentar , ich habe 32-Bit Fließkomma verwendet. Dies entspricht dem von Ihnen gemeldeten Fehler, einem relativen Fehler von etwa 10 ^ -7, um den 7. signifikanten Wert herum.

%Vor%

Nach dtype=np.float64 geht es besser, aber es ist immer noch nicht perfekt. Das obige Programm gibt dann

%Vor%

Das ist nicht perfekt, aber im Allgemeinen nah genug. Wenn Sie Fließkommazahlen vergleichen, möchten Sie fast nie eine strikte Gleichheit verwenden, da oben kleine Fehler auftreten können. Stattdessen subtrahieren Sie eine Zahl von der anderen und prüfen Sie, ob die absolute Differenz kleiner als eine Toleranz ist, und / oder sehen Sie sich den relativen Fehler an. Siehe beispielsweise numpy.isclose .

Wenn wir auf Ihr Problem zurückkommen, scheint es möglich zu sein, es besser zu machen. Immerhin hat f2 den Bereich von 0 bis 1, daher sollte es möglich sein, das Maximum in f1 zu replizieren. Das Problem kommt in der Zeile

%Vor%

Wenn ein Element von f2 1 ist, machst du viel mehr, als nur 1 mit dem Maximum von f1 zu multiplizieren, was zu der Möglichkeit führt, dass arithmetische Gleitkommafehler auftreten. Beachten Sie, dass Sie die Klammern f2*(np.max(f1)-np.min(f1)) bis f2*np.max(f1) - f2*np.min(f1) multiplizieren und dann die resultierende - f2*np.min(f1) + np.min(f1) zu np.min(f1)*(f2-1) giving

faktorisieren können %Vor%

Wenn also ein Element von f2 1 ist, haben wir 1*np.max(f1) - np.min(f1)*0 . Umgekehrt, wenn ein Element von f2 0 ist, haben wir 0*np.max(f1) - np.min(f1)*1 . Die Zahlen 1 und 0 können genau dargestellt werden, so dass keine Fehler auftreten sollten.

Das geänderte Programm gibt

aus %Vor%

d. wie gewünscht.

Trotzdem würde ich immer noch dringend empfehlen, nur einen ungenauen Gleitkommavergleich (mit engen Grenzen, wenn Sie brauchen) zu verwenden, es sei denn, Sie haben einen sehr guten Grund, dies nicht zu tun. Es gibt alle Arten von subtilen Fehlern, die bei der Fließkomma-Arithmetik auftreten können, und die einfachste Möglichkeit, sie zu vermeiden, besteht darin, nie einen genauen Vergleich zu verwenden.

Ein alternativer Ansatz zu dem oben Erwähnten, der vielleicht vorzuziehen ist, würde sein, beide -Arrays auf zwischen 0 und 1 zu skalieren. Dies könnte die am besten geeignete Form sein, um innerhalb des Programms zu verwenden. (Und beide Arrays könnten bei Bedarf mit einem Skalierungsfaktor wie dem ursprünglichen Bereich von f1 multipliziert werden.)

Wenn Sie das Problem mit Rundungen lösen, würde ich nicht dies empfehlen. Das Problem mit der Rundung - abgesehen von der Tatsache, dass es unnötig ist, reduziert die Genauigkeit Ihrer Daten - ist, dass sich sehr nahe Zahlen in verschiedene Richtungen runden können. ZB

%Vor%

Ausgabe

%Vor%

Dies hängt mit der Tatsache zusammen, dass es zwar üblich ist, Zahlen zu diskutieren, die mit so vielen signifikanten Zahlen übereinstimmen, dass die Leute sie jedoch nicht so auf dem Computer vergleichen. Sie berechnen die Differenz und teilen sie dann durch die richtige Zahl (für einen relativen Fehler).

Zu Mantissen und Exponenten, siehe math.frexp und math.ldexp , dokumentiert hier . Ich würde jedoch nicht empfehlen, diese selbst zu setzen (beachte zwei Zahlen, die sehr nahe sind, aber unterschiedliche Exponenten haben, zum Beispiel - willst du wirklich die Mantisse setzen). Viel besser, einfach das Maximum von f2 explizit auf das Maximum von f1 zu setzen, wenn Sie sicherstellen wollen, dass die Zahlen genau gleich sind (und ebenso für das Minimum).

    
TooTone 07.02.2016, 22:30
quelle
7

Es hängt davon ab, was Sie mit "Mantisse" meinen.

Intern werden Floats in der wissenschaftlichen Notation in Base 2 gespeichert. Wenn Sie also die base 2 Mantisse meinen, ist es eigentlich sehr einfach: Multiplizieren Sie einfach oder teilen Sie sie durch Zweierpotenzen (nicht Potenzen von 10) ), und die Mantisse wird gleich bleiben (vorausgesetzt, der Exponent wird nicht außer Reichweite geraten; wenn dies der Fall ist, wirst du gegen unendlich oder Null geklemmt, oder möglicherweise in denormalzahlen abhängig von den architektonischen Details). Es ist wichtig zu verstehen, dass die Dezimal-Erweiterungen nicht übereinstimmen, wenn Sie auf Potenzen von zwei skalieren. Es ist die binäre Erweiterung, die mit dieser Methode beibehalten wird.

Aber wenn Sie die Mantisse der Basis 10 meinen, dann ist das mit Floats nicht möglich, weil der skalierte Wert möglicherweise nicht genau darstellbar ist. Zum Beispiel kann 1.1 in der Basis 2 (mit einer endlichen Anzahl von Ziffern) nicht genau so dargestellt werden, wie 1/3 nicht in der Basis 10 (mit einer endlichen Anzahl von Ziffern) dargestellt werden kann. Eine Neuskalierung von 11 um 1/10 kann daher nicht exakt durchgeführt werden:

%Vor%

Sie können Letzteres jedoch mit decimal durchführen. Dezimalstellen arbeiten in der Basis 10 und verhalten sich wie erwartet in Bezug auf die Neuskalierung der Basis 10. Sie bieten auch eine ziemlich große Menge an spezialisierten Funktionen, um verschiedene Arten von Präzisionsverlusten zu erkennen und zu behandeln. Aber die Dezimalstellen profitieren nicht von NumPy-Beschleunigungen . Wenn Sie also mit sehr vielen Daten arbeiten müssen, können sie dies tun nicht effizient genug für Ihren Anwendungsfall. Da NumPy von der Hardware-Unterstützung für Fließkommazahl abhängig ist und die meisten (alle?) Modernen Architekturen keine Hardware-Unterstützung für die Basis 10 bieten, ist dies nicht einfach zu beheben.

    
Kevin 07.02.2016 20:48
quelle
3

Ersetzen Sie die zweite Zeile durch

%Vor%

Erklärung: Es gibt zwei Stellen, an denen sich der Unterschied einschleichen könnte:

Schritt 1) f2 = (f2-np.min(f2))/(np.max(f2)-np.min(f2))

Wenn Sie np.min(f2) und np.max(f2) untersuchen, erhalten Sie genau 0 und 1 oder etwas wie 1.0000003?

Schritt 2) f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1)

Der Ausdruck wie (a-b)+b erzeugt aufgrund eines Rundungsfehlers nicht immer genau a . Der vorgeschlagene Ausdruck ist etwas stabiler.

Für eine sehr detaillierte Erklärung, sehen Sie bitte Was jeder Informatiker über Fließkomma-Arithmetik wissen sollte von David Goldberg p>     

Paisa Seeluangsawat 12.02.2016 19:00
quelle
-2

Hier ist eins mit Dezimalstellen

%Vor%

BEARBEITEN ** Ich bin etwas verwirrt darüber, warum ich so viel negatives Feedback bekomme, also ist hier noch eine andere Lösung, die keine Dezimalstellen verwendet:

%Vor%     
bmbigbang 10.02.2016 08:48
quelle