Long Double zu unsigned integer typ



  • Hallo, ich möchte aus einer Long double variable einen unsigned integer machen:

    long double EineZahl;
    if(EineZahl<1.0)
       return 0;
    else
       return std::bitset<256> tmp(UmwandlungZuGroßemInteger(EineZahl));
    

    Im Prinzip muss ich ja dafür die Mantisse auslesen und diese dann so oft shiften, wie es der Exponent vorgibt. zB:

    std::bitset<256> tmp = MantisseVonLongDouble;
    tmp <<= ExponentVonLongDouble; //oder rechtsshift halt
    

    Die Mantisse auszulesen ist ja nicht schwer (es ist vorgegeben, dass little Endian benutzt wird)

    long double EineZahl;
    unsigned char arr[ sizeof(long double) ];
    std::memcpy(arr, &EineZahl, sizeof(long double));
    
    std::bitset<256> tmp;
    //fülle das bitset mit der mantisse von der long double variable
    for(unsigned int i = 0; i != sizeof(long double); ++i)
        for(unsigned int j = 0; j != CHAR_BIT; ++j)
        {
           if( CHAR_BIT*i+j == LDBL_MANT_DIG )
                return tmp;
           if( arr[i] & (1<<j) )
                tmp[ i * CHAR_BIT + j ] = true;
        }
    

    Aber wie ist der Exponent aufgebaut? Im Prinzip ist der Exponent ja ( CHAR_BIT * sizeof(long double) - LDBL_MANT_DIG - 1 ) bits lang. Aber wie stellt der positive/negative werte dar? das hab ich bisher nicht verstanden.
    Hilft es zu wissen, ob sich an den iec Standard gehalten wird? (numeric_limits<long double>::is_iec559)

    Schon mal vielen Dank an alle, die Vorschläge machen 🙂



  • So, ich hab mich jetzt mal weiter damit beschäftigt und gelesen, dass es beim Exponent einen "bias" gibt und versucht das zu implementieren. Hier mein Programm (sieht ziemlich lang aus, ist es aber nicht 😉 ). Anmerkung: print gibt ein bitset als string aus und schneidet führenden Nullen ab.

    template<typename T, std::size_t N> //T = float, N = 1024 damit es nicht zu überläufen im bitset kommen kann
    std::bitset<N> cast(T EineZahl)
    {
    	//----------------------------------------------------------------------------------------------------------
    	// Zahlen kleiner 1 ausschließen
    	//----------------------------------------------------------------------------------------------------------
        if(EineZahl < (T)1.0)
    	{
    		std::cerr<<"ERROR: Value too small!"<<std::endl;
            return std::bitset<N>();
    	}
    
    	//----------------------------------------------------------------------------------------------------------
    	// Eigenschaften von T ausgeben
    	//----------------------------------------------------------------------------------------------------------
    	const unsigned int exp_size = sizeof(T) * CHAR_BIT - (std::numeric_limits<T>::digits+1) ;
    	const unsigned long int bias = (1<<(exp_size-1))-1;
    	std::cout<<"Bits in T   : "<<sizeof(T)*CHAR_BIT<<std::endl;
    	std::cout<<"Bits in Mant: "<<std::numeric_limits<T>::digits<<std::endl;
    	std::cout<<"Bits in Exp : "<<exp_size<<std::endl;
    	std::cout<<"Bias        : "<<bias<<std::endl<<std::endl;
    
    	//----------------------------------------------------------------------------------------------------------
    	// Long Double ins Bitset übetragen
    	//----------------------------------------------------------------------------------------------------------
    	unsigned char arr[ sizeof(T) ];
        std::memcpy(arr, &EineZahl, sizeof(T));
    
        std::bitset<N> Zahl;
    
        for(unsigned int i = 0; i != sizeof(T); ++i)
            for(unsigned int j = 0; j != CHAR_BIT; ++j)
               if( arr[i] & (1<<j) )
                    Zahl[ i * CHAR_BIT + j ] = true;
    
    	//----------------------------------------------------------------------------------------------------------
    	// Zahl in mantisse kopieren und alle bits nach der Mantisse auf 0 setzen
    	//----------------------------------------------------------------------------------------------------------
        std::bitset<N> mantisse = Zahl;
    	for(unsigned int i = std::numeric_limits<T>::digits; i != sizeof(T) * CHAR_BIT; ++i)
            mantisse.reset(i);
    
    	//----------------------------------------------------------------------------------------------------------
    	// Zahl in exponent kopieren und so oft rechts shiften, dass nur noch der exponent drin steht
    	//----------------------------------------------------------------------------------------------------------
        std::bitset<N> exponent = Zahl;
        exponent>>=std::numeric_limits<T>::digits; //sign bit kann ignoriert werden, da es hier immer 0 ist
    
    	//----------------------------------------------------------------------------------------------------------
    	// Mantisse und Exponent binär ausgeben
    	//----------------------------------------------------------------------------------------------------------
    	std::cout<<"Mantisse    : "<<print(mantisse)<<std::endl;
    	std::cout<<"Exponent    : "<<print(exponent)<<std::endl;
    
    	//----------------------------------------------------------------------------------------------------------
    	// Bitset exponent in unsigned long umwandeln
    	//----------------------------------------------------------------------------------------------------------
    	unsigned long int exp_val;
        try
        {
            exp_val = exponent.to_ulong();
        }
        catch(...)
        {
    		std::cerr<<"ERROR: Cannot convert exponent!"<<std::endl;
            return std::bitset<N>();
        }
    
    	//----------------------------------------------------------------------------------------------------------
    	// Exponent vor und nach Verrechnung mit Bias
    	//----------------------------------------------------------------------------------------------------------
        std::cout<<"exp (before): "<<exp_val<<std::endl;
    	int sign = 1;
    	if(bias > exp_val) sign = -1;
        exp_val -= bias;
    	exp_val &= ((1<<exp_size)-1) ; //exp_val auf exp_size stellen begrenzen (falls overflow bei subtraktion)
    	std::cout<<"exp (after) : "<<exp_val<<std::endl;
    
    	//----------------------------------------------------------------------------------------------------------
    	// Verrechnung von Mantisse und Exponent
    	//----------------------------------------------------------------------------------------------------------
    	if(sign == 1)
            mantisse <<= exp_val;
        else
            mantisse >>= exp_val;
    
        return mantisse;
    }
    
    int main()
    {
    	const std::size_t N = 1024;
        std::bitset<N> tmp = cast<float, N>(123.0f);
    
    	std::cout<<std::endl;
        try
        {
    		std::cout<<"Zahl fertig : "<<print(tmp)<<std::endl;
    		std::cout<<"Zahl ulong  : "<<tmp.to_ulong()<<std::endl;
        }
    	catch(...)
        {
    		std::cout<<"ERRROR: Cannot convert to ulong in main()!";
        }
    }
    

    Ausgabe (Bei den Bitsets sind alle führenden, das heißt höherwertigen Nullen abgeschnitten. Links steht das höchstwertigste Bit und rechts das Niedrigste. Intern sind die Bits andersrum angeordnet. Bitset[0] enthält das Niedrigste.):

    Bits in T   : 32
    Bits in Mant: 24
    Bits in Exp : 7
    Bias        : 63
    
    Mantisse    : 111101100000000000000000
    Exponent    : 1000010
    exp (before): 66        (bevor bias verrechnet wird)
    exp (after) : 3
    
    Zahl fertig : 111101100000000000000000000
    Zahl ulong  : 128974848
    
    Erwartetes Ergebnis:
    Zahl fertig : 1111011
    Zahl ulong  : 123
    

    4 Sachen wundern mich:

    • Warum sind bei der Mantisse die höher wertigen Bits gesetzt und nicht die niedrigen, so wie im Exponent?
    • Auf Wikipedia steht, man soll den Bias vom Exponenten abziehen. Wenn ich ihn allerdings addiere, dann ist der Exponent hier nicht 3 sondern 1 (was mir irgendwie plausibler erscheint)
    • Auf Wikipedia steht, man solle 1 auf die Mantisse addieren. Aber hier ist die Mantisse doch schon meine gesuchte Zahl?!
    • Warum habe ich hier andere Werte für die Eigenschaften von float, obwohl sich mein Compiler (Microsoft Visual C++ Express) angeblich an den iec559 standard hält? (laut wikipedia hat ein 32bit float nach diesem standard 23 bits mantisse und 8 bits exponent, nicht 24 und 7 wie bei mir)

    Was mache ich hier also falsch?


  • Mod

    long double foo=1234.5678;
      long unsigned int bar=foo;
    

    Hab ich jetzt gewonnen?

    Übrigens: Der interne Aufbau von doubles ist nicht standardisiert. Wie genau der double aufgebaut ist, steht in der Anleitung deines Compilers. Vermutlich in Form eines Verweises auf den IEEE 754 Standard.



  • SeppJ schrieb:

    long double foo=1234.5678;
      long unsigned int bar=foo;
    

    Hab ich jetzt gewonnen?

    long double foo = 2e100L;
    unsigned int bar = foo; //unfug
    


  • den unfug kannst du aber auch mit einer million zeilen selbstgefrickeltem code nicht loswerden. was nicht geht, geht nicht. und wenn du rausfinden willst, ob's geht, vergleich den double halt vorher mit std::numeric_limits<zieltyp>::min() und max().

    ru,
    cirion



  • Das ist kein Unfug, denn:

    long double foo = 2e200L;
    MeinInteger bar = foo;
    

    lässt sich verlustfrei darstellen. Dafür muss ich aber Exponent und Mantisse auslesen und mir das selbst zusammen basteln.



  • siehe std::frexp aus <cmath>

    gruß,
    kk



  • @Umwandelnung:
    Alle deine Fragen sollten sich von selbst beantworten wenn du mal weitere 5 oder 10 Minuten erübrigst um dich ein wenig über IEEE 754 schlau zu lesen.
    Bei 32 Bit Zahlen nach IEEE 754 ist die führende 1 der 24 Bit langen Mantisse implizit, wird also nicht in den 32 Bit abgespeichert.

    Ich schätze mal das wird der Grund sein warum std::numeric_limits<float>::digits 24 liefert, obwohl das verwendete Format (32 Bit Binär-Representation nach IEEE 754 aka. IEC 559) nur 23 Bit der Mantisse abspeichert. 23 Bit + 1 implizites = 24.

    Der Rest sind Folgefehler.

    Dann stimmt auch auf einmal der Exponent (133 - 127 = 6 = korrekter Exponent für 123, da 123 = 1,..... * 2^6).

    Wenn du wirklich direkt Bit-Gefummel betreiben willst, dann würde ich an deiner Stelle eher die diversen Konstanten aus IEEE 754 direkt irgendwo in den Code schreiben. Andere Floating-Point Representationen könnten gänzlich anders aussehen, wodurch dein "generischer" Code sowieso versagen würde. Nebenbei wird es schon kaum möglich sein einen "generischen" Bitfummler Code zu bauen der mit 32 Bit und 64 Bit Floats nach IEEE 754 klarkommt, da die führende 1 in der Mantisse bei 64 Bit Floats nach IEEE 754 nämlich sehr wohl abgespeichert wird (im Gegensatz zu 32 Bit Floats nach IEEE 754). Also zumindest nicht ohne 32/64 Bit Fallunterscheidung.


Anmelden zum Antworten