Festkomma-Arithmetik



  • Hallo,
    ich habe mich in den letzten Tagen damit beschäftigt, ob ich es hinbekomme, eine Template-Bibliothek für Festkomma-Arithmetik umzusetzen. Ich habe mir das recht einfach vorgestellt, und zunächst einmal kann man sich damit natürlich allerhand magische Konstanten usw. sparen, allerdings bleibt da immer das Problem der Korrektheit bestehen.
    Wenn man sich die Festkomma-Berechnungen in normalen C-Programmen so anschaut, so sind sie meist recht kurz ausgedrückt, strotzen vor magischen Konstanten, und sind durchspickt von Annahmen die nirgends tatsächlich überprüft werden und eben auch nur implizit im Code stehen.
    Wenn ich nun aber eine Template-Bibliothek bastele, dann muss diese ja zunächst einmal korrekt arbeiten, das heißt, man muss ihr explizit sagen, was sie durchführen darf (Rundungen etc.). Das führt nach meinem jetztigen Stand dazu, dass die Berechnung mit dieser Bibliothek zwar korrekt sind und man explizit sieht welche Tradeoffs in der Berechnung gemacht werden (und dass die Bibliothek Annahmen auch tatsächlich überprüfen kann), allerdings sind die Berechnungen im Quelltext so auch sehr viel ausufernder, also kein "int32((int64(a)<<20)/(foo>>2)) + (d>>2)" mehr, sondern eben "sub< Annahmen >(div< Annahmen >(a, foo), d)" z.B..
    Außerdem finde ich eigentlich keinen praktibalen Ansatz um die gewöhnlichen Operatoren für die Berechnungen zu benutzen, weil die ja im Allgemeinen die pessimistischsten Annahmen zum Willen des Programmierers treffen müssten (3.3 + 3.3 = 4.3, usw.), und die Berechnungen somit schnell unpraktikabel werden, wenn man nicht am Anfang extrem mit signifikanten Stellen geizt.

    Daher meine Frage: Könnt ihr euch vorstellen, dass eine solche Bibliothek auf Aktzeptanz stoßen kann, wenn man mit ihr praktisch dazu gezwungen wird, ausführlicher zu schreiben, was passieren soll, egal ob man damit magische Konstanten vermeidet, problematische C-Typkonvertierungen umschifft werden, Wertebreiche überprüft werden können usw.?



  • Also Code sagt ja bekanntlich mehr als tausend Worte.
    Würdet ihr das so verwenden wollen?

    void divbla() {
    	using nom = fix::sfixed<10, 12>;
    	using den = fix::sfixed<2, 5>;
    
    	constexpr auto result = fix::div<fix::fits<3,5>, fix::positive, fix::towards_zero>(nom::from(-3.213), den::from(-1.523));
    	static_assert(std::is_same<std::decay_t<decltype(result)>, fix::ufixed<3, 5>>::value, "Foooo!");
    	constexpr auto value = result.to<double>();
    }
    


  • Wobei man es natürlich auch kurz haben kann, aber dann natürlich kaum Kontrolle über das Ergebnis hat...

    void divbla2()
    {
    	using nom = fix::sfixed<10, 12>;
    	using den = fix::sfixed<2, 5>;
    
    	constexpr auto result = fix::div<fix::positive>(nom::from(-3.213), den::from(-1.523));
    	static_assert(std::is_same<std::decay_t<decltype(result)>, fix::ufixed<15,17>>::value, "Foooo!");
    	constexpr auto value = result.to<double>();
    }
    


  • Naja, ich schätze mal man käme als Anwender mit einer überschaubaren Anzahl von Aliasen normalerweise ganz gut klar:

    auto div = fix::div<fix::fits<3,5>, fix::positive, fix::towards_zero>;
    
    auto result = div(nom::from(-3.213), den::from(-1.523));
    


  • Bis auf sehr spezielle Faelle wuerde niemand sowas verwenden wollen.



  • Care to elaborate? Klingt irgendwie dünn, die Argumentation 😉



  • Genau der Grund.



  • template< int I, int F >
    constexpr auto sine3_1stq(fix::sfixed<I, F> angle)
    {
    	// "taylor": 1/2 * angle * (3 - angle*angle)
    	// wasting a bit, since the lib doesnt know that angle is always [-1 ... +1] (maybe add max/min to the type?) :(
    	return (angle * (FIXED_INTEGER(3) - angle*angle)).virtual_shift<-1>();
    }
    
    template< int I >
    constexpr auto sine3(fix::sfixed<I, 0> angle)
    {
    	return sine3_1stq(angle.virtual_shift<-I+1>());
    }
    
    #define CA constexpr auto
    
    void sine_test2()
    {
    	using namespace fix;
    	CA angle = sfixed<12, 0>(222);
    	CA angle_val = angle.virtual_shift<-23>().to<double>() * 90;
    	CA val = sine3(angle);		// type is sfixed 3.13
    	CA conv = val.to<double>();
    }
    

    Wenn man einfach die Anzahl der signifikanten Bits (bzw. Bits der Quelloperanden) als Zielgröße festlegt, als Standard, funktioniert das schon ganz gut! 🙂
    Wenn man außerdem jetzt noch den Min- und Maxwert mitgeben würde, für jeden Wert, dann würden auch keine Bits verschwendet werden. Aber irgendwie habe ich im Moment erstmal keine Lust mehr 😉
    Zwischenergebnisse unter https://github.com/decimad/fixed zu finden (fixedtest.cpp zeigt dann das ganze Ausmaß des Verbrechens). Der Offset-Member ist letztlich leider total unnütz, denke ich.
    Ist jede Menge undefined dingsbums drin und wohl auch ansonten Fehler... Nehme gerne Hinweise auf.



  • So, jetzt werden auch die Wertebereiche mitgeschoben durch die Rechnungen...
    Was ich echt cool finde ist, wie Visual C++ 2015 das alles mit IntelliSense durchrechnet... Weiß jemand ob man Eclipse-CDT auch irgendwie dazu bewegen kann?

    Und noch eine andere Sache... während man das so mit Templates hinbugsiert nervt außerordentlich, dass man für die Compile-Zeit nicht einen Integer-Typ hat, der größer ist als alle Integer-Typen, die Laufzeitunterstützung haben. In dem Korsett die Grenzfälle hinzubekommen macht keinen Spaß... Würde mich freuen, falls mich jemand auf bessere Lösungen aufmerksam machen könnte.


Anmelden zum Antworten