Wie genau ist denn nun float?



  • Hi,

    laut IEEE Standard reserviert eine float Variable 23 Bits für die Mantisse und 8 Bits für den Exponenten. Und wer gut in Mathe aufgepasst hat, der weiß, dass das dann insegasmt (incl. 1 VZ-Bit) 32 Bit sind. So weit so gut.

    Nun heißt das denn nicht auch, dass die Genauigkeit 23 Bits ist (da 23 Nachkommastellen) ?

    Was dagegen spricht ist dieses simple Programm:

    float f = 1.1234567890123456789;
    printf("%.20f\n", f);
    

    Bei der Ausgabe stimmen dann immer nur die ersten 6 Nachkommastellen überein. Wieso?

    Danke und mfG



  • Float ist mit sieben gültigen Stellen angegeben. Passt doch also.

    Nun heißt das denn nicht auch, dass die Genauigkeit 23 Bits ist (da 23 Nachkommastellen) ?

    Ein Bit pro Ziffer??? --- NÖÖÖÖ! 👎
    😉



  • gh0st124 schrieb:

    Nun heißt das denn nicht auch, dass die Genauigkeit 23 Bits ist (da 23 Nachkommastellen) ?

    Nein, deine Genauigkeit errechnet sich ganz einfach:

    2 (soviele Zustände kann ein Bit annehmen) ^ 23 (soviele Bits hast du zur Verfügung) = 8388608

    D. h. ein Float kann 6 Stellen sicher garantieren (die 7. nicht mehr ganz, wie man sieht). Das heißt aber nicht, dass das 6 Nachkommastellen sind! Sondern einfach nur 6 Stellen, was wiederum heißt: Auch deine Stellen vor dem Komma werden miteingerechnet!



  • Wenn du noch genauere Informationen zum Floating Point Format haben willst:
    http://www.3dcenter.org/artikel/fp_format/index.php
    Da solltest du fündig werden 😉

    Gruss,
    DeSoVoDaMu





  • Wichtig bei der Verwendung von Fliesskommazahlen ist uebrigens noch, dass nicht alle Zahlen eine Bitrepraesentation haben. Das fuehrt beim Rechnen zwangslaeufig zu Rechenfehlern, die nur durch Rundung ausgeglichen werden koennen.

    Z.B. fuer kaufmaennische Berechnungen sind Fliesskommazahlen daher eigentlich ungeeignet, da ja keine falschen Ergebnisse vorkommen duerfen. Man kann dem Problem zwar durch Rundung einigermassen beikommen, aber es hat schon seinen Grund, warum in COBOL zum Beispiel mit BCD-Zahlen gerechnet wird.

    Ich finde es schade, dass es in C keine BCD-Datentypen gibt (zumal viele Prozessoren eingebaute Befehle fuer BCD-Arithmetik haben -- da wird echt ein Teil des CPU-Befehlssatzes verschwendet, aber das wird er sowieso, da kein Compiler Code generiert, der den Befehlssatz richtig ausnutzt, mittlerweile sind die Prozessorhersteller sogar dazu uebergangen die Programmausfuehrung auf den Code gaengier C-Compiler hin zu optimieren statt andersherum).



  • Power Off schrieb:

    Ich finde es schade, dass es in C keine BCD-Datentypen gibt

    ich finds viel blöder, dass c keine binärzahlen kennt (hex kann's ja auch mit 0x... warum nicht binär mit z.b. 0b...)?
    und ausserdem ist auch doof dass es keine überläufe erkennen kann (hat kein carry-bit wie fast alle cpu's)



  • net schrieb:

    ich finds viel blöder, dass c keine binärzahlen kennt (hex kann's ja auch mit 0x... warum nicht binär mit z.b. 0b...)?

    Naja, so tragisch find ich das nicht. Kann man ja recht schnell von binär nach hex umrechnen. Kannst dir ja ein Makro basteln 😉

    net schrieb:

    und ausserdem ist auch doof dass es keine überläufe erkennen kann (hat kein carry-bit wie fast alle cpu's)

    Das finde ich allerdings auch ungeschickt...



  • TactX schrieb:

    net schrieb:

    und ausserdem ist auch doof dass es keine überläufe erkennen kann (hat kein carry-bit wie fast alle cpu's)

    Das finde ich allerdings auch ungeschickt...

    jo, dafür sind relativ unwichtige sachen dazugekommen, wie ein 'bool' datentyp 😉
    auch fänd' ich's gut, wenn man die breite von 'enums' bestimmen könnte (z.b. ein 8bit enum, wenn man's braucht)
    überhaupt: datentypen von fester breite fehlen irgendwie



  • TactX schrieb:

    net schrieb:

    und ausserdem ist auch doof dass es keine überläufe erkennen kann (hat kein carry-bit wie fast alle cpu's)

    Das finde ich allerdings auch ungeschickt...

    Wie sollte denn sowas aussehen? Aus anderen Sprachen kenne ich folgende Varianten:

    a) Es gibt keine obere Grenze, Datentypen werden intern solange gewechselt, wie ausreichend Speicher zur verfügung steht
    b) Es gibt eine Ausnahme
    c) Das Verhalten für einen arith. Überlauf ist wohldefiniert (evtl. kann man herausfinden, ob ein Überlauf stattgefunden hat)
    d) Das Verhalten ist undefiniert

    Je nachdem, mit welchen Datentypen man arbeitet, ist C eine Sprache vom Typ c) oder d), wobei man hier trivial herausfinden kann, ob ein Überlauf stattgefunden hat, bzw. wenn die Operation undefiniert ist, stattfinden wird.

    Wie sollte sich denn C wünschenswerterweise verhalten?

    Datentypen mit fester Breite (8 Bit, 16 Bit: intN_t) oder minimaler Breite sind in C optional, aber auf aktuellleren Systemen idR vorhanden.



  • Daniel E. schrieb:

    Wie sollte denn sowas aussehen?

    Z.B. so:

    if ( _carry( a + b ) ) {
       /* ... */
    }
    

    bzw.

    if ( _borrow( a - b ) ) {
       /* ... */
    }
    

    Vielleicht hat man sowas weggelassen, weil es bei einigen CPU's keine solchen Flags gibt, oder man wusste nicht, wie man es machen koennte.

    Bei den meisten CPUs tritt zudem ein Uebertrag bzw. Borgen nur dann auf, wenn mit vorzeichenlosen Operationen gerechnet wird.

    D.h. die obigen Ausdruecke waeren nur gueltig fuer "unsigned <base type> a, b".

    Rotationsbefehle haette man auch einbauen koennen. Einige C-Compiler der 80er Jahre hatten uebrigens Rotation mittels "<<<<" bzw. ">>>>" Operatoren.



  • Power Off schrieb:

    if ( _carry( a + b ) ) {
       /* ... */
    }
    

    Ja, so kann man das schon machen, aber man kann auch einfach (c=a+b)<a schreiben, wenigstens bei unsigned-Typen. Ich sehe einfach nicht, wieso C um jeden Maschinenbefehl (siehe dein Beispiel von rol/ror und den >>>>-Dingens) einen Operator drüberlegen muß. Man hat das bei einigen Dingern gemacht, die ziemlich jedes existierende System hat (nicht alle), aber man hat auch wirklich abstraktere Operatoren. Natürlich _könnte_ man mit einigen Klimmzügen sowas implementieren, aber, hey, ein rol bekomme ich mit Klimmzügen auch locker hin. Man muß da halt irgendwo eine Grenze ziehen und spätestens wenn man so Sachen wie Integerüberläufe abzufangen versucht, dann bekommt man eine ziemliche Bandbreite an Möglichkeiten, die evtl. zu den Designzielen der Norm konträr verlaufen, schwer geschmacksspez. sind oder schwierig zu implementieren.



  • Daniel E. schrieb:

    aber man kann auch einfach (c=a+b)<a schreiben, wenigstens bei unsigned-Typen.

    Leider nicht:

    unsigned int a = 0X80000000U;
    unsigned int b = 0XFFFFFFFFU;
    unsigned int c = a + b;   /* c: 0X7FFFFFFFU, c < a, (a+b)<a liefert false */
    


  • Power Off schrieb:

    Daniel E. schrieb:

    aber man kann auch einfach (c=a+b)<a schreiben, wenigstens bei unsigned-Typen.

    Leider nicht:

    unsigned int a = 0X8000000U;
    unsigned int b = 0XFFFFFFFU;
    unsigned int c = a + b;   /* c: 0X7FFFFFFFU, c < a, (a+b)<a liefert false */
    

    Hmm, wieso ist c<a?



  • Daniel E. schrieb:

    Hmm, wieso ist c<a?

    Weil 0X7FFFFFFFU eins kleiner ist als 0X80000000U, deshalb. (EDIT: Ach so, Tippfehler, hab ihn schon korrigiert, danke! 🙂 )



  • Portabler waere ohnehin:

    #include <limits.h>
    
    void test( void ) {
       unsigned int a = (unsigned int)( INT_MIN-1 ); /* da INT_MIN = -(2^(n-1)-1) lt. Standard */
       unsigned int b = UINT_MAX;
       unsigned int c = a + b;
       int carry_test = ( a + b ) < a ? 1 : 0;
    }
    

    Achtung: INT_MIN ist im Standard eigentlich falsch definiert, da der kleinste Wert (-32768 bei 16 Bit) nicht repraesentiert wird. Manche Versionen von "limits.h" korrigieren dies, und machen INT_MIN fuer solche Zwecke unbrauchbar.

    Daher ist die folgende Version vielleicht besser:

    #include <limits.h>
    
    void test( void ) {
       unsigned int a = (unsigned int)( 1 << ( CHAR_BIT*sizeof(int)-1 ) );
       unsigned int b = UINT_MAX;
       unsigned int c = a + b;
       int carry_test = ( a + b ) < a ? 1 : 0;
    }
    


  • Power Off schrieb:

    PAchtung: INT_MIN ist im Standard eigentlich falsch definiert, da der kleinste Wert (-32768 bei 16 Bit) nicht repraesentiert wird.

    Haeh? INT_MIN ist die kleinste darstellbare Zahl, nichts anderes. Und im Einerkomplement (dass ausdrücklich erlaubt ist) ist die kleineste Zahl nunmal -32767, im Zweierkomplement -32768.



  • SG1 schrieb:

    Haeh? INT_MIN ist die kleinste darstellbare Zahl, nichts anderes. Und im Einerkomplement (dass ausdrücklich erlaubt ist) ist die kleineste Zahl nunmal -32767, im Zweierkomplement -32768.

    Dann kann man bloss hoffen, dass die Werte in limits.h den Tatsachen entsprechen, und dass die Werte im Standard nicht bloss abgetippt wurden.

    Einerkomplement zur Darstellung negativer Zahlen benutzt naemlich kein mir bekannter Prozessor. (EDIT: Das waere auch Quatsch, weil man dann spezielle Befehle fuer vorzeichenbehaftete Addition und Subtraktion braeuchte, beim Zweierkomplement sind "signed" und "unsigned" dabei egal.)

    (EDIT II: Prinzipiell hast Du aber Recht, siehe Kapitel 6.2.6.2 des Standards: Es sind sogar negative 0-Werte erlaubt! Das macht Ausdruecke wie "(a+b)<a" erst recht non-portabel!)



  • Power Off schrieb:

    Einerkomplement zur Darstellung negativer Zahlen benutzt naemlich kein mir bekannter Prozessor. (EDIT: Das waere auch Quatsch, weil man dann spezielle Befehle fuer vorzeichenbehaftete Addition und Subtraktion braeuchte, beim Zweierkomplement sind "signed" und "unsigned" dabei egal.)

    Dann kennst du vielleicht einfach nicht sehr viele Prozessoren. Wenn es das nicht gäbe, hätte man es nicht berücksichtigen müssen.



  • Power Off schrieb:

    (EDIT II: Prinzipiell hast Du aber Recht, siehe Kapitel 6.2.6.2 des Standards: Es sind sogar negative 0-Werte erlaubt! Das macht Ausdruecke wie "(a+b)<a" erst recht non-portabel!)

    Es ging um unsigned; ich verstehe immer noch nicht, wie man zwei vorzeichenlose Zahlen so addieren kann, das sie (1) überlaufen und (2) trotzdem größer als einer der beiden Summanden werden kann, wenn man Cs Überlaufsemantik übernimmt (ein Restklassenkörper von UINT_MAX+1, sozusagen.)


Anmelden zum Antworten