Maschinenepsilon
-
Hallo, die Aufgabe war as Maschinenepsilon zu bestimmen. Ich habe das so gelöst, kommt auch 10^-16 raus.
#include <iostream> using namespace std; int main () { double eps = 0.1; double k = 1 + eps; while(k>1) { eps *= 0.1; k = 1 + eps; } cout<<eps<<endl; }
Meine Frage: warum kommt eine viel kleinere Zahl raus, wenn man 1+epsilon nicht vorher speichert?
-
Namal schrieb:
kommt auch 10^-16 raus.
Es würde mich doch sehr wundern, wenn das Maschinenepsilon eine Zehnerpotenz wäre.
Meine Frage: warum kommt eine viel kleinere Zahl raus, wenn man 1+epsilon nicht vorher speichert?
Was genau meinst du? Wir können nicht in deinen Kopf gucken.
while(1+epsilon > 1)
? Es kann (und ist auf x87 auch so), dass die eigentlichen Fließkommaberechnungen in höherer Genauigkeit (bei x87 mit 80-Bit) durchgeführt werden. Wenn du das Ergebnis also direkt gegen 1 prüfst, solange es noch im Rechenwerk steht, bekommst du ein anderes Ergebnis.P.S.: Außerdem gibst du als Ergebnis den Wert aus, der bei Addition zu 1 gerade keinen Unterschied mehr macht. Bist du da nicht einen Schritt zu weit gegangen?
-
[quote="SeppJ"]
Namal schrieb:
kommt auch 10^-16 raus.
Also laut wikipedia is epsilon 1.1*10^-16
http://de.wikipedia.org/wiki/Maschinengenauigkeit
Ich mein wenn ich vorher nicht addiere, dann sieht es so aus
int main () { double eps = 0.1; while(1+eps>1) { eps *= 0.1; } cout<<eps<<endl; }
Und ich bekomme 10^-20 raus.
Bist du da nicht einen Schritt zu weit gegangen?
Tatsache!
-
Namal schrieb:
Namal schrieb:
kommt auch 10^-16 raus.
Also laut wikipedia is epsilon 1.1*10^-16
Ich glaube, da passt was nicht:
https://ideone.com/2uhaLDein Genauigkeitsproblem hat sich aber gelöst, oder? Das ist genau das was ich erklärt habe.
-
SeppJ schrieb:
Dein Genauigkeitsproblem hat sich aber gelöst, oder? Das ist genau das was ich erklärt habe.
Also teilweise, denn ich kann es nich so recht begreifen, die Operation 1+epsilon in der whileschleife wird doch auch immer wieder gespeichert?
#include <iostream> using namespace std; int main () { double eps = 0.1, epsalt; double k = 1 + eps; while(k>1) { epsalt = eps; eps *= 0.5; k = 1 + eps; } cout<<epsalt<<endl; }
War auch dumm von mir, 0.1 zu nehmen
. Nunja jetzt hab ich 1,7...*10^-16
-
SeppJ schrieb:
Ich glaube, da passt was nicht:
Epsilon hat nichts mit der kleinsten positiven von Null verschiedenen darstellbaren Zahl zu tun.
-
camper schrieb:
SeppJ schrieb:
Ich glaube, da passt was nicht:
Epsilon hat nichts mit der kleinsten positiven von Null verschiedenen darstellbaren Zahl zu tun.
Das ist nicht das ,was ich sagen wollte. Das Epsilon beim üblichen double ist ca. 1.1e-16, nicht 1.0e-16, das war falsch.
Namal schrieb:
War auch dumm von mir, 0.1 zu nehmen
. Nunja jetzt hab ich 1,7...*10^-16
Passt auch nicht. Dein Anfangswert ist ungünstig, da 0.1 einen Primfaktor 5 drin hat. Fang am besten mit 1 an.
-
SeppJ schrieb:
Fang am besten mit 1 an.
Dann bekomm ich aber 2.22045e-16 raus
Mit 0.001 hab ich 1.13687e-16
-
camper schrieb:
SeppJ schrieb:
Ich glaube, da passt was nicht:
Epsilon hat nichts mit der kleinsten positiven von Null verschiedenen darstellbaren Zahl zu tun.
Sondern? Was ist dessen Zweck?
-
out schrieb:
camper schrieb:
SeppJ schrieb:
Ich glaube, da passt was nicht:
Epsilon hat nichts mit der kleinsten positiven von Null verschiedenen darstellbaren Zahl zu tun.
Sondern? Was ist dessen Zweck?
epsilon ist die Differenz zwischen eins und der nächst größeren exakt darstellbaren Zahl. Mithin ein Maß für die maximale relative Genauigkeit einer normalisierten Zahl. Definitionsgemäß muss es sich um eine Potenz der jeweils verwendeten Zahlenbasis handeln (typischerweise 2).
-
Ich denke, der Umgang mit Fliesskommazahlen, deren binäre Genauigkeit der Darstellung und der zwingend notwendige Umgang mit einer Toleranzgrösse eps bei allen Rechenoperationen - vor allem bei Vergleichen - ist vielen nicht klar. Da kann man leicht Fehler machen.
Merke: Computer arbeiten mit Binärzahlen auf der Basis 2 - der Mensch denkt aber im Dezimalsystem, weil er das in der Schule so gelernt hat. Aber auch dezimal ist nicht alles geritzt: 1/3 = 0.3333 Peridode
-
Ich denke was Namal gemeint hat, ist: Es gibt unterschiedliche Ergebnisse wenn man zum einen schreibt:
if( 1.+eps > 1. )
bzw.
double speicher = 1.+eps; if( speicher > 1. )
das liegt meines Erachtens daran, dass der Akkumulator der CPU (bzw. FPU), der das Ergebnis der Summe '1.+eps' aufnimmt, eine höhere Genauigkeit hat als der verwendete Datentyp (hier double). Beim Speichern in eine Variable (beim Typ double vielleicht, bei float sicher) wird dann gerundet und dabei geht dann die Differenz zu 1.0 verloren. Besonders deutlich wird es, wenn man double durch float tauscht. Der Akkumulator ist ja bei float derselbe.
Folgende Demo zeigt das:
#include <iostream> #include <limits> #define MIT_SPEICHERN template< typename T > void zeige_epsilon() { using namespace std; T eps = 1.0; # ifdef MIT_SPEICHERN T speicher; for( T next_eps = eps; speicher =(T(1.0) + next_eps), speicher > T(1.0); next_eps = eps/2 ) # else for( T next_eps = eps; (T(1.0) + next_eps) > T(1.0); next_eps = eps/2 ) # endif eps = next_eps; cout << "Mein epsilon : " << eps << endl; cout << "numeric_limits< " << typeid(T).name() << " >::epsilon: " << numeric_limits< T >::epsilon() << endl; cout << "Beide Epsilons sind " << (eps == numeric_limits< T >::epsilon()? "gleich": "verschieden") << "\n" << endl; } int main() { zeige_epsilon< double >(); zeige_epsilon< float >(); zeige_epsilon< long double >(); return 0; }
gibt die Ausgabe:
Mein epsilon : 2.22045e-016 numeric_limits< double >::epsilon: 2.22045e-016 Beide Epsilons sind gleich Mein epsilon : 1.19209e-007 numeric_limits< float >::epsilon: 1.19209e-007 Beide Epsilons sind gleich Mein epsilon : 2.22045e-016 numeric_limits< long double >::epsilon: 2.22045e-016 Beide Epsilons sind gleich
lässt man Zeile 4 weg, so erhält man statt dessen:
Mein epsilon : 2.22045e-016 numeric_limits< double >::epsilon: 2.22045e-016 Beide Epsilons sind gleich Mein epsilon : 2.22045e-016 numeric_limits< float >::epsilon: 1.19209e-007 Beide Epsilons sind verschieden Mein epsilon : 2.22045e-016 numeric_limits< long double >::epsilon: 2.22045e-016 Beide Epsilons sind gleich
In meinem Fall macht es bei double keinen Unterschied. Ich hatte aber schon einen PC, wo selbst bei double unterschiedliche Ergebnisse gekommen wären.
Die Moral von der Geschicht': Vergleich von Fliesskomazahlen ist eine Kunst, die es zu beherrschen gilt.
Gruß
Werner
-
Namal schrieb:
SeppJ schrieb:
Fang am besten mit 1 an.
Dann bekomm ich aber 2.22045e-16 raus
Mit 0.001 hab ich 1.13687e-16
Wie du an Werners Beitrag siehst, ist 2.22045e-16 richtig.
-
Nebenbei bemerkt ist ein Cast genauso wirkungsvoll
vgl. http://ideone.com/wGNuJ und http://ideone.com/uEm71Wer Standardreferenzen haben will, schaut in 5.2.9/4; und allgemeiner 5./11+Fußnote 60
-
Hallo alle,
SeppJ schrieb:
Namal schrieb:
SeppJ schrieb:
Fang am besten mit 1 an.
Dann bekomm ich aber 2.22045e-16 raus
Mit 0.001 hab ich 1.13687e-16
Wie du an Werners Beitrag siehst, ist 2.22045e-16 richtig.
Keineswegs! an meinem Beitrag sieht man nicht, ob 2.22045e-16 richtig ist. Was richtig ist und was nicht hängt vielmehr von der Definition dessen ab, was man berechnen will.
Wenn man sich die Definitionen im Wikipediaartikel über Maschinengenauigkeit und im Standard
C++Standard schrieb:
static constexpr T epsilon() noexcept;
Machine epsilon: the difference between 1 and the least value greater than 1 that is representable.
anschaut, so kommt man drauf, dass
std::epsilon() == 2*Maschinengenauigkeit
ist.
Und wenn man sich den Wikipediaartikel weiter durchliest, so steht's da im Klartext:
Wiki Maschinengenauigkeit schrieb:
.. wobei der Begriff Maschinenepsilon auch für den maximalen relativen Abstand zweier benachbarter normalisierter Gleitkommazahlen verwendet wird. Dieser hat die Größe 2 .
Der Algorithmus, den Namal gewählt hat, berechnet von seinem Ansatz her die Maschinengenauigkeit und nicht std::epsilon. Das das Ergebnis in meinem Fall trotzdem std::epsilon ist und bei Namal vom gewählten Eingangswert abhängt ..
Namal schrieb:
SeppJ schrieb:
Fang am besten mit 1 an.
Dann bekomm ich aber 2.22045e-16 raus
Mit 0.001 hab ich 1.13687e-16
das liegt daran, dass der Wert von
eps
vor der Addition gerundet wird - und zwar mathematisch und nicht kaufmännisch. Und genau dieser Unterschied zwischen den beiden Rundungsarten führt in meinem Code dazu, dass beim Start miteps=1.0
das 'falsche' Ergebnis heraus kommt. Mathematisch runden heißt im Falle von *,5 auf die nächste gerade Zahl runden. Dies ist im Fall von 0,5 die 0 und nicht 1.
Wer es nicht glaubt, ändere die Zeile 10 in meinem Listing nachT eps = 1.0 + std::numeric_limits< T >::epsilon();
was auf den üblichen Maschinen gleichbedeutend mit
1.0+std::pow(2.,-52)
ist.Ein Algorithmus, der das std::epsilon berechnet, müsste also etwas anders aussehen:
#include <iostream> #include <limits> namespace my { template< typename T > struct numeric_limits { static T epsilon() { T eins_plus_epsilon; for( T delta = T(1) /* <== Startwert ist 'egal'*/; ; delta *= 0.5 ) { const T tmp = T(1) + delta; // hier geschieht die starke Rundung von 'delta' if( tmp == T(1) ) break; // delta wurde zu klein, um noch eine Änderung von 1 zu bewirken eins_plus_epsilon = tmp; } return eins_plus_epsilon - T(1); } }; } using std::cout; using std::endl; template< typename T > void zeige_epsilon() { cout << "Mein epsilon : " << my::numeric_limits< T >::epsilon() << endl; cout << "numeric_limits< " << typeid(T).name() << " >::epsilon: " << std::numeric_limits< T >::epsilon() << endl; cout << "Beide Epsilons sind " << (my::numeric_limits< T >::epsilon() == std::numeric_limits< T >::epsilon()? "gleich": "verschieden") << "\n" << endl; } int main() { zeige_epsilon< double >(); zeige_epsilon< float >(); zeige_epsilon< long double >(); return 0; }
.. und der Startwert in Zeile 12 ist jetzt egal, solange er nur größer als std::epsilon ist. Das Ergebnis ist hier immer korrekt ==std::epsilon.
Die Maschinengenauigkeit lässt sich dann aus dem std::epsilon berechnen:
const double maschinengenauigkeit = (std::numeric_limits< double >::epsilon()/2)*(1.0 + std::numeric_limits< double >::epsilon());
wobei der Faktor (1+std::epsilon) nur dazu dient, dass die letzte Rundung nach oben und nicht nach unten geschieht, und zur Demonstration füge man noch diese Zeilen hinzu:
cout << "Maschinengenauigkeit = " << maschinengenauigkeit << endl; cout << "(1.0+maschinengenauigkeit>1.0) ist " << (1.0+maschinengenauigkeit>1.0? "richtig":"falsch") << endl; cout << "und (1.0+maschinengenauigkeit*(1.0-std::epsilon()) == 1.0) ist " << (1.0+maschinengenauigkeit*(1.0-std::numeric_limits< double >::epsilon()) == 1.0? "auch richtig": "falsch") << endl;
Gruß
Werner