C++Talk.NET Forum Index C++Talk.NET
C++ language newsgroups
 
Archives   FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Weshalb die 1.0 nicht mehr?
Goto page 1, 2  Next
 
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C++ (German)
View previous topic :: View next topic  
Author Message
Dieter Wellmann
Guest





PostPosted: Mon Jan 29, 2007 12:54 pm    Post subject: Weshalb die 1.0 nicht mehr? Reply with quote



Guten Morgen! C++-Newbie mit vielleicht dummer Frage.

#include <iostream>
using namespace std;
int main() {
for (float x=0.0; x<=1.0; x+=0.1) {
cout<<x<<endl;
}
return(0);
}

liefert...
0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9

Weshalb die 1.0 nicht mehr?

Falls das von Bedeutung ist:
g++ (GCC) 4.1.2 20060928 (prerelease) (Ubuntu 4.1.1-13ubuntu5)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

--
Viel Spaß noch! D.***
Wieso, weshalb, warum - wer nicht fragt, bleibt dumm.
Back to top
Rolf Magnus
Guest





PostPosted: Mon Jan 29, 2007 5:23 pm    Post subject: Re: Weshalb die 1.0 nicht mehr? Reply with quote



Dieter Wellmann wrote:

Quote:
Guten Morgen! C++-Newbie mit vielleicht dummer Frage.

#include <iostream
using namespace std;
int main() {
for (float x=0.0; x<=1.0; x+=0.1) {
cout<<x<<endl;
}
return(0);
}

liefert...
0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9

Weshalb die 1.0 nicht mehr?

Das ist weniger ein C++-spezifisches Problem, sondern eher eins mit
Fließkomma-Berechnungen. Fließkommazahlen sind nicht exakt. Man kann nicht
jede beliebige Zahl speichern, denn dafür bräuchte man unendlich viel
Speicherplatz. Speziell die 0,1 ist in einer binären Repräsentation eine
periodische Zahl, so wie 1/3 im Dezimalsystem (0,333333333....). Daher wird
x in der Schleife nicht um 0,1, sondern um den nächsten darstellbaren Wert
inkrementiert. Deshalb ist nach 10 Durchläufen das Ergebnis nicht exakt 1,
und der Test auf Gleichheit schägt fehl. Allgemein sollte man deshab
Fließkommazahlen nicht auf Gleichheit testen.
Back to top
Heinz Saathoff
Guest





PostPosted: Mon Jan 29, 2007 6:23 pm    Post subject: Re: Weshalb die 1.0 nicht mehr? Reply with quote



Dieter Wellmann schrieb...
Quote:
Guten Morgen! C++-Newbie mit vielleicht dummer Frage.

#include <iostream
using namespace std;
int main() {
for (float x=0.0; x<=1.0; x+=0.1) {
cout<<x<<endl;
}
return(0);
}

liefert...
0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9

Weshalb die 1.0 nicht mehr?

Weil, wie Rolf schon geantwortet hat, nicht alle Zahlen exakt
darstellbar sind.
Wenn trotzdem die 1.0 sicher mit ausgegeben werden soll, muß halt die
Grenze verschoben werden. Da Du in 0.1 Schritten inkrementierst, kannst
Du die Grenze z. B. bei 1.0+0.05 setzen, also

for(float x=0.0; x<1.05; x+=0.1) {
cout<<x<<endl;
}

Auf der anderen Seite könnte bei deinem Programm auch die 1.0 mit
ausgegeben werden (andere Platform, andere Darstellung). Falls Du also
umgekehrt sicher sein willst, dass die 1.0 _nicht_ mit ausgegeben wird,
dann einfach die Grenze nach unten verschieben, also 1.0-0.05:

for(float x=0.0; x<0.95; x+=0.1) {
cout<<x<<endl;
}

In beiden Fällen ist nur ein Vergleich mit '<' erforderlich, kein '<='.


- Heinz
Back to top
Thorsten Bässler
Guest





PostPosted: Mon Jan 29, 2007 7:58 pm    Post subject: Re: Weshalb die 1.0 nicht mehr? Reply with quote

Heinz Saathoff schrieb:
Quote:
Wenn trotzdem die 1.0 sicher mit ausgegeben werden soll, muß halt die
Grenze verschoben werden. Da Du in 0.1 Schritten inkrementierst, kannst
Du die Grenze z. B. bei 1.0+0.05 setzen, also

for(float x=0.0; x<1.05; x+=0.1) {
cout<<x<<endl;
}

Könnte man für so einen Fall nicht

x < 1.0f + std::numeric_limits<float>::epsilon();

benutzen?

Gruß,
Thorsten.
Back to top
Dieter Wellmann
Guest





PostPosted: Mon Jan 29, 2007 8:01 pm    Post subject: Re: Weshalb die 1.0 nicht mehr? Reply with quote

Rolf Magnus schrieb:
Quote:
Das ist weniger ein C++-spezifisches Problem, sondern eher eins mit
Fließkomma-Berechnungen. Fließkommazahlen sind nicht exakt.

inkrementiert. Deshalb ist nach 10 Durchläufen das Ergebnis nicht
exakt 1,
und der Test auf Gleichheit schägt fehl. Allgemein sollte man deshab
Fließkommazahlen nicht auf Gleichheit testen.

Heinz Saathoff schrieb:
Quote:
Wenn trotzdem die 1.0 sicher mit ausgegeben werden soll, muß halt die
Grenze verschoben werden. Da Du in 0.1 Schritten inkrementierst, kannst
Du die Grenze z. B. bei 1.0+0.05 setzen, also

Danke an euch beide! Fazit: möglichst mit int bzw. ganzzahlig arbeiten,
damit Vergleiche sicher funktionieren. Man kann ja sekundär wieder durch
zehn nehmen.
Wenns möglich ist, so vielleicht?
int main() {
float y=0;
for (int x=0; x<=10; x++) {
y=x/10.0;
cout<<y<<endl;
}
return(0);
}
liefert...
0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
1

Bissel umständlich, aber sicherer.

Was mir weniger einleuchtet, ist diese scheinbar willkürlich definierte
Grenze 0.05. Weshalb nicht 0.00001 oder 0.00000007?

--
Viel Spaß noch! D.***
Wieso, weshalb, warum - wer nicht fragt, bleibt dumm.
Back to top
Rolf Magnus
Guest





PostPosted: Tue Jan 30, 2007 12:01 am    Post subject: Re: Weshalb die 1.0 nicht mehr? Reply with quote

Dieter Wellmann wrote:

Quote:
Rolf Magnus schrieb:
Das ist weniger ein C++-spezifisches Problem, sondern eher eins mit
Fließkomma-Berechnungen. Fließkommazahlen sind nicht exakt.

inkrementiert. Deshalb ist nach 10 Durchläufen das Ergebnis nicht
exakt 1,
und der Test auf Gleichheit schägt fehl. Allgemein sollte man deshab
Fließkommazahlen nicht auf Gleichheit testen.

Heinz Saathoff schrieb:
Wenn trotzdem die 1.0 sicher mit ausgegeben werden soll, muß halt die
Grenze verschoben werden. Da Du in 0.1 Schritten inkrementierst, kannst
Du die Grenze z. B. bei 1.0+0.05 setzen, also

Danke an euch beide! Fazit: möglichst mit int bzw. ganzzahlig arbeiten,
damit Vergleiche sicher funktionieren.

Zumindest für Schleifenzähler würde ich dem uneingeschränkt zustimmen. Bei
Fließkomma-Vergleichen muss man halt immer daran denken

Quote:
Man kann ja sekundär wieder durch
zehn nehmen.
Wenns möglich ist, so vielleicht?
int main() {
float y=0;
for (int x=0; x<=10; x++) {
y=x/10.0;
cout<<y<<endl;
}
return(0);
}

Man könnte daraus noch machen:

for (int x=0; x<=10; x++) {
float y=x/10.0;
cout<<y<<endl;
}

Lokale Variablen sollten immer den kleinstmöglichen "scope" haben, daher
sollte y in die Schleife gezogen werden. (Ist eher eine Stil-Angelegenheit)

Quote:
liefert...
0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
1

Bissel umständlich, aber sicherer.

Ja.

Quote:
Was mir weniger einleuchtet, ist diese scheinbar willkürlich definierte
Grenze 0.05. Weshalb nicht 0.00001 oder 0.00000007?

Ist wohl wirklich eher willkürlich gewählt. Es ist genau die Hälfte von 0.1,
so dass der Code so gegen größmögliche Abweichung in beiden Richtungen
abgesichert ist.
Back to top
Heinz Saathoff
Guest





PostPosted: Tue Jan 30, 2007 1:32 am    Post subject: Re: Weshalb die 1.0 nicht mehr? Reply with quote

Thorsten Bässler schrieb...
Quote:
Wenn trotzdem die 1.0 sicher mit ausgegeben werden soll, muß halt die
Grenze verschoben werden. Da Du in 0.1 Schritten inkrementierst, kannst
Du die Grenze z. B. bei 1.0+0.05 setzen, also

for(float x=0.0; x<1.05; x+=0.1) {
cout<<x<<endl;
}

Könnte man für so einen Fall nicht

x < 1.0f + std::numeric_limits<float>::epsilon();

benutzen?

Könnte schiefgehen.
'x' wird ja in der Schleife inkrementiert, wodurch sich auch der Fehler
mit erhöht. Falls z.B. 0.1 intern als (0.1+eps) dargestellt wird, hat
'x' nach 10 Schleifendurchläufen den Wert 10*(0.1+eps), der größer als
(1.0+eps) ist. Somit würde der 11. Lauf nicht mehr stattfinden.


- Heinz
Back to top
Heinz Saathoff
Guest





PostPosted: Tue Jan 30, 2007 1:32 am    Post subject: Re: Weshalb die 1.0 nicht mehr? Reply with quote

Dieter Wellmann schrieb...
Quote:
Danke an euch beide! Fazit: möglichst mit int bzw. ganzzahlig arbeiten,
damit Vergleiche sicher funktionieren. Man kann ja sekundär wieder durch
zehn nehmen.
Wenns möglich ist, so vielleicht?
int main() {
float y=0;
for (int x=0; x<=10; x++) {
y=x/10.0;
cout<<y<<endl;
}
return(0);
}
liefert...
0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
1

Bissel umständlich, aber sicherer.

Wenn's möglich ist, sind Ganzzahlen schon vorzuziehen. Wenn man sich der
Grenzen in der Zahlendarstellung von float und double bewußt ist, kann
man aber auch mit diesen Datentypen sicher programmieren.


Quote:
Was mir weniger einleuchtet, ist diese scheinbar willkürlich definierte
Grenze 0.05. Weshalb nicht 0.00001 oder 0.00000007?

Es ist tatsächlich ein willkürlicher Wert. Soweit ich weiss, hat float
auf Intel-CPU nur etwa 6 signifikante Stellen in der Mantisse.In dem
Fall ist Dein erster Wert 0.00001 schon fast die kleinstmögliche
Erhöhung der Grenze, während die 2. Konstante schon als Summe mit 1.0
nicht mehr darstellbar ist und 1.0 bleibt.

Du kannst ja mal dieses kleine Progrämmchen laufen lassen und schauen,
was passiert:

#include <iostream>
#include <limits>

using namespace std;

float Eps[8] = {0.1, 0.01, 0.001, 0.0001,
0.00001, 0.000001, 0.0000001,
0.00000001};
float One = 1.0;

int main()
{
for(int i=0; i<8; ++i) {
float OnePlusEps = One + Eps[i];
cout << "1.0"
<< ( One==OnePlusEps ? "==" : "!=" )
<< OnePlusEps
<< endl;
}
return 0;
}

Bei mir kommt wird folgendes ausgegeben:
1.0!=1.1
1.0!=1.01
1.0!=1.001
1.0!=1.0001
1.0!=1.00001
1.0!=1
1.0!=1
1.0==1

Die letzte Ausgabe ist plötzlich '==' geworden.



- Heinz
Back to top
Ole Hinz
Guest





PostPosted: Tue Jan 30, 2007 2:11 am    Post subject: Re: Weshalb die 1.0 nicht mehr? Reply with quote

Dieter Wellmann wrote:
Quote:
Was mir weniger einleuchtet, ist diese scheinbar willkürlich definierte
Grenze 0.05. Weshalb nicht 0.00001 oder 0.00000007?

Weil 0.05 die Hälfte Deines Inkrements ist. Hättest Du 0.01 in jedem
Schleifendurchlauf hochgezählt, hätte die Antwort in der NG vermutlich
0.005 gelautet.

Bei 0.00001 oder 0.00000007 könnte die Darstellung der Zahlen einem
schon wieder einen Strich durch die Rechnung machen.

Gruß
Ole Hinz

--
http://www.ole-hinz.de
Back to top
Stefan Reuther
Guest





PostPosted: Tue Jan 30, 2007 3:42 am    Post subject: Re: Weshalb die 1.0 nicht mehr? Reply with quote

Dieter Wellmann wrote:
Quote:
Danke an euch beide! Fazit: möglichst mit int bzw. ganzzahlig arbeiten,
damit Vergleiche sicher funktionieren. Man kann ja sekundär wieder durch
zehn nehmen.

Genau.

Quote:
Wenns möglich ist, so vielleicht?
int main() {
float y=0;
for (int x=0; x<=10; x++) {
y=x/10.0;
cout<<y<<endl;
}
return(0);
}
liefert...
[...]
Bissel umständlich, aber sicherer.

Noch als Anmerkung: wenn du nicht genau weißt, warum du da 'float'
nimmst, nimm 'double'. Es gibt heutzutage kaum mehr Gründe, die gegen
'double' sprechen. Klar, doppelter Speicherbedarf, aber darüber reden
wir bei einer einzelnen lokalen Variable lieber mal nicht. Gängige FPUs
sind auch bei 'float' nicht schneller als bei 'double'.

'float' nimmt man heutzutage, wenn man (a) so enorm gigantisch große
Arrays hat, dass 'double' den Cache/RAM sprengt, 'float' jedoch nicht,
oder (b) nur Software-Floating-Point hat. Das dürfte bei dir beides
nicht zutreffen.

Zugegeben, mit 'double' hätte dein erstes Programm gleich funktioniert,
und du hättest nichts über Fließkommazahlen gelernt :-)


Stefan
Back to top
Markus Becker
Guest





PostPosted: Tue Jan 30, 2007 5:43 am    Post subject: Re: Weshalb die 1.0 nicht mehr? Reply with quote

Thorsten Bässler <goofy19 (AT) web (DOT) de> schrieb:

Quote:
Könnte man für so einen Fall nicht

x < 1.0f + std::numeric_limits<float>::epsilon();

benutzen?

Das Problem ist ja nicht die 1.0, sondern die 0.1

Markus
Back to top
Tibor Pausz
Guest





PostPosted: Tue Jan 30, 2007 10:11 am    Post subject: Re: Weshalb die 1.0 nicht mehr? Reply with quote

Stefan Reuther schrieb:
Quote:
Gängige FPUs
sind auch bei 'float' nicht schneller als bei 'double'.

Das mag auf x86 zutreffen, aber für viele CPUs trifft das nicht zu. Der
Extremfall dürfte Cell sein, der ist bei Float ca. 10x schneller.
Back to top
James Kanze
Guest





PostPosted: Tue Jan 30, 2007 4:53 pm    Post subject: Re: Weshalb die 1.0 nicht mehr? Reply with quote

Heinz Saathoff wrote:
Quote:
Dieter Wellmann schrieb...
Danke an euch beide! Fazit: möglichst mit int bzw. ganzzahlig arbeiten,
damit Vergleiche sicher funktionieren. Man kann ja sekundär wieder durch
zehn nehmen.
Wenns möglich ist, so vielleicht?
int main() {
float y=0;
for (int x=0; x<=10; x++) {
y=x/10.0;
cout<<y<<endl;
}
return(0);
}
liefert...
0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
1

Bissel umständlich, aber sicherer.

Wenn's möglich ist, sind Ganzzahlen schon vorzuziehen.

Eindeutig. Man soll nur Gleitkomma benutzen, wenn man genau
weiß, was man tut. Die Regel sind nicht einfach.

Quote:
Wenn man sich der
Grenzen in der Zahlendarstellung von float und double bewußt ist, kann
man aber auch mit diesen Datentypen sicher programmieren.

Was mir weniger einleuchtet, ist diese scheinbar willkürlich definierte
Grenze 0.05. Weshalb nicht 0.00001 oder 0.00000007?

Es ist tatsächlich ein willkürlicher Wert.

In diesem Fall hat man wahrscheinlich einen halben Schritt
genommen. Was auch nicht in jedem Fall richtig ist. In der
Praxis muss man immer ganz genau analysieren, was man erreichen
will, und berücksichtigen, wie Gleitkommazahlen tatsächlich
funktionnieren. Anzufangen würde ich "What Every Computer
Scientist Should Know About Floating Point Arithmetic"
(http://docs.sun.com/source/806-3568/ncg_goldberg.html, aber
auch an vielen anderen Stellen) empfehlen.

Quote:
Soweit ich weiss, hat float
auf Intel-CPU nur etwa 6 signifikante Stellen in der Mantisse.

Es ist wesentlich komplizierter. Im Speicher hat einen float
ungefähr 7 signifikante Dezimaleziffer---21 Bits, genau
genommen. In der Gleitkommarechner aber wird immer in long
double gearbeitet, mit 64 Bits (dazu ein Paar »guard bits«
während des Rechnens). Und es kann vorkommen, dass der Compiler
Zwischenergebnisse als double (52 Bits) speichert. Ich habe
schon den Fall gesehen, wo bei
if ( p->f() < q->f() )
wahr liefert, wenn beide Ergebnisse gleich waren---das erste
würde als double gespeichert, dann mit dem long double Ergebnis
des zweiten Aufrufes verglichen.

Wann und wo gespeichert wird, kann auch von der
Optimisierungsstufe des Compilers abhängen.

Quote:
In dem
Fall ist Dein erster Wert 0.00001 schon fast die kleinstmögliche
Erhöhung der Grenze, während die 2. Konstante schon als Summe mit 1.0
nicht mehr darstellbar ist und 1.0 bleibt.

Nur ist 0.00001 ein double. Und wenn ein Ausdruck einmal double,
einmal float enthält, wird er in double ausgeführt.

Die Grundüberlegung ist aber nicht falsch. Es kann wohl
vorkomment, dass »x != 0.0 && a+x == a«. Genauso wie »a+(b+c) !(a+b)+c« oder »(a+b)-b != a«.

--
James Kanze (GABI Software) email:james.kanze (AT) gmail (DOT) com
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
Back to top
Dieter Wellmann
Guest





PostPosted: Tue Jan 30, 2007 9:54 pm    Post subject: Re: Weshalb die 1.0 nicht mehr? Reply with quote

Heinz Saathoff schrieb:
Quote:
1.0!=1.0001
1.0!=1.00001
1.0!=1
1.0!=1
1.0==1

Die letzte Ausgabe ist plötzlich '==' geworden.

Ja, kommt bei mir genau so. Erinnert mich irgendwie an den
Taschenrechner, der die 1/3 minus dem angezeigtem Ergebnis (von 1/3)
einen Rest anzeigte.

--
Viel Spaß noch! D.***
Wieso, weshalb, warum - wer nicht fragt, bleibt dumm.
Back to top
Dieter Wellmann
Guest





PostPosted: Tue Jan 30, 2007 9:59 pm    Post subject: Re: Weshalb die 1.0 nicht mehr? Reply with quote

Stefan Reuther schrieb:
Quote:
Zugegeben, mit 'double' hätte dein erstes Programm gleich funktioniert,
und du hättest nichts über Fließkommazahlen gelernt Smile

Ja, danke noch 1x allen für die Gedanken!

--
Viel Spaß noch! D.***
Wieso, weshalb, warum - wer nicht fragt, bleibt dumm.
Back to top
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C++ (German) All times are GMT
Goto page 1, 2  Next
Page 1 of 2

 
 


Powered by phpBB © 2001, 2006 phpBB Group