Forum: Mikrocontroller und Digitale Elektronik Rückgabewert einer Funktion - float immer eindeutig?


von Hans (Gast)


Lesenswert?

N'Abend zusammen!

Ich weiß, der Titel ist behämmert, aber ich weiß nicht, wie ich es 
formulieren soll. Also ich habe eine Funktion, welche eine Berechnung 
durchführt und dazu eine Variable übergeben bekommt und halt das 
Ergebnis zurückgibt.

Jetzt soll, wenn die übergebene Variable beispielsweise kleiner Null 
ist, ein Fehler zurückgegeben werden.
1
if( variable < 0.0 )
2
{
3
  return -1;
4
}
5
6
...Berechnung...
7
8
return Ergebnis

So, nun ist es ja so, dass ein Float meist keine wirklich gerade Zahl 
ist - z.B. 9,999999999E-1 oder so.

Wenn ich jetzt den Rückgabewert der Funktion überprüfen wollen würde mit
1
if( Rueckgabewert == -1 )
2
{
3
  ...
4
}
dann könnte das ja daneben gehen, oder nicht? Oder klappt das immer? 
Sollte man stattdessen Bereiche abfragen wie 0,9 < x < 1,1?


Über eine Erleuchtung wäre ich dankbar.

von Purzel H. (hacky)


Lesenswert?

Floatwerte, die eindeutig vielfache von Zweierpotenzen sind koennen 
genau angegeben werden. Und -1 = - 2^0

von Wegstaben V. (wegstabenverbuchsler)


Lesenswert?

du könntest deinen Rückgabewert z.B. -2 machen, und Rückgabe auf " < -1 
" prüfen

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Hans schrieb:
> ein Fehler zurückgegeben werden

Für Fehler von Float Funktionen bietet sich der Wer NaN oder +/-INF an 
(je nach gewünschter Aussage), auf welchen man auch mit entsprechenden 
Funktionen prüfen kann, siehe hier: 
http://www.gnu.org/s/hello/manual/libc/Infinity-and-NaN.html

Alternativ kann man bei Floats auch noch das Sign prüfen.

von Matthias L. (Gast)


Lesenswert?

1
int8_t function ( real *variable )
2
{
3
if ( *variable < 0 ) return -1
4
5
// ..berechnung...
6
7
*variable = ergebnis;
8
return 0
9
}
10
11
12
//--------------------------------------------------------------
13
parameter = 1.2345678;
14
if ( function( &parameter ) == 0
15
{
16
  // fehler
17
}
18
19
parameter;
20
// mach was mit ergebnis

von Hans (Gast)


Lesenswert?

Ok! Danke schonmal für die Antworten! Das hilft mir sehr. Ich habe vier 
Fälle von Rückgabewerten als Fehlerkennzeichnung. Alle negativen Werte 
sind Fehler, ein positiver Wert oder halt auch 0 sind gültige Rückgaben.

Wenn ich also return

-1
-2
-4
-8

mache, dann brauche ich mir keine Sorgen machen?

von H.Joachim S. (crazyhorse)


Lesenswert?

sollte im Normalfall gehen.
Ich würde es dennoch lieber mit Vergleichen machen
if (return < -3.0)
   {
   } else if (return < -2.0)
            {
            }
            else if (return < -1.0)
                  {
                   }
                   else if (return < -0.0)
                           {
                            }
                            else {
                                 }

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

1
float func (float var)
2
{
3
    if (variable < 0.0
4
      return 0.0/0.0;
5
6
    ...
7
8
    return sqrt (var)
9
}
10
11
void call_func (float var)
12
{
13
    float res = func (val);
14
15
    if (isNaN (res))
16
      exit (1);
17
18
    ...
19
}

Explizit so durch 0 zu dividieren ist zwar unvertraut, aber das wird 
selbst in Bibliotheksquellen gemacht um NaNs zu erzeugen; libgcc WIMRE.

von Volker S. (volkerschulz)


Lesenswert?

Johann L. schrieb:
> [...]
> Explizit so durch 0 zu dividieren ist zwar unvertraut, aber das wird
> selbst in Bibliotheksquellen gemacht um NaNs zu erzeugen; libgcc WIMRE.

Und fuer seine vier moeglichen Fehler-Rueckgabewerte soll er dann vier 
verschiedene Arten von NANs nutzen, oder wie? ;)

Und gibt's eigentlich einen Grund dafuer, dass hier jeder zweite mit 
"0.0" anstelle von "0" vergleicht?

Gruss,
Volker

von Hans (Gast)


Lesenswert?

Das ist auch so eine Sache...macht es einen Unterschied, 1.0 
zurückzugeben und darauf zu prüfen, oder einfach nur 1?

von Christian B. (casandro)


Lesenswert?

Moment, ich glaube nicht, dass C das Gleitkommasystem spezifiziert. Es 
mag sein, dass es in Deinem Falle funktioniert, generell verlassen würde 
ich mich darauf nicht.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Ich würde einen float nie auf Gleichheit prüfen. Denn das mag heute mit 
2.0 noch gehen. Morgen änderst du das einfach zum Test mal auf 3.0 und 
dann...

von Karl H. (kbuchegg)


Lesenswert?

Volker Schulz schrieb:


> Und gibt's eigentlich einen Grund dafuer, dass hier jeder zweite mit
> "0.0" anstelle von "0" vergleicht?

Weil es an dieser Stelle völlig wurscht ist. Die 0 wird vom Compiler 
sowieso auf 0.0 umgecastet. Aber mich als Programmierer erinnert die 0.0 
daran, dass ich es hier mit Floating Point Werten zu tun habe.

von Karl H. (kbuchegg)


Lesenswert?

Hans schrieb:
> Das ist auch so eine Sache...macht es einen Unterschied, 1.0
> zurückzugeben und darauf zu prüfen, oder einfach nur 1?

Nein.
Wo soll der Unterschied sein. Der Return Wert der Funktion ist als float 
spezifiziert. Also wird auch ein float zurückgegeben. Ob du diesen float 
nun als 1.0 (ist je eigentlich ein double) oder 1 schreibst ist egal. In 
beiden Fällen wird ein float mit dem Wert 1.0 zurückgegeben. Ob das 
Floating Point Format die 1.0 exakt darstellen kann oder nicht, ist in C 
nicht weiter spezifizert, sondern hängt vom verwendeten Floating Point 
Format ab. Seit einigen Jahren hat man es praktisch nur noch mit dem 
IEEE 754 Format zu tun, welches ganze Zahlen bis zu einer gewissen Größe 
exakt darstellen kann.

Handelt es sich wie bei dir um im Quellcode angegebene ganze Zahlen und 
weiß man, dass IEEE 754 benutzt wird, kann man es riskieren einen 
exakten Vergleich zu benutzen. Aber in dem Moment, in dem Werte 
berechnet werden, ist ein Vergleich auf Gleichheit tödlich.

2 Gleitkommawerte  a und b   werden so auf Gleichheit verglichen

   if( fabs( a - b ) < Epsilon )
     sind gleich

und selbst dann, stellt einen die Wahl eines vernünftigen Epsilons noch 
oft genug vor Probleme. Aber alles andere ist Murks. Und selbst dann, 
wenn man Epsilon berücksichtigt, ist man noch lang nicht aus dem 
Schneider.

http://download.oracle.com/docs/cd/E19957-01/806-3568/ncg_goldberg.html

von Anja (Gast)


Lesenswert?

Hans schrieb:
> dann könnte das ja daneben gehen, oder nicht?

Ja, deswegen prüft man floats immer gegen eine Fehlergrenze.

also z.B. Vergleich auf == 1.0

if (fabs(Float - 1.0) < 1e-6)
{
}

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Anja schrieb:
> Ja, deswegen prüft man floats immer gegen eine Fehlergrenze
Lothar Miller schrieb:
> Ich würde einen float nie auf Gleichheit prüfen
Karl Heinz Buchegger schrieb:
> 2 Gleitkommawerte  a und b   werden so auf Gleichheit verglichen

Generell mag das wohl stimmen, die Frage ist doch eher, ob der Compiler 
eine Konstante auf unterschiedliche Weise kodieren kann, den nur dann 
wird der Vergleich schief gehen. NaN und +/- INF sind solche scheinbaren 
Konstanten, bei einfache Zahlen mit Ausnahme der 0 (welche positiv oder 
negativ sein kann) ist die Kodierung aber festgelegt und der Vergleich 
muß klappen, eventuell findet ja jemand in der IEEE Spec einen Hinweis 
der mich widerlegt, ist schon länger her das ich das lesen durfte ;)

Volker Schulz schrieb:
> vier verschiedene Arten von NANs nutzen, oder wie?
Naja es gibt schon einige mögliche Kodierungen für NaN...

... prinzipiell ist aber der Weg, ich gebe verschiedene (float) Werte 
zurück die verschiedene Fehler repräsentieren wenn die Funktion auch ihr 
Ergebnis als float zurück gibt sowieso kaputt.

Wenn man das wirklich machen will, sollte man im Fehlerfall NaN 
zurückgeben, und der Funktion einen int pointer mitgeben welcher dann 
den Status repräsentiert. (Ähnlich wie von Matthias Lipinsky 
vorgeschlagen)

von Volker S. (volkerschulz)


Lesenswert?

Läubi .. schrieb:
> ... prinzipiell ist aber der Weg, ich gebe verschiedene (float) Werte
> zurück die verschiedene Fehler repräsentieren wenn die Funktion auch ihr
> Ergebnis als float zurück gibt sowieso kaputt.

Ich stimme zu, es ist bei Rueckgabewerten generell jedoch gaengige 
Praxis und spaetestens bei Funktionen, die ueber irgendwelche 
Interfacegrenzen hinweg aufgerufen werden auch einfacher.

In diesem speziellen Fall wuerde ich, auch wenn es nicht die eleganteste 
Loesung ist, dem TO raten den Rueckgabewert (wenn negativ) zu runden und 
dann die entsprechenden vergleiche anzustellen. Da kann dann nichts mehr 
schiefgehen. ;)

Volker

von Karl H. (kbuchegg)


Lesenswert?

Läubi .. schrieb:


> Generell mag das wohl stimmen, die Frage ist doch eher, ob der Compiler
> eine Konstante auf unterschiedliche Weise kodieren kann, den nur dann
> wird der Vergleich schief gehen. NaN und +/- INF sind solche scheinbaren
> Konstanten, bei einfache Zahlen mit Ausnahme der 0 (welche positiv oder
> negativ sein kann) ist die Kodierung aber festgelegt und der Vergleich
> muß klappen, eventuell findet ja jemand in der IEEE Spec einen Hinweis
> der mich widerlegt, ist schon länger her das ich das lesen durfte ;)

Nein, nein.
Da hast du schon recht.
Bei IEEE 754 ist es möglich, ganze Zahlen (bis zu einer gewissen Größe) 
exakt darzustellen.

Das Problem: C verlangt nicht, dass IEEE 754 benutzt werden muss.

Das ist das eine, das andere ist, dass eine Berechnung, sagen wir mal

  sum = 0.0;
  for( i = 0; i < 10; ++i )
    sum += 0.1;

obwohl mathematisch 1.0 raus kommen müsste, auf einem Computer seher 
nicht exakt 1.0 ergibt. OK, bei diesem ganz speziellen Fall vielleicht, 
weil der Compiler eventuell schlau genug ist, das so zu optimieren, aber 
ihr wisst schon was ich meine: Ich kann das Beispiel beliebig umformen, 
so dass der Compiler eben nicht mehr optimieren kann. Und dann fällt man 
mit direkten solchen Vergleiche auf die Schnauze.

Ich kann nur das PDF empfehlen, dass ich weiter oben verlinkt habe. Das 
zeigt, wie man bei Floating Point ganz leicht zu widersprüchlichen 
Ergebnissen kommen kann.

von Klaus W. (mfgkw)


Lesenswert?

Auch wenn das, was du vorhast, wohl funktionieren wird....

Es ist Murks.
Wenn eine Funktion einen float-Rückgabewert liefert, dann sollte man den 
nicht ohne guten Grund für etwas anderes wie "hat geklappt/hat nicht 
geklappt" mißbrauchen.

NAN oder +/- INF wäre noch vertretbar, wenn es eine gewisse 
matrhematische Entsprechung hat (acos(1.5)), aber du willst ja den 
Rückgabewert für mehrere Fehlermeldungen zweckentfremden.

Entweder machst du den Rückgabe zu einer int oder enum, mit der du den 
(Miß-) Erfolg signalisierst, und lieferst im Erfolgsfall über einen 
übergebenen Zeiger den Wert, oder umgekehrt (Ergebnis als Rückgabewert, 
Zeiger auf int/enum für Fehlermeldung).

Das alles in einem Rückgabewert zu vermatschen, ist 
Hinterhofprogrammierung, zu der man sich damit deutlich bekennt.
Wenn ich so etwas in einem Programm sehe, nehme ich den Rest meist schon 
nicht mehr ernst.

von Volker S. (volkerschulz)


Lesenswert?

Klaus Wachtler schrieb:
> [...]
> Das alles in einem Rückgabewert zu vermatschen, ist
> Hinterhofprogrammierung, zu der man sich damit deutlich bekennt.
> Wenn ich so etwas in einem Programm sehe, nehme ich den Rest meist schon
> nicht mehr ernst.

So Hinterhof-Klitschen wie Microsoft, Apple, The PHP Group, Creative 
Labs, AMD, ... machen sowas. Wer kann deren Code schon richtig ernst 
nehmen? Oder diese Hinterhof Linux-Experten, die Libs fuer Videotreiber 
oder aehnliches schreiben. Voellig daneben finde ich auch beispielsweise 
den Rueckgabewert von sprintf: "On success, the total number of 
characters written is returned. [...] On failure, a negative number is 
returned." Wer macht denn sowas?!? Igitt!

Volker

von Hans (Gast)


Lesenswert?

Also Leute, vielen Dank für eure Rückmeldungen.

Ich habe es nun folgendermaßen gelöst: Ich werte zuerst die Eingabe aus 
und setze eine Status-Variable, die vor dem eigentlichen Ergebnis 
abgeholt wird:
1
if( !(status) )
2
{
3
  hole Ergebnis;
4
}
5
else
6
{
7
  werte status aus...
8
}

Tritt ein Status anders als 0 auf, wird auch keine Berechnung 
durchgeführt. Wichtig ist dabei natürlich, dass der Status immer vorab 
abgeholt wird, da das Ergebnis sonst ein vorheriges widerspiegeln 
könnte.

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Karl Heinz Buchegger schrieb:
> Und dann fällt man
> mit direkten solchen Vergleiche auf die Schnauze.

Wie gesagt, im allgemeinem Fall stimme ich 100% zu, hier im speziellem 
geht es aber nicht um Berechnungen o.ä. sonder darum, das eine Funktion 
einen konstanten Wert zurückliefert (-1, -2, -3 ...) und im Code 
ebenfalls auf diese Konstanten Werte verglichen wird.
Das sollte nach meinem Verständnis (auch wenn nicht gefordert ist das 
IEEE 754 genutzt wird) keine Probleme bereiten.

Anderes Beispiel (Zahlen beliebig gewählt):
1
if (5f == 5f) {
2
//tu was
3
}
Hier würde ich erwarten das eine "alway true" warnung o.ä. vom Compiler 
kommt, oder ob dies ein "undefined behaviour" ist, da der Compiler 
(durch den C-Standard) die Erlaubnis hat die beiden Konstanten 
unterschiedlich zu kodieren.

von Volker S. (volkerschulz)


Lesenswert?

Läubi .. schrieb:
> Karl Heinz Buchegger schrieb:
>> Und dann fällt man
>> mit direkten solchen Vergleiche auf die Schnauze.
>
> Wie gesagt, im allgemeinem Fall stimme ich 100% zu, hier im speziellem
> geht es aber nicht um Berechnungen o.ä. sonder darum, das eine Funktion
> einen konstanten Wert zurückliefert (-1, -2, -3 ...) und im Code
> ebenfalls auf diese Konstanten Werte verglichen wird.
> Das sollte nach meinem Verständnis (auch wenn nicht gefordert ist das
> IEEE 754 genutzt wird) keine Probleme bereiten.

Da hast Du (nach meinem Verstaendnis) natuerlich recht. "Da draussen im 
echten Leben" wird aber auch der negative Fehler-Rueckgabewert schon mal 
"berechnet" um mehrere Fehler gleichzeitig abbilden zu koennen.

error=0;
if(fehler1) error+=1;
if(fehler2) error+=2;
if(fehler3) error+=4;
[...]
return error*-1

Volker

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Volker Schulz schrieb:
> "Da draussen im
> echten Leben"
Wie schon von mir weiter oben geschrieben, würde ich solch eine Art der 
Fehlerbehandlung auch im echten Leben strickt ablehnen, das sind nämlich 
genau die "genialen" Einfälle wo man sich zwei Minuten Tiparbeit sparen 
wollte oder "nur mal eben schnell" einen Fehler signalisieren welche 
einem dann später den ganzen Tag versauen ;)

Es wird nämlich immer einen Dummen (meist man selbst) geben der dann 
doch einfach sowas macht:
1
float x = z * berechne(y) + 1;
und leider erst viel zu spät mitkriegt, dass das Ergebnis komischerweise 
manchmal falsch ist, aber nicht wenn z gleich -42... ;-P

Hans schrieb:
> Ich werte zuerst die Eingabe aus
> und setze eine Status-Variable, die vor dem eigentlichen Ergebnis
> abgeholt wird
Klingt etwas verworren, zeig doch einfach mal deine Funktionsdefinition, 
man kann das z.B. so lösen:
1
float berechne(float variable, int *status) { 
2
if( variable < 0.0 ) {
3
  *status = ERROR_VALUE_TO_SMALL;
4
  return FP_NAN;
5
}
6
...Berechnung...
7
*status = OK;
8
return Ergebnis
9
}

von Frank Freiberg (Gast)


Lesenswert?

Volker Schulz schrieb:
> Da hast Du (nach meinem Verstaendnis) natuerlich recht. "Da draussen im
> echten Leben" wird aber auch der negative Fehler-Rueckgabewert schon mal
> "berechnet" um mehrere Fehler gleichzeitig abbilden zu koennen.
>
> error=0;
> if(fehler1) error+=1;
> if(fehler2) error+=2;
> if(fehler3) error+=4;
> [...]
> return error*-1
>
> Volker


Super Idee.  Muss ich mir merken.

von Volker S. (volkerschulz)


Lesenswert?

Läubi .. schrieb:
> Volker Schulz schrieb:
>> "Da draussen im
>> echten Leben"
> Wie schon von mir weiter oben geschrieben, würde ich solch eine Art der
> Fehlerbehandlung auch im echten Leben strickt ablehnen, das sind nämlich
> genau die "genialen" Einfälle wo man sich zwei Minuten Tiparbeit sparen
> wollte oder "nur mal eben schnell" einen Fehler signalisieren welche
> einem dann später den ganzen Tag versauen ;)

Hehe. Haengt natuerlich von mehr als einem Umstand ab. Mir erleichtern 
solche Arten der Fehlerbehandlung taeglich die Arbeit: Wenn ich ein 
Handle per API hole, ist es schlichtweg bequem (und m.E. auch sinnvoll) 
wenn ich direkt am (negativen) Rueckgabewert den Fehler "ablesen" kann. 
Und das auch dann, wenn das Ziel eines Zeigers eventuell schon nicht 
mehr existiert. Nicht selten hatte ich auch schon Spezifikationen auf 
dem Tisch, in denen explizit gefordert wurde, dass ein Objekt noch vor 
Auswertung der Rueckgabewerte "disposable" sein muss (oder sich sogar 
selbst "disposen" muss), mit Pointern kommt man da im Zweifel auch nicht 
weit. Ich bin auch schon mit der Qualitaetssicherung aneinander geraten, 
weil ich (auf zugegebenermassen schmalbruestigen Systemen) die 
Bitbreiten von Variablen / Registern nicht in groesstmoeglichem Umfang 
ausgenutzt habe... Es gibt diesbezueglich also durchaus 
verschiedenartige Ansaetze und Ansichten, die ich nicht ohne Weiteres in 
"Hinterhof" und "Nicht Hinterhof" unterteilt wissen moechte. ;)

Volker

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.