W
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 ⋅ε\cdot \varepsilon⋅ε.
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 mit eps=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 nach
T 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