Forum: Compiler & IDEs Festkommazahlen in Würde


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Klaus W. (mfgkw)


Lesenswert?

Mahlzeit!

Ich habe da mal eine Schnapsidee zu Festkommazahlen; vielleicht
interessiert es ja auch noch andere.

In letzter Zeit gab es bei mehreren Threads das Stichwort
Festkommazahl ([[Skalierung auf Int bei einer FFT (128-Punkte)]], aber
auch andere).
Das scheint gemeinhin so gehandhabt zu werden, daß man sich
hoffentlich erstmal klar wird, wie man uint*_t oder int*_t auf Vor-
und Nachkommastellen aufteilt und damit skalierte Werte bekommt.
Additionen und Subtraktionen gehen wie gehabt (wenn man nicht
versehentlich unterschiedliche Typen mischt), bei Multiplikationen muß
man händisch die Skalierung korrigieren. Zwischenergebnisse können
gerne über oder unterlaufen, auch wenn das Endergebnis harmlos ist.

Der Typ den C sieht, ist erstmal eine normale ganze Zahl.
Irgendeine Unterstützung seitens des Compilers (Typkontrolle,
Korrektur der Skalierung, Typkonvertierung): komplette Fehlanzeige...

Im Endeffekt hat man in seinem Quelltext viel Handarbeit, was viele
Fehler provoziert und den Quelltext schnell unlesbar macht, was
wiederum mehr Fehler provoziert.

Das finde ich ziemlich unbefriedigend.

(Ebenso unbefriedigend finde ich, daß man in so einem Forum vielen
Leuten immer mühsam beibringen muß, was sie beachten müssen.)

Man kann also relativ sauber und elegant programmieren, wenn man mit
ganzen Zahlen auskommt. Sobald das nicht mehr geht, hat man die Wahl
zwischen Pest und Cholera: Festkommazahlen in Handarbeit mit allen
Nachteilen, oder float/double mit dem Problem der Geschwindigkeit, die
gegen 0 konvergiert.

Was man in C dagegen unternehmen könnte, ist eigentlich nicht viel.

Mit Präprozessormakros kommt man nicht sehr weit und handelt sich
gleich wieder neue, noch gemeinere Fehler ein.
Also bleiben eigentlich nur Funktionen, die man sich mühsam schreibt
und genauso mühsam anwendet.

Für einen Festkommatyp, der z.B. auf 16 Bit Zahlen (unsigned) mit 5
binären Nachkommastellen arbeitet, müsste man sich Funktionen
schreiben, was dann beim Aufruf etwa so aussehen könnte:
1
  // irgendeine Rechnerei mit Gleitkomazahlen:
2
  double a = 1.5, b = 5, c;
3
  c = (25+3*a)/(a-20+b++);
4
5
6
  // das Gleiche wie oben, aber jetzt mit Fixkommazahlen:
7
#include <ganzfix_16_5.h>
8
  ganzfix_u16_5_t
9
    a = ganzfix_u16_5_set_double( 1.5 ),
10
    b = ganzfix_u16_5_set_int( 5 ),
11
    c;
12
13
  c = ganzfix_u16_5_div( ganzfix_u16_5_add( ganzfix_u16_5_set_int( 25 ), ganzfix_u16_5_mul( ganzfix_u16_5_set_int( 3 ), a ) ), ganzfix_u16_5_add( ganzfix_u16_5_sub( a, ganzfix_u16_5_set_int( 20 ) ), b ) );
14
  b = ganzfix_u16_5_add( b, ganzfix_u16_5_set_int( 1 ) );

Als lesbar würde ich so einen Quelltext nicht bezeichnen; das
Umbrechen habe ich mir gleich gespart, weil es nichts bringt.

Die ganzen Funktionen, die man für allerlei Operationen
inkl. Konvertierungen so braucht, müssten für jede Variante von
Festkommatyp (signed/unsigned, diverse Gesamtlängen und
Nachkommastellen) geschrieben werden.

Wenigstens wird es halbwegs laufzeiteffizient, falls man die
Funktionen inline bekommt. Falls nicht, gute Nacht!

Irgendwie ist das auch kein Fortschritt, sondern ziemlich unwürdig.

Dann gibt es aber jetzt schon seit einiger Zeit C++.
Da hat man parametrierbare Typen (template<...> class...) und
überladene Operatoren.
Auch wenn ich jetzt das böse Wort "C++" benutzt habe und einige gleich
aufschreien, daß das auf einem Atmel doch gar nicht vernünftig geht,
habe ich es mal ausprobiert und finde es gar nicht so schlecht.

Weiter unten (nicht gleich jetzt, um niemanden unnötig zu
verschrecken) kommt eine Headerdatei fixpoint.h.

Um jetzt mal zu sehen, wie es mit Codegröße und Rechenzeit aussieht,
habe ich mir ein Testprogramm AVR_LCD44780_fixpoint.cpp gebastelt (das
war vorher ein Programm, um die LCD-Ausgabe zu probieren).
Hier nur der interessante Teil, was den Test angeht:
1
#include "lcd-routines.h"
2
#include "fixpoint.h"
3
4
// Eine der folgenden Zeilen aktivieren, den Rest auskommentieren:
5
6
typedef double                                      test_t;  typedef double   const_t; const char*t_text="double";
7
//typedef uint8_t                                     test_t;  typedef uint8_t  const_t; const char*t_text="uint8_t";
8
//typedef uint16_t                                    test_t;  typedef uint16_t const_t; const char*t_text="uint16_t";
9
//typedef uint32_t                                    test_t;  typedef uint32_t const_t; const char*t_text="uint32_t";
10
//typedef uint64_t                                    test_t;  typedef uint64_t const_t; const char*t_text="uint64_t";
11
//typedef Anyware::fixpoint< uint8_t, uint8_t, 1 >    test_t;  typedef uint8_t  const_t; const char*t_text="f(8,8)";
12
//typedef Anyware::fixpoint< uint8_t, uint16_t, 1 >   test_t;  typedef uint8_t  const_t; const char*t_text="f(8,16)";
13
//typedef Anyware::fixpoint< uint16_t, uint16_t, 1 >  test_t;  typedef uint16_t const_t; const char*t_text="f(16,16)";
14
//typedef Anyware::fixpoint< uint16_t, uint32_t, 1 >  test_t;  typedef uint16_t const_t; const char*t_text="f(16,32)";
15
//typedef Anyware::fixpoint< uint32_t, uint32_t, 1 >  test_t;  typedef uint32_t const_t; const char*t_text="f(32,32)";
16
//typedef Anyware::fixpoint< uint32_t, uint64_t, 1 >  test_t;  typedef uint32_t const_t; const char*t_text="f(32,64)";
17
//typedef Anyware::fixpoint< uint64_t, uint64_t, 1 >  test_t;  typedef uint64_t const_t; const char*t_text="f(64,64)";
18
19
20
volatile uint8_t       bMessungLaeuft  =  0;
21
const int              laufzeit_sec    = 10;
22
23
#ifdef __cplusplus
24
const char *lang_text = "C++";
25
#else
26
const char *lang_text = "C";
27
#endif /* ifdef __cplusplus */
28
29
30
31
int main( int nargs, char **args )
32
{
33
  char          tmp_puffer[30];
34
  uint32_t      nGeschafft = 0;
35
  // Werte, mit denen etwas gerechnet werden soll:
36
  test_t       a = (const_t)3, b = (const_t)1, c = (const_t)1;
37
38
  // LCD initialisieren, Timer starten
39
  // ...
40
41
42
  bMessungLaeuft = 1;
43
  while( bMessungLaeuft )
44
  {
45
    size_t       i;
46
    const size_t nLoop = 10;
47
    a = (const_t)3;
48
    b = (const_t)1;
49
    c = (const_t)1;
50
51
    for( i=0; i<nLoop; ++i )
52
    {
53
      if( (i%4)<2 )
54
      {
55
        b *= (const_t)2;
56
        a += b;
57
        c += a*b;
58
      }
59
      else
60
      {
61
        c -= a*b;
62
        a -= b;
63
        b /= (const_t)2;
64
      }
65
    }
66
    ++nGeschafft; // Durchläufe zählen
67
  }
68
69
  lcd_clear();
70
  lcd_set_cursor( 0, 1 );
71
  sprintf( tmp_puffer, "%.0f %s %s", (double)c, t_text, lang_text );
72
  lcd_string( tmp_puffer );
73
  lcd_set_cursor( 0, 2 );
74
  sprintf( tmp_puffer, "%.1f / sec  ", nGeschafft/(double)laufzeit_sec );
75
  lcd_string( tmp_puffer );
76
77
  while( "warten aufs jüngste Gericht" )
78
  {
79
  }
80
...

Daneben gibt es noch eine Interruptroutine, die nach (laufzeit_sec) 
Sekunden den Wert von (bMessungLaeuft) zu 0 setzt und damit die
Meßschleife beendet.
Nach der Meßschleife wird die Anzahl der Schleifendurchläufe pro
Sekunde ausgegeben.

Durch Auskommentieren je einer der typedef kann man die verschiedenen
Typen leicht ausprobieren.

Die Typen double und uint... sind die gewohnten aus C, dagegen sind
die Typen Anyware::fixpoint< uint8_t, uint8_t, 1 > und folgende in
fixpoint.h als template definiert.
Mit den Parametern (hier: < uint8_t, uint8_t, 1 >) kann man den Typ
zurechtschneidern.

Dabei ist der erste Parameter der Grunddatentyp, der für die Rechnung
und für das Speichern von Werten verwendet wird. Er definiert also,
wieviele Stellen vor und nach dem Komma insgesamt zur Verfügung
stehen.
Es kann ein uint...- oder ein int...-Typ sein.

Der zweite Parameter gibt an, welcher Datentyp an einigen Stellen
intern für Zwischenwerte verwendet werden soll, wenn der Grunddatentyp
möglicherweise zu kurz ist.
Bei uint16_t als Grunddatentyp würde also uint32_t als zweiter
Parameter an diesen Stellen einen Überlauf verhindern.
Nutzt man den Wertebereich nicht ganz aus, kann man aber auch den
zweiten Parameter gleich dem ersten setzen und spart Rechenzeit.

Der dritte Parameter steht für die Anzahl Nachkommastellen.
Bei Anyware::fixpoint< uint8_t, uint8_t, 1 > hätte man 7 Vor- und
1 Nachkommastelle, man hätte damit die möglichen Werte 0, 0.5, 1.0, ...,
127.0, 127.5.
Es ist auch möglich, hier einen negativen Wert anzugeben. Dann hat man
einen größeren Zahlenbereich mit einer gröberen Abstufung als 1.
Anyware::fixpoint< uint8_t, uint8_t, -2 > könnte die Werte 0, 4, 8,
..., 1016, 1020 annehmen.

Je nachdem, welchen Datentyp im obigen Testprogramm und je nachdem, ob
man das Programm mit C oder C++ kompiliert, kommen folgende Laufzeiten
und Codegrößen raus (Mega8 mit 16 MHz):
1
                                  |     n/sec     |    .text    |
2
                                  |  C    |  C++  |  C   |  C++ |
3
  --------------------------------+-------+-------+------+------+
4
  double                          |  3468 |  3468 | 4876 | 4876 |
5
  --------------------------------+-------+-------+------+------+
6
  uint8_t                         | 73487 | 73487 | 4312 | 4312 |
7
  uint16_t                        | 51721 | 51721 | 4348 | 4348 |
8
  uint32_t                        | 19614 | 19614 | 4500 | 4500 |
9
  uint64_t                        |  1038 |  1038 | 6524 | 6524 |
10
  --------------------------------+-------+-------+------+------+
11
  fixpoint<uint8_t,uint8_t,1>     |       | 13161 |      | 4584 |
12
  fixpoint<uint8_t,uint16_t,1>    |       | 31907 |      | 4552 |
13
  fixpoint<uint16_t,uint16_t,1>   |       | 30300 |      | 4560 |
14
  fixpoint<uint16_t,uint32_t,1>   |       | 12859 |      | 4726 |
15
  fixpoint<uint32_t,uint32_t,1>   |       | 12629 |      | 4760 |
16
  fixpoint<uint32_t,uint64_t,1>   |       |   626 |      | 6528 |
17
  fixpoint<uint64_t,uint64_t,1>   |       |   597 |      | 7206 |
18
  --------------------------------+-------+-------+------+------+

Daran sieht man jedenfalls schon mal, daß es -unter sonst gleichen
Umständen- vollkommen egal ist, ob man das Testprogramm als C oder C++
kompiliert (soweit es den jeweiligen Typ in C überhaupt gibt
natürlich).

Also lasse ich C mal forsch weg. Das Argument, daß C++ zuviel Overhead
für MCs hat, gilt nicht für alles in C++. Bei aufwendigen Klassen,
womöglich mit virtuellen Methoden und Resourcenallokierung, Strings,
Streams etc. gilt das sicher. Aber bei Ausdrücken, die in C auch
gültig wären, hat man sowieso keine Nachteile.

Interessant wäre jetzt also, wie sich (unabhängig von C/C++)
template-Funktionen gegenüber handgeschriebenen Funktionen verhalten.

Beispiel: Vergleich Anzahl pro Sekunde uint16_t gegenüber
fixpoint<uint16_t,uint16_t,1>.
Bei uint16_t kommt man auf 51721 pro Sekunde, bei fixpoint auf 30300.
Dabei muß man aber auch beachten, daß die uint16_t-Rechnung deutlich
einfacher ist, weil es eben keinerlei Skalierung gibt gegenüber der
Fixpunktzahl.
Würde man mit uint16_t manuell eine Fixpunktarithmetik bauen, müsste
man auch etwas Rechenzeit opfern. Ich gehe davon aus, daß damit eine
handgeschriebene Version zumindest keinen großen Laufzeitvorteil
hätte.

Auch die Codegröße (4348 bei uint16_t gegenüber 4560 bei fixpoint)
lässt nicht erwarten, daß man mit handgeschriebener Fixpunktarithmetik
wesentlich besser liegen würde.

Insgesamt sieht mir das gut genug aus, daß ich in Zukunft keine
Festkommazahlen mehr von Hand anfasse, sondern lieber C++ nehme.


Und hier die versprochene fixpoint.h:
1
////////////////////////////////////////////////////////////////////////////////
2
//
3
// Time-stamp: "09.07.09 18:20 fixpoint.h klaus?wachtler.de"
4
//
5
////////////////////////////////////////////////////////////////////////////////
6
//
7
//
8
//     C++ templates defining numerical fixed point types
9
//     --------------------------------------------------
10
//
11
//
12
// The functions are intended to be light weight and useful on small
13
// systems like micro controllers.
14
//
15
// Speed and code size might be a little worse than integral
16
// calculations but much better than floating point arithmetic
17
// especially on systems without FPU.
18
//
19
// Tested with g++ 4.3.2 and avr-gcc 4.3.2.
20
//
21
// ATTENTION!
22
// The code is tested only partially (as from 09.07.2009).
23
// Not usable for production code.
24
//
25
////////////////////////////////////////////////////////////////////////////////
26
//
27
// (C) 2009 Klaus Wachtler
28
//          Breidingstr. 17
29
//          D-29614 Soltau
30
//          Phone: +49-171-4553039
31
//          Mail:  fixpoint<you guess the character?>wachtler.de
32
//
33
// This program is free software; you can redistribute it and/or
34
// modify it under the terms of the GNU General Public License as
35
// published by the Free Software Foundation; either version 3 of the
36
// License, or (at your option) any later version.
37
//
38
// This program is distributed in the hope that it will be useful, but
39
// WITHOUT ANY WARRANTY; without even the implied warranty of
40
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
41
// General Public License for more details.
42
// You should have received a copy of the GNU General Public License
43
// along with this program; if not, see <http://www.gnu.org/licenses/>.
44
//
45
////////////////////////////////////////////////////////////////////////////////
46
//
47
// Template arguments:
48
//////////////////////
49
//
50
// - TBASE is some usual signed or unsigned integral type like
51
//   signed/unsigned int, int32_t, uint32_t etc..
52
//   TBASE is used to keep an internal value which has to be scaled to
53
//   get the correct number.
54
//
55
// - TTEMP is another integral type used for values subject to
56
//   overflow if TBASE would be used.
57
//   You might want to use the same type for TBASE and TTEMP (like
58
//   fixpoint< int16_t, int16_t >) as long as you ensure to keep
59
//   intermediate results like in a*b within valid range.
60
//   If in doubt choose TTEMP one step larger than TBASE, so
61
//   fixpoint< uint8_t, uint16_t > or fixpoint< int32_t, int64_t >
62
//   seem to be a good choice.
63
//   Default ist TBASE.
64
//
65
// - LFRACTIONAL scales this value and determines range and
66
//   accuracy. It counts in binary digits.
67
//   A positive value of LFRACTIONAL gives a type with reduced range
68
//   and accuracy better than 1 by using LFRACTIONAL binary digits as
69
//   fractional part (LFRACTIONAL digits are used for the fractional
70
//   part, the rest of TBASE is used for the integer part),
71
//   while negative values of LFRACTIONAL give an extended range with
72
//   less accuracy (compared to TBASE).
73
//   * Example 1:
74
//     fixpoint< uint16_t, 3 > is based on uint16_t scaled
75
//     by 2**3=8.
76
//     While uint16_t has a range from 0 to 65535 with steps of 1
77
//     fixpoint< uint16_t, 3 > has values from 0 to 8191.875 (65535/8)
78
//     with steps of 1/8=0.125. The fractional part is 3 binary
79
//     digits.
80
//     Possible values are 0, 0.125, 0.25, 0.375, 0.5 ... 8191.875.
81
//   * Example 2:
82
//     fixpoint< uint8_t, -2 > is based on uint8_t scaled
83
//     by 2**(-2)=1/4.
84
//     The values range from 0 (0*4) to 1020 (255*4) in steps of 4.
85
//     Possible values are 0, 4, 8, 12, ... 1020.
86
//   * Example 3:
87
//     fixpoint< uint8_t, 0 > is based on uint8_t without scaling.
88
//     This resembles uint8_t with values from 0 to 255.
89
//   * Example 4:
90
//     fixpoint< int8_t, -2 > is based on int8_t scaled
91
//     by 2**(-2)=1/4.
92
//     The values range from -512 (-128*4) to +508 (127*4) in steps of 4.
93
//     Possible values are -512, -508, -502, ... -4, 0, +4, ... +508.
94
//
95
// - ROUNDTONEAREST determines if assignments and initializations
96
//   cut off trailing digits (false) or rounds up to the nearest
97
//   integral value (true).
98
//   Default is true.
99
//
100
////////////////////////////////////////////////////////////////////////////////
101
//
102
// Usage:
103
/////////
104
//
105
// Compilation:
106
//
107
// - Plain C is not supported. Use C++.
108
// - Use #include <fixpoint.h> and give -I... if necessary.
109
// - No libs needed (everything is inlined).
110
// - Compile with -DNO_STDSTRING or #define NO_STDSTRING before
111
//   #include <fixpoint.h> if no std::string is available.
112
// - Compile with -DNO_STDIOSTREAM or #define NO_STDIOSTREAM before
113
//   #include <fixpoint.h> if no iostreams are available.
114
// - Don't worry about warnings saying "shift count is negative".
115
//   This occurs in dead code which will be optimized away by the
116
//   compiler later.
117
//
118
//
119
// Object creation:
120
//
121
// fixpoint objects can be created either:
122
// - without arguments (resulting in a 0 value)
123
// - an integral value (with their base type); values being not
124
//   integral can not be defined this way even if LFRACTIONAL is
125
//   positive
126
// - two integral values a and b (resulting in a/b, which will be cut
127
//   off or rounded to a valid value  depending on template argument
128
//   ROUNDTONEAREST); this is done by a template method with any
129
//   integral types for a and b and usual C style promotions
130
//   performing the division.
131
//   If LFRACTIONAL is positive the type TTEMP should be wide enough to
132
//   hold a<<LFRACTIONAL. If LFRACTIONAL is negative TTEMP should be
133
//   able to keep b<<LFRACTIONAL.
134
// - a floating point number (which will be cut off or rounded
135
//   to a valid value depending on template argument ROUNDTONEAREST)
136
//
137
//
138
// Valid operations:
139
//
140
//            | type of a | type of b | result   | lvalue | notes
141
// -----------+-----------+-----------+----------+--------+------------------------------
142
// f()        |           |           | fixpoint |        | constructor
143
// -----------+-----------+-----------+----------+--------+------------------------------
144
// f(a)       | base type |           | fixpoint |        | constructor (1)
145
// -----------+-----------+-----------+----------+--------+------------------------------
146
// f(a)       | double    |           | fixpoint |        | constructor
147
// -----------+-----------+-----------+----------+--------+------------------------------
148
// f(a,b)     | integral  | integral  | fixpoint |        | constructor (1)
149
// -----------+-----------+-----------+----------+--------+------------------------------
150
// !a         | fixpoint  |           | bool     |        | true if a==0
151
// -----------+-----------+-----------+----------+--------+------------------------------
152
// ++a        | fixpoint  |           | fixpoint | yes    | nop if LFRACTIONAL<0
153
// -----------+-----------+-----------+----------+--------+------------------------------
154
// a++        | fixpoint  |           | fixpoint |        | nop if LFRACTIONAL<0
155
// -----------+-----------+-----------+----------+--------+------------------------------
156
// --a        | fixpoint  |           | fixpoint | yes    | nop if LFRACTIONAL<0
157
// -----------+-----------+-----------+----------+--------+------------------------------
158
// a--        | fixpoint  |           | fixpoint |        | nop if LFRACTIONAL<0
159
// -----------+-----------+-----------+----------+--------+------------------------------
160
// +a         | fixpoint  |           | fixpoint |        | nop
161
// -----------+-----------+-----------+----------+--------+------------------------------
162
// -a         | fixpoint  |           | fixpoint |        |
163
// -----------+-----------+-----------+----------+--------+------------------------------
164
// a*b        | fixpoint  | fixpoint  | fixpoint |        | (1)
165
// -----------+-----------+-----------+----------+--------+------------------------------
166
// a/b        | fixpoint  | fixpoint  | fixpoint |        |
167
// -----------+-----------+-----------+----------+--------+------------------------------
168
// a%b        | fixpoint  | fixpoint  | fixpoint |        | x-n*y with n=x/y rounded -> 0
169
// -----------+-----------+-----------+----------+--------+------------------------------
170
// a+b        | fixpoint  | fixpoint  | fixpoint |        |
171
// -----------+-----------+-----------+----------+--------+------------------------------
172
// a-b        | fixpoint  | fixpoint  | fixpoint |        |
173
// -----------+-----------+-----------+----------+--------+------------------------------
174
// a<b        | fixpoint  | fixpoint  | bool     |        |
175
// -----------+-----------+-----------+----------+--------+------------------------------
176
// a>b        | fixpoint  | fixpoint  | bool     |        |
177
// -----------+-----------+-----------+----------+--------+------------------------------
178
// a<=b       | fixpoint  | fixpoint  | bool     |        |
179
// -----------+-----------+-----------+----------+--------+------------------------------
180
// a>=b       | fixpoint  | fixpoint  | bool     |        |
181
// -----------+-----------+-----------+----------+--------+------------------------------
182
// ==         | fixpoint  | fixpoint  | bool     |        |
183
// -----------+-----------+-----------+----------+--------+------------------------------
184
// !=         | fixpoint  | fixpoint  | bool     |        |
185
// -----------+-----------+-----------+----------+--------+------------------------------
186
// a&&b       | fixpoint  | fixpoint  | bool     |        |
187
// -----------+-----------+-----------+----------+--------+------------------------------
188
// a||b       | fixpoint  | fixpoint  | bool     |        |
189
// -----------+-----------+-----------+----------+--------+------------------------------
190
// a=b        | fixpoint  | any (2)   | fixpoint |        |
191
// -----------+-----------+-----------+----------+--------+------------------------------
192
// a+=b       | fixpoint  | any (2)   | fixpoint |        |
193
// -----------+-----------+-----------+----------+--------+------------------------------
194
// a-=b       | fixpoint  | any (2)   | fixpoint |        |
195
// -----------+-----------+-----------+----------+--------+------------------------------
196
// a*=b       | fixpoint  | any (2)   | fixpoint |        | (1)
197
// -----------+-----------+-----------+----------+--------+------------------------------
198
// a/=b       | fixpoint  | any (2)   | fixpoint |        |
199
// -----------+-----------+-----------+----------+--------+------------------------------
200
// a%=b       | fixpoint  | any (2)   | fixpoint |        |
201
// -----------+-----------+-----------+----------+--------+------------------------------
202
// (double)   | fixpoint  |           | double   |        |
203
// -----------+-----------+-----------+----------+--------+------------------------------
204
// (int8_t)   | fixpoint  |           | int8_t   |        | (1)
205
// -----------+-----------+-----------+----------+--------+------------------------------
206
// (int16_t)  | fixpoint  |           | int16_t  |        | (1)
207
// -----------+-----------+-----------+----------+--------+------------------------------
208
// (int32_t)  | fixpoint  |           | int32_t  |        | (1)
209
// -----------+-----------+-----------+----------+--------+------------------------------
210
// (int64_t)  | fixpoint  |           | int64_t  |        | (1)
211
// -----------+-----------+-----------+----------+--------+------------------------------
212
// (uint8_t)  | fixpoint  |           | uint8_t  |        | (1)
213
// -----------+-----------+-----------+----------+--------+------------------------------
214
// (uint16_t) | fixpoint  |           | uint16_t |        | (1)
215
// -----------+-----------+-----------+----------+--------+------------------------------
216
// (uint32_t) | fixpoint  |           | uint32_t |        | (1)
217
// -----------+-----------+-----------+----------+--------+------------------------------
218
// (uint64_t) | fixpoint  |           | uint64_t |        | (1)
219
// -----------+-----------+-----------+----------+--------+------------------------------
220
//
221
// (1) In operations like a*b an intermediate result might overflow
222
//     if TTEMP is as narrow as TBASE.
223
//     Please take care of the values and ranges. If in doubt choose
224
//     TTEMP wider than TBASE.
225
//
226
// (2) "any" means any type which can be converted to fixpoint,
227
//     i.e. the same fixpoint type, any integral type, float and double.
228
//
229
//
230
// There is no implicit conversion between different fixpoint types.
231
// Please convert manually. Example:
232
// Anyware::fixpoint< int16_t, int32_t, 2 >   a( 4 );
233
// Anyware::fixpoint< int32_t, int32_t, 8 >   b( a.getBaseValue(),
234
//                                               1<<a.getScaleBinaryExponent()
235
//                                               );
236
//
237
//
238
////////////////////////////////////////////////////////////////////////////////
239
//
240
// History:
241
///////////
242
//
243
// 08.07.2009 kw     - created
244
//                   - tested on Linux + g++ 4.3.2 and avr-g++ 4.3.2
245
//
246
// 09.07.2009 kw     - template argument TTEMP
247
//                   - types for intermediate values changed
248
//                   - more comments
249
//                   - test programs
250
//
251
//  kw     
252
//
253
//  kw     
254
//
255
//  kw     
256
//
257
//  kw     
258
//
259
////////////////////////////////////////////////////////////////////////////////
260
//
261
// TODO:
262
////////
263
//
264
// - testing all cases (with rounding and without)
265
// - external documentation
266
// - take care for ROUNDTONEAREST in operator ...int...
267
// - 
268
// - 
269
// - 
270
// - 
271
// - 
272
//
273
////////////////////////////////////////////////////////////////////////////////
274
//
275
// There are 2 demo programs:
276
//
277
// - testfixpoint_linux.cpp shows basic usage and makes some tests,
278
//   written for a PC system running Linux. It might or might not work on
279
//   other systems; there is no GUI needed.
280
//
281
// - AVR_LCD44780_fixpoint.cpp, lcd-routines.c and lcd-routines.h are
282
//   used to build a test program running on an Atmel AVR (AtMega8
283
//   tested).
284
//
285
//   It calculates some things in a loop until 10 seconds are elapsed
286
//   and writes the loop count to an LCD (Hitachi HDC44780).
287
//   For more information on connecting the AVR and the LCD see
288
//   AVR_LCD44780_fixpoint.cpp.
289
//   Changes on pin usage may be done in lcd-routines.h.
290
//
291
//   Inside AVR_LCD44780_fixpoint.cpp there is a typedef where you can
292
//   set the type used for all calculations.
293
//   As long as you use C types here (int16_t, uint32_t, float etc.;
294
//   no fixpoint) the source file may be renamed to
295
//   AVR_LCD44780_fixpoint.c and compiled as traditional C for
296
//   comparison.
297
//
298
//   There are two makefiles supplied: Makefile_AVR_c and Makefile_AVR_cpp
299
//   can be used to compile and flash the program on my system (atmega8,
300
//   16 MHz, siprog using /dev/ttyS0) the C resp. C++ version or
301
//   modified as needed.
302
//   The Makefiles are derived from the sample files at [2].
303
//   Caution! The original Makefile from [2] will overwrite the
304
//   C++ source (*.cpp) with the listing, because the replacement in the line:
305
//       CPPFLAGS += -Wa,-adhlns=$(<:.c=.lst)
306
//   fails (since the extension is not .c).
307
//   This leads to a listing file name identical to the source file
308
//   name.
309
//   I changed the listing file name to the full source name and .lst
310
//   appended, e.g. AVR_LCD44780_fixpoint.cpp.lst to avoid the mess.
311
//
312
////////////////////////////////////////////////////////////////////////////////
313
//
314
// [1] Wikipedia Fixed-point arithmetic
315
//     http://en.wikipedia.org/wiki/Fixed-point_arithmetic
316
//
317
//
318
// [2] http://www.mikrocontroller.net/
319
//
320
////////////////////////////////////////////////////////////////////////////////
321
322
323
#define inline
324
325
#ifndef _FIXPOINT_H_
326
#define _FIXPOINT_H_
327
328
// only valid in C++, not C
329
#ifdef __cplusplus
330
331
332
#include <stdint.h>
333
#include <inttypes.h>
334
#include <math.h>
335
336
337
// A prior definition of NO_STDSTRING suppresses the conversion to
338
// std::string and eliminates the need #include <string>
339
//
340
// A prior definition of NO_STDIOSTREAM suppresses output to
341
// std::ostream and input from std::istream.
342
343
#ifndef NO_STDSTRING
344
#include <string>
345
#endif /* ifndef NO_STDSTRING */
346
347
#ifndef NO_STDIOSTREAM
348
#include <iostream>
349
#endif /* ifndef NO_STDIOSTREAM */
350
351
352
namespace Anyware
353
{
354
355
  template< typename TBASE = int16_t,
356
            typename TTEMP = TBASE,
357
            const int LFRACTIONAL = sizeof(TBASE)/2,
358
            const bool ROUNDTONEAREST = true
359
            > class fixpoint
360
  {
361
362
  public:
363
364
    // this type
365
    typedef    fixpoint< TBASE, TTEMP, LFRACTIONAL, ROUNDTONEAREST >   type;
366
367
    //
368
    // constructing
369
    //
370
371
    // ctor with no arguments: 0
372
    fixpoint()
373
      : basevalue( 0 )
374
    {}
375
376
    // ctor with integral value
377
    fixpoint( TBASE init )
378
      : basevalue( LFRACTIONAL>0
379
                   ? ( init << LFRACTIONAL )
380
                   : ( ROUNDTONEAREST
381
                       ? ( (((TTEMP)init)+(1<<(-LFRACTIONAL-1))) >> -LFRACTIONAL )
382
                       : ( init >> -LFRACTIONAL )
383
                       )
384
                   )
385
    {
386
    }
387
388
    // ctor with floating point value
389
    fixpoint( const double &init )
390
      : basevalue( ldexp( init, LFRACTIONAL )
391
                   +
392
                   ( ROUNDTONEAREST ? ( init<0.0 ? -0.5 : +0.5 ) : 0.0 )
393
                   )
394
    {
395
    }
396
397
    // ctor with a and b: initial value is a/b
398
    template< typename TA, typename TB > fixpoint( TA a, TB b )
399
400
      : basevalue( ( ( b<0 ? (void)( (a=-a), (b=-b) ) : (void)0 ),
401
                     ( LFRACTIONAL>0
402
                       ? ( ROUNDTONEAREST
403
                           ? ( a>=0
404
                               ? (((((TTEMP)a)<<LFRACTIONAL)+(b/2))/b)
405
                               : (((((TTEMP)a)<<LFRACTIONAL)-(b/2))/b)
406
                               )
407
                           : (((TTEMP)a)<<LFRACTIONAL)/b
408
                           )
409
                       : ( ROUNDTONEAREST
410
                           ? ( a>0
411
                               ? ((a+(((TTEMP)b)<<(-LFRACTIONAL-1)))
412
                                  /(((TTEMP)b)<<-LFRACTIONAL))
413
                               : ((a-b/2)/(((TTEMP)b)<<-LFRACTIONAL))
414
                               )
415
                           : (a/(((TTEMP)b)<<-LFRACTIONAL)) )
416
                       )
417
                     )
418
                   )
419
    {
420
    }
421
422
423
    //
424
    // conversions
425
    //
426
427
    operator double() const
428
    {
429
      return ldexp( double( basevalue ), -LFRACTIONAL );
430
    }
431
432
    // TODO: take care for ROUNDTONEAREST
433
#define _FIXPOINT_DEFINE_CONVERSION_TO_INTEGRAL(integraltype)   \
434
    operator integraltype() const                               \
435
    {                                                           \
436
      if( LFRACTIONAL>0 )                                       \
437
      {                                                         \
438
        return basevalue >> LFRACTIONAL;                        \
439
      }                                                         \
440
      else if( LFRACTIONAL<0 )                                  \
441
      {                                                         \
442
        return ((integraltype)basevalue) << -LFRACTIONAL;       \
443
      }                                                         \
444
      else                                                      \
445
      {                                                         \
446
        return basevalue;                                       \
447
      }                                                         \
448
    }
449
450
    _FIXPOINT_DEFINE_CONVERSION_TO_INTEGRAL(int8_t);
451
    _FIXPOINT_DEFINE_CONVERSION_TO_INTEGRAL(int16_t);
452
    _FIXPOINT_DEFINE_CONVERSION_TO_INTEGRAL(int32_t);
453
    _FIXPOINT_DEFINE_CONVERSION_TO_INTEGRAL(int64_t);
454
    _FIXPOINT_DEFINE_CONVERSION_TO_INTEGRAL(uint8_t);
455
    _FIXPOINT_DEFINE_CONVERSION_TO_INTEGRAL(uint16_t);
456
    _FIXPOINT_DEFINE_CONVERSION_TO_INTEGRAL(uint32_t);
457
    _FIXPOINT_DEFINE_CONVERSION_TO_INTEGRAL(uint64_t);
458
459
#undef _FIXPOINT_DEFINE_CONVERSION_TO_INTEGRAL
460
461
462
    //
463
    // operations
464
    //
465
466
    // !a
467
    bool operator!() const
468
    {
469
      return basevalue==0;
470
    }
471
472
    // prefix++: ++a returns modified value
473
    type &operator++()
474
    {
475
      if( LFRACTIONAL>=0 )
476
      {
477
        basevalue += 1<<LFRACTIONAL;
478
      }
479
      return *this;
480
    }
481
482
    // postfix++: a++ returns unmodified value
483
    type operator++( int )
484
    {
485
      type oldvalue( *this );
486
      ++*this;
487
      return oldvalue;
488
    }
489
490
    // prefix--: --a returns modified value
491
    type &operator--()
492
    {
493
      if( LFRACTIONAL>=0 )
494
      {
495
        basevalue += 1<<LFRACTIONAL;
496
      }
497
      return *this;
498
    }
499
500
    // postfix--: a-- returns unmodified value
501
    type operator--( int )
502
    {
503
      type oldvalue( *this );
504
      --*this;
505
      return oldvalue;
506
    }
507
508
    // +a does nothing
509
    type operator+()
510
    {
511
      return *this;
512
    }
513
514
    // -a negates
515
    type operator-()
516
    {
517
      type ret;
518
      ret.basevalue = -basevalue;
519
      return ret;
520
    }
521
522
    type operator*( const type &rS ) const
523
    {
524
      TTEMP  tempvalue = TTEMP(basevalue) * rS.basevalue;
525
      if( LFRACTIONAL>0 )
526
      {
527
        if( ROUNDTONEAREST )
528
        {
529
          // get a bit away from 0 to have the result rounded instead
530
          // of truncating
531
          if( tempvalue>0 )
532
          {
533
            tempvalue += 1<<(LFRACTIONAL-1);
534
          }
535
          else if( tempvalue<0 )
536
          {
537
            tempvalue -= 1<<(LFRACTIONAL-1);
538
          }
539
        }
540
        tempvalue >>= LFRACTIONAL;
541
      }
542
      else if( LFRACTIONAL<0 )
543
      {
544
        tempvalue <<= -LFRACTIONAL;
545
      }
546
      type ret;
547
      ret.basevalue = tempvalue;
548
      return ret;
549
    }
550
551
    type operator/( const type &rS ) const
552
    {
553
      if( LFRACTIONAL>0 )
554
      {
555
        return type( TTEMP(basevalue)<<LFRACTIONAL,
556
                     TTEMP(rS.basevalue)<<LFRACTIONAL
557
                     );
558
      }
559
      else
560
      {
561
        return type( basevalue, rS.basevalue );
562
      }
563
    }
564
565
    type operator%( const type &rS ) const
566
    {
567
      type ret;
568
      ret.basevalue = basevalue % rS.basevalue;
569
      return ret;
570
    }
571
572
    // a+b
573
    type operator+( const type &rS ) const
574
    {
575
      type ret;
576
      ret.basevalue = basevalue + rS.basevalue;
577
      return ret;
578
    }
579
580
    // a-b
581
    type operator-( const type &rS ) const
582
    {
583
      type ret;
584
      ret.basevalue = basevalue - rS.basevalue;
585
      return ret;
586
    }
587
588
    // a<b
589
    bool operator<( const type &rS ) const
590
    {
591
      return basevalue<rS.basevalue;
592
    }
593
594
    // a>b
595
    bool operator>( const type &rS ) const
596
    {
597
      return basevalue>rS.basevalue;
598
    }
599
600
    // a<=b
601
    bool operator<=( const type &rS ) const
602
    {
603
      return basevalue<=rS.basevalue;
604
    }
605
606
    // a>=b
607
    bool operator>=( const type &rS ) const
608
    {
609
      return basevalue>=rS.basevalue;
610
    }
611
612
    // a==b
613
    bool operator==( const type &rS ) const
614
    {
615
      return basevalue==rS.basevalue;
616
    }
617
618
    // a!=b
619
    bool operator!=( const type &rS ) const
620
    {
621
      return basevalue!=rS.basevalue;
622
    }
623
624
    // a&&b
625
    bool operator&&( const type &rS ) const
626
    {
627
      return basevalue&&rS.basevalue;
628
    }
629
630
    // a||b
631
    bool operator||( const type &rS ) const
632
    {
633
      return basevalue||rS.basevalue;
634
    }
635
636
    // a=b
637
    template< typename T >
638
    const type &operator=( const T &rS )
639
    {
640
      basevalue = type( rS ).basevalue;
641
      return *this;
642
    }
643
644
    // a+=b
645
    template< typename T >
646
    const type &operator+=( const T &rS )
647
    {
648
      basevalue = ( *this + type( rS ) ).basevalue;
649
      return *this;
650
    }
651
652
    // a-=b
653
    template< typename T >
654
    const type &operator-=( const T &rS )
655
    {
656
      basevalue = ( *this - type( rS ) ).basevalue;
657
      return *this;
658
    }
659
660
    // a*=b
661
    template< typename T >
662
    const type &operator*=( const T &rS )
663
    {
664
      basevalue = ( *this * type( rS ) ).basevalue;
665
      return *this;
666
    }
667
668
    // a/=b
669
    template< typename T >
670
    const type &operator/=( const T &rS )
671
    {
672
      basevalue = ( *this / type( rS ) ).basevalue;
673
      return *this;
674
    }
675
676
    // a%=b
677
    template< typename T >
678
    const type &operator%=( const T &rS )
679
    {
680
      basevalue = ( *this % type( rS ) ).basevalue;
681
      return *this;
682
    }
683
684
685
686
687
    // returns the raw internal value
688
    //
689
    // The represented value might be calculated with
690
    // rawvalue/(2^scalebinaryexponent)
691
    TBASE getBaseValue() const
692
    {
693
      return basevalue;
694
    }
695
696
    // returns the binary exponent of the scale factor (length of
697
    // fractional part)
698
    //
699
    // The represented value might be calculated with
700
    // rawvalue/(2^scalebinaryexponent)
701
    int getScaleBinaryExponent() const
702
    {
703
      return LFRACTIONAL;
704
    }
705
706
    // returns the scale factor
707
    //
708
    // The represented value might be calculated with
709
    // rawvalue/(scale)
710
    double getScale() const
711
    {
712
      if( LFRACTIONAL>0 )
713
      {
714
        return double(1<<LFRACTIONAL);
715
      }
716
      else if( LFRACTIONAL<0 )
717
      {
718
        return 1.0/double(1<<-LFRACTIONAL);
719
      }
720
      else
721
      {
722
        return 1.0;
723
      }
724
    }
725
726
#ifndef NO_STDIOSTREAM
727
728
    // Value will be converted to double for writing into stream.
729
    // Thus all floating point formatting from include <iomanip> like
730
    // std::setprecision(4) will work.
731
    void printToOstream( std::ostream &os ) const
732
    {
733
734
#ifdef TESTFIXPOINT
735
      // test version: write internal value, scaling and resulting
736
      // value:
737
      os << "fixpoint( internal=" << basevalue
738
         << ", scaling=" << LFRACTIONAL
739
         << ", rounding=" << ROUNDTONEAREST
740
         << ", resulting=" << double( *this )
741
         << " )";
742
#else
743
      // final version: write resulting value:
744
      os << double( *this );
745
#endif /* ifdef TESTFIXPOINT */
746
    }
747
748
749
    void readFromIstream( std::istream &is )
750
    {
751
      double   dValue;
752
      is >> dValue;
753
      *this = dValue;
754
    }
755
756
#endif /* ifndef NO_STDIOSTREAM */
757
758
759
  private:
760
761
    TBASE        basevalue;
762
763
764
  }; // template class fixpoint
765
766
767
#ifndef NO_STDIOSTREAM
768
769
  template< typename TBASE,
770
            typename TTEMP,
771
            const int LFRACTIONAL,
772
            const bool ROUNDTONEAREST
773
            >
774
  inline std::ostream &operator<<
775
  ( std::ostream &os,
776
    const Anyware::fixpoint< TBASE, TTEMP, LFRACTIONAL, ROUNDTONEAREST > &f
777
    )
778
  {
779
    f.printToOstream( os );
780
    return os;
781
  }
782
783
  template< typename TBASE,
784
            typename TTEMP,
785
            const int LFRACTIONAL,
786
            const bool ROUNDTONEAREST
787
            >
788
  inline std::istream &operator>>
789
  ( std::istream &is,
790
    const Anyware::fixpoint< TBASE, TTEMP, LFRACTIONAL, ROUNDTONEAREST > &f
791
    )
792
  {
793
    f.readFromIstream( is );
794
    return is;
795
  }
796
797
#endif /* ifndef NO_STDIOSTREAM */
798
799
800
} // namespace Anyware
801
802
803
804
#endif /* ifdef __cplusplus */
805
806
#endif /* ifndef _FIXPOINT_H_ */

Dieser Stand ist sicher nicht produktionsreif, weil er großteils nur
hingeschrieben und weitgehend ungetestet ist.

Ich werde das sicher für mich noch hinfeilen.
Falls noch jemand Interesse daran hat, bin ich aber natürlich für
konstruktive Vorschläge offen...

: Gesperrt durch Moderator
von Mark B. (markbrandis)


Lesenswert?

Da fehlt ein TL;DR ;-)

Ansonsten aber Coolität.

von Klaus W. (mfgkw)


Lesenswert?

Die Zeile
1
#define inline
in fixpoint.h gehört natürlich noch weg, sorry (noch nicht fertig wie 
gesagt...).

Verrätst du mir noch, was TL;DR ist?

von Mark B. (markbrandis)


Lesenswert?

Too Long; Don't Read :-)

(für eine Mini-Zusammenfassung, die man darüber oder darunter schreibt)

von Klaus W. (mfgkw)


Angehängte Dateien:

Lesenswert?

ok, eingesehen.

Aber die meisten hier haben doch eh zuviel Zeit, dann können sie es auch 
ganz lesen ;-)
Und die Überschrift sagt doch schon alles, oder?

Nebenbei noch in aller Kürze die Ausgabe von einem Lauf...

von uLuxx (Gast)


Lesenswert?

Junge, das ist mal was edles, großes Lob! Danke!

von Simon K. (simon) Benutzerseite


Lesenswert?

Nicht schlecht! Wow. Gute Arbeit

Das wäre doch mal was für eine zukünftig avr-libcpp.

Kritik:
Das Template ist so gestaltet, dass man einen zweiten Datentyp hat, der 
bei Overflows benutzt wird.
Frage: Braucht man das überhaupt?

Meiner Meinung nach kann, wenn immer nur uint8_t * uint8_t im Spiel sind 
maximal ein uint16_t herauskommen.

Sprich: Man kann doch automatisch veranlassen, dass auf den nächst 
höheren Datentyp gecastet wird, wenn ein Überlauf passieren könnte. Oder 
man castet bei Multiplikationen einfach immer auf den nächsthöheren Typ.

Das müsste innerhalb des Templates mit ein paar
if (sizeof(T) == 1)
{
    //berechnung mit uint16_t ausführen
}

gehen. Oder irre ich mich jetzt? Zugegebenermaßen habe ich mir jetzt 
nicht über jeden Fall, der auftreten kann Gedanken gemacht, aber 
eigentlich sollte das doch so gehen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Klaus Wachtler schrieb:

> Ich habe da mal eine Schnapsidee zu Festkommazahlen; vielleicht
> interessiert es ja auch noch andere.
>
> In letzter Zeit gab es bei mehreren Threads das Stichwort
> Festkommazahl ([[Skalierung auf Int bei einer FFT (128-Punkte)]], aber
> auch andere).
> Das scheint gemeinhin so gehandhabt zu werden, daß man sich
> hoffentlich erstmal klar wird, wie man uint*_t oder int*_t auf Vor-
> und Nachkommastellen aufteilt und damit skalierte Werte bekommt.
> Additionen und Subtraktionen gehen wie gehabt (wenn man nicht
> versehentlich unterschiedliche Typen mischt), bei Multiplikationen muß
> man händisch die Skalierung korrigieren. Zwischenergebnisse können
> gerne über oder unterlaufen, auch wenn das Endergebnis harmlos ist.
>
> Der Typ den C sieht, ist erstmal eine normale ganze Zahl.
> Irgendeine Unterstützung seitens des Compilers (Typkontrolle,
> Korrektur der Skalierung, Typkonvertierung): komplette Fehlanzeige...

Nein, nicht komplett.

GCC bietet auf GNU-C Ebene Unterstützung für Fixpunkt-Arithmetik 
verschiedener Genauigkeiten, zB als short _Fract, long _Fract, etc. Das 
beinhaltet Arithmetik, Typ-Umwandlungen und Unterstützung im 
Debug-Format dwarf-2. Auch saturierte Typen werden unterstützt:
   http://gcc.gnu.org/onlinedocs/gcc-4.3.3/gcc/Fixed_002dPoint.html#Fixed_002dPoint

Das einzige, klitzekleine Detail das noch fehlt, ist daß sich jemand 
hinsetzte und die Unterstützung in die Derivate einbaut, wo sowas 
sinnvoll ist. Dazu gehören Architekturen ohne FPU wie AVR, MSP, manche 
ARM-Derivate, usw. und auch Architekturen, die Fixpunkt-Arithmetik per 
Hardware unterstützen. Dazu gehörten zB AVRs der ATmega-Familie.

Einfache Befehle würde man direkt in der Maschinenbeschreibung 
untertützen und inline expandieren, komplexere Sachen wie gewohnt in die 
libgcc auslagern und bei Bedarf einen (transparenten) Funktionsaufruf 
machen.

Sooo viel Arbeit ist das garnicht mal, aber es müsste eben jemand 
machen...

Vorteil
 -- Die Quelle wird besser leserlich, da man +, -, *, / ... verwenden
    kann
 -- Transparente Funktionsaufrufe sind effizienter als explizite
 -- Der Compiler kann Optimierungen ausführen
 -- C-Code ist portabel zwischen Compilern, die GNU-C implementieren
    und _Fract unterstützen.
 -- Spart Arbeit das Fixed-Arithmetik-Rädchen zum 10000 Mal neu
    zu erfinden und zu implementieren schnitzen

Nachteil
 -- Kein Standard-C
 -- Kein Schwanz wird sich dazu bereit erklären diese Arbeit
    zu machen, zB für avr-gcc ;o)

Johann

von Klaus W. (mfgkw)


Lesenswert?

@Simon:
Gestern hatte ich auch nur einen Typ, hat mir aber dann doch nicht
gefallen.

Sicher kann man es im Voraus nicht sagen, was man für
Zwischenergebisse braucht.
Für den zweiten Datentyp kommen ernsthaft nur zwei Typen in Fragen:
entweder der erste oder der nächst große (doppelte Länge).

Bei (u)int64_t gibt es gar keinen nächstgrößeren.
Aber wozu multipliziert man uint64_t mit uint_64_t?
Das werden sicher nicht beides Zahlen sein, die am oberen des
Wertebereiches kratzen. Solche Zahlen braucht man z.B. für
Dateipositionen im Terabyte-Bereich, die wird man aber nicht
miteinander multiplizieren, sondern eine große Zahl mit der Größe
einer struct beispielsweise.

Auch bei 32*32 Bit ist nicht immer gesagt, daß man auf 64 Bit
ausweichen muß; das hängt jeweils vom Einzelfall ab.
Deshalb wollte ich es dem Anwender überlassen, ob er es für
nötig hält - schließlich kostet die größere Länge auch gleich
Rechenzeit.

Der temporäre Wert kann größer sein als das letztliche Ergebnis,
z.B. bei Mulitplikation:
1
    type operator*( const type &rS ) const
2
    {
3
      TTEMP  tempvalue = TTEMP(basevalue) * rS.basevalue;
4
      if( LFRACTIONAL>0 )
5
      {
6
        if( ROUNDTONEAREST )
7
        {
8
          // get a bit away from 0 to have the result rounded instead
9
          // of truncating
10
          if( tempvalue>0 )
11
          {
12
            tempvalue += 1<<(LFRACTIONAL-1);
13
          }
14
          else if( tempvalue<0 )
15
          {
16
            tempvalue -= 1<<(LFRACTIONAL-1);
17
          }
18
        }
19
        tempvalue >>= LFRACTIONAL;
20
      }
21
      else if( LFRACTIONAL<0 )
22
      {
23
        tempvalue <<= -LFRACTIONAL;
24
      }
25
      type ret;
26
      ret.basevalue = tempvalue;
27
      return ret;
28
    }

Es werden erst die internen Werte multipliziert, dann wird die zu
hohe Skalierung ggf. durch Rechtsschieben korrigiert.
Bis dahin kann es eng werden.
Würde ich erst einen Operanden nach rechts schieben und dann
multiplizieren, würde das Problem nicht auftreten.
Dafür geht Genauigkeit verloren.
Möglich wäre es auch, die Werte anzuschauen und jeweils so
weit schieben, wie möglich, ohne Einsen zu verlieren,
dann multiplizieren und falls es vorher nicht reichte die
restlichen Stellen danach zu schieben.
Das wiederum ist aufwendiger, also auch nicht der Bringer.

Insofern denke ich, macht es Sinn, dem Anwender den schwarzen
Peter zu geben; nur der kennt seine Zahlen und muß halt im
Zweifelsfall Rechenzeit opfern.

von ManuelStahl (Gast)


Lesenswert?

Die _Fract Typen haben das Komma aber immer an der gleichen Stelle, 
oder? Damit ist die Template-Bibliothek dann wesentlich flexibler.

von Klaus W. (mfgkw)


Lesenswert?

... und unabhängig vom g++.

von ManuelStahl (Gast)


Lesenswert?

Hab grad nochmal über die fixpoint Klasse gelesen:

- in type &operator--() ist noch ein copy&paste-Fehler
- warum wird beim operator/ der shift verwendet, beim operator% aber 
nicht?

von ManuelStahl (Gast)


Lesenswert?

> -- Kein Schwanz wird sich dazu bereit erklären diese Arbeit
>    zu machen, zB für avr-gcc ;o)

Möglicherweise doch:
http://archives.free.net.ph/message/20090110.122139.00bf7bbb.de.html

von Peter D. (peda)


Lesenswert?

Klaus Wachtler schrieb:
> Bei (u)int64_t gibt es gar keinen nächstgrößeren.
> Aber wozu multipliziert man uint64_t mit uint_64_t?


(u)int64_t sollte man auf dem AVR unbedingt vermeiden!
Es ist zwar implementiert, aber von hinten durch die Brust ins Auge.
D.h. es erzeugt deutlich größeren Code und ist auch deutlich langsamer 
als float.
Soweit möglich, also besser float nehmen, statt (u)int64_t.

double ist auf dem AVR garnicht implementiert.
Laut C-Standard ist es erlaubt, daß double stillschweigend als float 
compiliert wird.
Man merkt es also nur, wenn man sizeof(double) ausgibt oder daß ein paar 
Stellen Genauigkeit fehlen.

Die optimierte float-lib "-lm" ist außerdem auf dem AVR garnicht so 
schlecht.
Wenn man nicht gerade mit jedem % CPU-Leistung geizen muß oder noch was 
unbedingt in nen ATtiny13 reinpassen soll, kann man ruhig float nehmen.
Insbesondere, wenn es um Berechnungen für die Werteausgabe an den 
langsamen Menschen geht.
Wenn im Mapfile 264 Byte zusätzlich belegter SRAM auftauchen, hat man 
die falsche Lib genommen.


Peter

von Klaus W. (mfgkw)


Lesenswert?

ManuelStahl schrieb:
> Hab grad nochmal über die fixpoint Klasse gelesen:

Danke!

>
> - in type &operator--() ist noch ein copy&paste-Fehler

war falsch, habe ich jetzt hier korrigiert.

> - warum wird beim operator/ der shift verwendet, beim operator% aber
> nicht?

Ich sehe jetzt nicht den Fehler und habe mal ein Beispiel probiert:
1
  Anyware::fixpoint< int32_t, int32_t, 2, true >  a( 25, 4 ), b( 10, 4 );
2
  Anyware::fixpoint< int32_t, int32_t, 2, true >  c;
3
4
  c = a/b;
5
  std::cout << "a=" << a << "\nb=" << b << "\na/b=" << c << std::endl << std::endl;
6
7
  c = a%b;
8
  std::cout << "a=" << a << "\nb=" << b << "\na%b=" << c << std::endl << std::endl;
9
10
  std::cout << "fmod(" << double(a) << "," << double(b) << ") meint: " << fmod( double(a), double(b) ) << std::endl;

Das ergibt (wenn mit -DTESTFIXPOINT kompiliert, um die volle
Ausgabe zu bekommen):
1
a=fixpoint( internal=25, scaling=2, rounding=1, resulting=6.25 )
2
b=fixpoint( internal=10, scaling=2, rounding=1, resulting=2.5 )
3
a/b=fixpoint( internal=10, scaling=2, rounding=1, resulting=2.5 )
4
5
a=fixpoint( internal=25, scaling=2, rounding=1, resulting=6.25 )
6
b=fixpoint( internal=10, scaling=2, rounding=1, resulting=2.5 )
7
a%b=fixpoint( internal=5, scaling=2, rounding=1, resulting=1.25 )
8
9
fmod(6.25,2.5) meint: 1.25
Wenn ich nicht gerade voll von der Rolle bin, sollte das
eigentlich stimmen; 6.25/2.5 ist doch 2.5 (was operator/ liefert)
und auf ganze Zahl abgerundet ist das 2; 2*2.5 gibt 5 und damit den
Rest 1.25 auf 6.25.

Bin jetzt aber etwas in Eile; ich sehe es mir heute oder morgen
nachmittag nochmal an und stelle eine korrigierte Version
(zumindest wg. operator--) irgendwohin.

Nochmal Danke!

mfgkw

von (prx) A. K. (prx)


Lesenswert?

Peter Dannegger schrieb:

> Laut C-Standard ist es erlaubt, daß double stillschweigend als float
> compiliert wird.

Wenn man es umgekehrt formuliert wird ein Schuh draus. Und zur Erfüllung 
des Standards fehlen bei 32bit doubles ein paar Stellen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:

> *Nachteil*
>  -- Kein Standard-C

Es scheint dem Entwurf für "Embedded C" zu entsprechen:

http://www.open-std.org/JTC1/SC22/wg14/www/docs/n1169.pdf

Schön, dass da schon jemand bei GCC was gemacht hat, hatte ich
gar nicht erwartet.

>  -- Kein Schwanz wird sich dazu bereit erklären diese Arbeit
>     zu machen, zB für avr-gcc ;o)

Hmm, vielleicht ja hoffentlich mal irgendwann...  Aber klar, wenn
ich die Reonanz auf den Aufruf sehe, mal 64-bit-FP zu implementieren,
dann wirst du wohl Recht haben. :-(

von Klaus W. (mfgkw)


Lesenswert?

so, den operator--() habe ich korrigiert.

Bei operator/() und operator%() sehe ich irgendwie keinen
Handlungsbedarf.
Wenn es nicht wie erwartet funktioniert: bitte Bescheid sagen.

Der jeweils aktuelle Stand liegt jetzt und bis auf weiteres immer
unter http://mfgkw.dyndns.org/fixpoint_release.zip

Beim Entpacken entsteht ein Verzeichnis, in dessen Name
Datum und Uhrzeit des Stands steht.

von Klaus W. (mfgkw)


Lesenswert?

Thread verlagert; siehe in Zukunft 
Beitrag "Festkommazahlen mit C++"

(hier kann meinetwegen geschlossen werden)

Dieser Beitrag ist gesperrt und kann nicht beantwortet werden.