Ist es im folgenden Beispiel möglich, eine Division durch 0 (oder unendlich) zu erhalten?
public double calculation(double a, double b)
{
if (a == b)
{
return 0;
}
else
{
return 2 / (a - b);
}
}
Im Normalfall wird dies natürlich nicht der Fall sein. Was aber, wenn a
und b
sehr nahe beieinander liegen, kann (a-b)
dies 0
auf die Genauigkeit der Berechnung zurückzuführen sein?
Beachten Sie, dass diese Frage für Java gilt, aber ich denke, dass sie für die meisten Programmiersprachen gilt.
Antworten:
Ist in Java
a - b
niemals gleich0
ifa != b
. Dies liegt daran, dass Java IEEE 754-Gleitkommaoperationen vorschreibt, die denormalisierte Zahlen unterstützen. Aus der Spezifikation :Wenn eine FPU mit denormalisierten Zahlen arbeitet , kann das Subtrahieren ungleicher Zahlen niemals Null ergeben (im Gegensatz zur Multiplikation). Siehe auch diese Frage .
Für andere Sprachen kommt es darauf an. In C oder C ++ ist beispielsweise die IEEE 754-Unterstützung optional.
Dies gesagt wird , ist es möglich , die für die Expression
2 / (a - b)
zu Überlauf, zum Beispiel mita = 5e-308
undb = 4e-308
.quelle
(a,b) = (3,1)
=>2/(a-b) = 2/(3-1) = 2/2 = 1
Ob dies mit IEEE-Gleitkomma zutrifft, weiß ich nichtWas ist als Problemumgehung mit den folgenden?
Auf diese Weise sind Sie in keiner Sprache auf IEEE-Unterstützung angewiesen.
quelle
a=b
, sollten Sie nicht zurückkehren0
. Wenn Sie0
in IEEE 754 durch dividieren, erhalten Sie unendlich, keine Ausnahme. Sie vermeiden das Problem, daher ist die Rückkehr0
ein Fehler, der darauf wartet, passiert zu werden. Überlegen Sie1/x + 1
. Wennx=0
, das in Folge würde1
, nicht der richtige Wert: unendlich.0
ist nicht wirklich das Problem. Dies ist, was das OP in der Frage tut. Sie können eine Ausnahme setzen oder was auch immer für die Situation in diesem Teil des Blocks angemessen ist. Wenn Sie nicht gerne zurückkehren0
, sollte dies eine Kritik an der Frage sein. Wenn Sie das tun, was das OP getan hat, ist dies sicherlich keine Ablehnung der Antwort. Diese Frage hat nichts mit weiteren Berechnungen nach Abschluss der angegebenen Funktion zu tun. Nach allem, was Sie wissen, müssen die Anforderungen des Programms zurückgegeben werden0
.Sie würden unabhängig vom Wert von keine Division durch Null erhalten
a - b
, da die Gleitkommadivision durch 0 keine Ausnahme auslöst. Es gibt die Unendlichkeit zurück.Der einzige Weg
a == b
, um true zurückzugeben, ist ifa
undb
enthält genau die gleichen Bits. Wenn sie sich nur um das niedrigstwertige Bit unterscheiden, ist der Unterschied zwischen ihnen nicht 0.EDIT:
Wie Bathseba richtig kommentierte, gibt es einige Ausnahmen:
"Keine Zahl vergleicht" false mit sich selbst, weist jedoch identische Bitmuster auf.
-0.0 ist definiert, um true mit +0.0 zu vergleichen, und ihre Bitmuster sind unterschiedlich.
Wenn also beide
a
undb
sindDouble.NaN
, erreichen Sie die else-Klausel, aber daNaN - NaN
auch zurückgegeben wirdNaN
, werden Sie nicht durch Null dividieren.quelle
Es gibt keinen Fall, in dem hier eine Division durch Null stattfinden kann.
Der SMT Solver Z3 unterstützt präzise IEEE-Gleitkomma-Arithmetik. Lassen Sie uns Z3 bitten, Zahlen zu finden
a
undb
so, dassa != b && (a - b) == 0
:Das Ergebnis ist
UNSAT
. Es gibt keine solchen Nummern.Mit der obigen SMTLIB-Zeichenfolge kann Z3 auch einen beliebigen Rundungsmodus auswählen (
rm
). Dies bedeutet, dass das Ergebnis für alle möglichen Rundungsmodi gilt (von denen es fünf gibt). Das Ergebnis beinhaltet auch die Möglichkeit, dass eine der im Spiel befindlichen VariablenNaN
unendlich ist.a == b
wird alsfp.eq
Qualität so implementiert+0f
und-0f
gleich vergleichen. Der Vergleich mit Null wird ebenfalls mit implementiertfp.eq
. Da die Frage darauf abzielt, eine Division durch Null zu vermeiden, ist dies der geeignete Vergleich.Wenn der Gleichheitstest wurde mit bitweise Gleichheit, umgesetzt
+0f
und-0f
ein Weg gewesen wäre , uma - b
Null. Eine falsche Vorgängerversion dieser Antwort enthält Modendetails zu diesem Fall für Neugierige.Z3 Online unterstützt die FPA-Theorie noch nicht. Dieses Ergebnis wurde unter Verwendung des neuesten instabilen Zweigs erhalten. Es kann mit den .NET-Bindungen wie folgt reproduziert werden:
Mit Z3 IEEE Float Fragen zu beantworten ist schön , weil es schwer ist , Fälle zu übersehen (wie
NaN
,-0f
,+-inf
) und Sie können beliebige Fragen stellen. Keine Notwendigkeit, Spezifikationen zu interpretieren und zu zitieren. Sie können sogar gemischte Float- und Integer-Fragen stellen, z. B. "Ist dieser bestimmteint log2(float)
Algorithmus korrekt?".quelle
Die bereitgestellte Funktion kann tatsächlich unendlich zurückgeben:
Die Ausgabe ist
Result: -Infinity
.Wenn das Ergebnis der Division zu groß ist, um in einem Doppel gespeichert zu werden, wird unendlich zurückgegeben, selbst wenn der Nenner nicht Null ist.
quelle
In einer Gleitkommaimplementierung, die IEEE-754 entspricht, kann jeder Gleitkommatyp Zahlen in zwei Formaten enthalten. Eins ("normalisiert") wird für die meisten Gleitkommawerte verwendet, aber die zweitkleinste Zahl, die es darstellen kann, ist nur ein kleines bisschen größer als die kleinste, und daher ist der Unterschied zwischen ihnen nicht in demselben Format darstellbar. Das andere ("denormalisierte") Format wird nur für sehr kleine Zahlen verwendet, die im ersten Format nicht darstellbar sind.
Schaltungen zur effizienten Handhabung des denormalisierten Gleitkommaformats sind teuer, und nicht alle Prozessoren enthalten sie. Einige Prozessoren bieten die Wahl, ob Operationen mit wirklich kleinen Zahlen viel langsamer sind als Operationen mit anderen Werten, oder ob der Prozessor Zahlen, die für ein normalisiertes Format zu klein sind, einfach als Null betrachtet.
Die Java-Spezifikationen implizieren, dass Implementierungen das denormalisierte Format unterstützen sollten, selbst auf Computern, auf denen Code langsamer ausgeführt wird. Auf der anderen Seite ist es möglich, dass einige Implementierungen Optionen bieten, mit denen Code schneller ausgeführt werden kann, wenn die Werte leicht schlampig behandelt werden, was für die meisten Zwecke viel zu klein wäre, um eine Rolle zu spielen (in Fällen, in denen Werte zu klein sind, um eine Rolle zu spielen) Es kann ärgerlich sein, wenn Berechnungen mit ihnen zehnmal so lange dauern wie wichtige Berechnungen. In vielen praktischen Situationen ist es daher nützlicher, auf Null zu gehen (langsame, aber genaue Arithmetik).
quelle
In früheren Zeiten vor IEEE 754 war es durchaus möglich, dass a! = B nicht ab! = 0 implizierte und umgekehrt. Dies war einer der Gründe, IEEE 754 überhaupt erst zu schaffen.
Mit IEEE 754 ist dies fast garantiert. C- oder C ++ - Compiler dürfen eine Operation mit höherer Genauigkeit als erforderlich ausführen. Wenn also a und b keine Variablen, sondern Ausdrücke sind, bedeutet (a + b)! = C nicht (a + b) - c! = 0, da a + b einmal mit höherer Genauigkeit und einmal ohne berechnet werden könnte höhere Präzision.
Viele FPUs können in einen Modus umgeschaltet werden, in dem sie keine denormalisierten Zahlen zurückgeben, sondern durch 0 ersetzen. In diesem Modus sind a und b winzige normalisierte Zahlen, bei denen die Differenz kleiner als die kleinste normalisierte Zahl, aber größer als 0 ist, a ! = b garantiert auch nicht a == b.
"Niemals Gleitkommazahlen vergleichen" ist Frachtkultprogrammierung. Unter den Menschen, die das Mantra "Sie brauchen ein Epsilon" haben, haben die meisten keine Ahnung, wie sie dieses Epsilon richtig auswählen sollen.
quelle
Ich kann mir einen Fall vorstellen , in dem Sie dies möglicherweise verursachen können. Hier ist ein analoges Beispiel in Basis 10 - das würde natürlich in Basis 2 passieren.
Gleitkommazahlen werden mehr oder weniger in wissenschaftlicher Notation gespeichert - das heißt, anstatt 35,2 zu sehen, würde die gespeicherte Zahl eher 3,52e2 entsprechen.
Stellen Sie sich der Einfachheit halber vor, wir haben eine Gleitkommaeinheit, die in Basis 10 arbeitet und eine Genauigkeit von 3 Stellen hat. Was passiert, wenn Sie 9,99 von 10,0 abziehen?
1.00e2-9.99e1
Verschieben, um jedem Wert den gleichen Exponenten zu geben
1.00e2-0.999e2
Auf 3 Stellen runden
1.00e2-1.00e2
Oh oh!
Ob dies letztendlich passieren kann, hängt vom FPU-Design ab. Da der Exponentenbereich für ein Double sehr groß ist, muss die Hardware irgendwann intern gerundet werden. Im obigen Fall verhindert jedoch nur eine zusätzliche Ziffer intern ein Problem.
quelle
strictfp
nicht aktiviert ist. Berechnungen können Werte liefern, die zu klein sind,double
aber in einen Gleitkommawert mit erweiterter Genauigkeit passen.strictfp
beeinflusst nur die Werte von "Zwischenergebnissen", und ich zitiere aus docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.4 .a
undb
sinddouble
Variablen, keine Zwischenergebnisse, daher sind ihre Werte Werte mit doppelter Genauigkeit, also Vielfache von 2 ^ -1074. Die Subtraktion dieser beiden Werte mit doppelter Genauigkeit ist folglich ein Vielfaches von 2 ^ -1074, so dass der breitere Exponentenbereich die Eigenschaft ändert, dass die Differenz 0 ist, wenn a == b.Sie sollten niemals Floats oder Doubles vergleichen, um die Gleichheit zu gewährleisten. weil Sie nicht wirklich garantieren können, dass die Nummer, die Sie dem Float oder Double zuweisen, genau ist.
Um Floats auf Gleichheit zu vergleichen, müssen Sie überprüfen, ob der Wert "nahe genug" an demselben Wert liegt:
quelle
abs(first - second) < error
(oder<= error
) einfacher und prägnanter.Die Division durch Null ist undefiniert, da die Grenze von positiven Zahlen gegen unendlich geht, die Grenze von negativen Zahlen gegen negative unendlich.
Ich bin mir nicht sicher, ob dies C ++ oder Java ist, da es kein Sprach-Tag gibt.
quelle
Das Kernproblem besteht darin, dass die Computerdarstellung eines Doppels (auch bekannt als float oder reelle Zahl in mathematischer Sprache) falsch ist, wenn Sie "zu viele" Dezimalstellen haben, z. B. wenn Sie mit double arbeiten, das nicht als numerischer Wert geschrieben werden kann ( pi oder das Ergebnis von 1/3).
A == b kann also nicht mit einem doppelten Wert von a und b gemacht werden. Wie geht man mit a == b um, wenn a = 0,333 und b = 1/3? Abhängig von Ihrem Betriebssystem gegen FPU gegen Anzahl gegen Sprache gegen Anzahl von 3 nach 0 haben Sie wahr oder falsch.
Wenn Sie auf einem Computer eine "Doppelwertberechnung" durchführen, müssen Sie sich mit der Genauigkeit befassen. Statt dies zu tun
a==b
, müssen Sie dies tunabsolute_value(a-b)<epsilon
, und epsilon ist relativ zu dem, was Sie zu diesem Zeitpunkt in Ihrem Algorithmus modellieren. Sie können nicht für alle Doppelvergleiche einen Epsilon-Wert haben.Kurz gesagt, wenn Sie a == b eingeben, haben Sie einen mathematischen Ausdruck, der auf einem Computer nicht übersetzt werden kann (für jede Gleitkommazahl).
PS: Summen, alles, was ich hier beantworte, ist noch mehr oder weniger in anderen Antworten und Kommentaren enthalten.
quelle
Basierend auf der Antwort von @malarres und dem Kommentar von @Taemyr ist hier mein kleiner Beitrag:
Mein Punkt ist zu sagen: Der einfachste Weg zu wissen, ob das Ergebnis der Division nan oder inf ist, ist tatsächlich die Division durchzuführen.
quelle