]> www.ginac.de Git - cln.git/blob - src/real/format-output/cl_fmt_floatstring.cc
dc20a6e9b7156768352252315588219684375562
[cln.git] / src / real / format-output / cl_fmt_floatstring.cc
1 // format_float_to_string().
2
3 // General includes.
4 #include "cl_sysdep.h"
5
6 CL_PROVIDE(cl_fmt_floatstring)
7
8 // Specification.
9 #include "cl_format.h"
10
11
12 // Implementation.
13
14 // BUGS:
15 // - This is slow.
16
17 #include "cl_output.h"
18 #include "cl_malloc.h"
19 #include "cl_float.h"
20 #include "cl_integer.h"
21 #include "cl_I.h"
22 #include "cl_spushstring.h"
23
24 // format_float_to_string(arg,width,d,k,dmin)
25 // ergibt einen String zum Floating-point arg:
26 // er hat den Wert von abs(arg)*expt(10,k), dabei mind. d Nachkommastellen
27 // und höchstens die Länge width (width<=0 -> keine Einschränkung).
28 // Trotzdem wird nicht auf weniger als dmin Stellen gerundet.
29
30 const digits_with_dot format_float_to_string (const cl_F& arg, const sintL width, const sintL d, const sintL k, const sintL dmin)
31 {
32         // One pre-allocated buffer. This reduces the allocation/free cost.
33         static cl_spushstring digitstring;
34
35         if (zerop(arg)) {
36                 var sintL places = (d < dmin ? dmin : d);
37                 if (width > 0)
38                         // width angegeben -> places := min(places,width-1)
39                         if (places >= width)
40                                 places = width-1;
41                 // ein Punkt und places Nullen
42                 var char* string = (char *) cl_malloc_hook(1+places+1);
43                 string[0] = '.';
44                 for (sintL i = 1; i <= places; i++) string[i] = '0';
45                 string[1+places] = '\0';
46                 return digits_with_dot(string, 1+places,
47                                 cl_true, (cl_boolean)(places==0), 0
48                         );
49         }
50         // significand : Integer >0
51         // expon : Integer
52         // mantprec : Anzahl der echten Mantissenbits von significand
53         // (also 2^mantprec <= significand < 2^(mantprec+1))
54         // width : Anzahl Stellen, die die Zahl (inklusive Punkt) nicht
55         //         überschreiten soll, oder 0
56         // d : Mindestanzahl Nachkommastellen oder 0
57         // k : Skalierungsfaktor (siehe CLTL S.394)
58         // dmin : Mindestanzahl von Dezimaltellen, die (trotz Angabe von width
59         //        oder d) nicht gerundet werden dürfen.
60         //        (Nur interessant, falls d <= dmin <= (precision der Zahl).)
61         // wandelt die Zahl significand*2^expon um in einen Dezimalstring um.
62         // Es ist kein Exponent dabei.
63         var cl_idecoded_float decoded = integer_decode_float(arg);
64         var const cl_I& significand = decoded.mantissa;
65         var const cl_I& expon = decoded.exponent;
66         var uintL mantprec = float_digits(arg)-1;
67         var cl_I numerator = significand;
68         var cl_I denominator = 1;
69         var cl_I abrund_einh = 1; // Abrundungseinheit:
70                // Abrunden um 1 in der letzten abrundbaren Stelle entspricht
71                // einer Erniedrigung von numerator um abrund_einh.
72         var cl_I aufrund_einh = 1; // Aufrundungseinheit:
73                // Aufrunden um 1 in der letzten aufrundbaren Stelle entspricht
74                // einer Erhöhung von numerator um aufrund_einh.
75         digitstring.reset();
76         if (expon > 0) {
77                 numerator = numerator << expon;
78                 aufrund_einh = abrund_einh = 1 << expon;
79         }
80         elif (expon < 0) {
81                 denominator = denominator << -expon;
82                 // aufrund_einh = abrund_einh = 1;
83         }
84         // Zahl = numerator/denominator
85         if (significand == ash(1,mantprec)) {
86                 // Ist der Significand=2^mantprec, so ist abrund-einh zu halbieren.
87                 // Man kann stattdessen auch alle 3 anderen Grössen verdoppeln:
88                 aufrund_einh = aufrund_einh << 1;
89                 numerator = numerator << 1;
90                 denominator = denominator << 1;
91         }
92         // Defaultmäßig: Auf-/Abrunde-Einheit = eine Einheit in der letzten
93         // BINÄRstelle.
94         // Zahl = numerator/denominator
95         // Skalierungsfaktor k in die Zahl mit einbeziehen (vgl. CLTL S.394)
96         // k<0 -> Mantisse durch 10^|k| dividieren
97         // k>0 -> Mantisse mit 10^k multiplizieren
98         // Dabei aufrund-einh, abrund-einh im Verhältnis zu numerator beibehalten.
99         if (k != 0) {
100                 if (k < 0) {
101                         var cl_I skal_faktor = expt_pos(10,-k);
102                         denominator = denominator * skal_faktor;
103                 }
104                 elif (k > 0) {
105                         var cl_I skal_faktor = expt_pos(10,k);
106                         numerator = numerator * skal_faktor;
107                         aufrund_einh = aufrund_einh * skal_faktor;
108                         abrund_einh = abrund_einh * skal_faktor;
109                 }
110         }
111         // Stellen: 0 = 1. Stelle vor dem Punkt, -1 = 1. Stelle nach dem Punkt.
112         var sintL stelle = 0; // Stelle der als nächstes auszugebenden Ziffer
113         // auf >= 1/10 adjustieren:
114         // (jeweils numerator mit 10 multiplizieren, eine führende 0 mehr vorsehen)
115         until (10*numerator >= denominator) {
116                 stelle = stelle-1;
117                 numerator = numerator * 10;
118                 aufrund_einh = aufrund_einh * 10;
119                 abrund_einh = abrund_einh * 10;
120         }
121         // stelle = Stelle der letzten führenden 0
122         //        = 1 + Stelle der 1. signifikanten Ziffer
123         //        oder =0, falls k>=0
124         // Ausführung der Rundung:
125         var cl_boolean letzte_stelle_p = cl_false; // d oder width angegeben?
126         var sintL letzte_stelle = 0; // falls d oder width angegeben waren:
127                                      // Stelle der letzten signifikanten Ziffer
128         var cl_boolean halbzahlig = cl_false; // zeigt an, ob hinten genau ein 0.500000 wegfällt
129         do {
130                 // Solange das Ergebnis auch nach Aufrundung >= 1 bliebe,
131                 // eine Vorkommastelle mehr einplanen:
132                 until (((numerator << 1) + aufrund_einh) < (denominator << 1)) {
133                         denominator = denominator * 10;
134                         stelle = stelle+1;
135                 }
136                 // Falls d oder width angegeben:
137                 // letzte_stelle ausrechnen
138                 if (d != 0) {
139                         // Falls dmin angegeben: min(-d,-dmin) = -max(d,dmin).
140                         // Sonst -d.
141                         letzte_stelle = -d;
142                         if (dmin > 0)
143                                 if (letzte_stelle > -dmin)
144                                         letzte_stelle = -dmin;
145                         letzte_stelle_p = cl_true;
146                 }
147                 elif (width > 0) {
148                         // Falls nicht d, nur width angegeben:
149                         if (stelle < 0)
150                                 // Es kommen führende Nullen nach dem Punkt -> d:=width-1
151                                 letzte_stelle = 1-width;
152                         else
153                                 // Es kommen keine führenden Nullen nach dem Punkt ->
154                                 // Es wird stelle Vorkommaziffern geben, d:=width-1-stelle
155                                 letzte_stelle = 1+stelle-width;
156                         // also letzte_stelle = -(width-1 - max(stelle,0))
157                         // wieder dmin berücksichtigen:
158                         if (dmin > 0)
159                                 if (letzte_stelle > -dmin)
160                                         letzte_stelle = -dmin;
161                         letzte_stelle_p = cl_true;
162                 }
163                 if (letzte_stelle_p) {
164                         var sintL ziffernzahl = letzte_stelle - stelle;
165                         // ziffernzahl = - Zahl signifikanter Stellen oder >=0.
166                         var cl_I dezimal_einh = denominator;
167                         // dezimal-einh := ceiling(dezimal_einh*expt(10,ziffernzahl))
168                         if (ziffernzahl > 0)
169                                 dezimal_einh = dezimal_einh*expt_pos(10,ziffernzahl);
170                         elif (ziffernzahl < 0)
171                                 dezimal_einh = ceiling1(dezimal_einh,expt_pos(10,-ziffernzahl));
172                         // dezimal-einh = Um wieviel numerator erhöht bzw. erniedigt werden
173                         // müßte, damit sich die Dezimaldarstellung um genau 1 an der
174                         // Position letzte_stelle verändert.
175                         if (abrund_einh < dezimal_einh)
176                                 abrund_einh = dezimal_einh;
177                         if (aufrund_einh < dezimal_einh)
178                                 aufrund_einh = dezimal_einh;
179                         // Jetzt darf auch um eine (halbe) DEZIMAL-Einheit gerundet werden.
180                         if (aufrund_einh == dezimal_einh)
181                                 halbzahlig = cl_true;
182                 }
183         } until (((numerator << 1) + aufrund_einh) < (denominator << 1));
184         // stelle = Position der ersten signifikanten Stelle + 1
185         var uintL digit_count = 0; // Zahl der bisher in digit-string
186                // ausgegebenen Ziffern (exklusive den Punkt)
187         var uintL point_pos = 0; // Punkt-Position = Zahl führender Stellen
188                                  // = Zahl der Ziffern vor dem Punkt
189         // Führenden Punkt und nachfolgende Nullen ausgeben:
190         if (stelle < 0) {
191                 digitstring.push('.');
192                 point_pos = digit_count;
193                 for (int i = -stelle; i >= 0; i--) {
194                         digitstring.push('0');
195                         digit_count++;
196                 }
197         }
198         // Ziffern der Mantisse ausgeben:
199         var uintL digit; // die laufende Ziffer, >=0, <10
200         var bool abrunden; // letzte Ziffer abzurunden?
201         var bool aufrunden; // letzte Ziffer aufzurunden?
202         for (;;) {
203                 if (stelle == 0) {
204                         digitstring.push('.');
205                         point_pos = digit_count;
206                 }
207                 stelle = stelle-1;
208                 var cl_I_div_t div = cl_divide(numerator*10,denominator);
209                 digit = cl_I_to_UL(div.quotient);
210                 numerator = div.remainder;
211                 abrund_einh = abrund_einh*10;
212                 aufrund_einh = aufrund_einh*10;
213                 abrunden = ((numerator<<1) < abrund_einh);
214                 aufrunden = (halbzahlig
215                              ? (numerator<<1) >= (denominator<<1) - aufrund_einh
216                              : (numerator<<1) > (denominator<<1) - aufrund_einh
217                             );
218                 if (abrunden || aufrunden
219                     || (letzte_stelle_p && (stelle <= letzte_stelle))
220                    )
221                    break;
222                 digitstring.push("0123456789"[digit]);
223                 digit_count++;
224         }
225         // letzte signifikante Ziffer ausgeben:
226         if (letzte_stelle_p ? (cl_boolean)(stelle >= letzte_stelle) : cl_true) {
227                 digit = (abrunden && !aufrunden ? digit :
228                          aufrunden && !abrunden ? digit+1 :
229                          (numerator<<1) <= denominator ? digit : digit+1);
230                 digitstring.push("0123456789"[digit]);
231                 digit_count++;
232         }
233         // Nachfolgende Nullen und Punkt ausgeben
234         if (stelle >= 0) {
235                 for (int i = stelle; i >= 0; i--) {
236                         digitstring.push('0');
237                         digit_count++;
238                 }
239                 digitstring.push('.');
240                 point_pos = digit_count;
241         }
242         if (d != 0)
243                 for (int i = d - (digit_count - point_pos); i >= 0; i--) {
244                         digitstring.push('0');
245                         digit_count++;
246                 }
247         return digits_with_dot(digitstring.contents(), digit_count+1,
248                         (cl_boolean)(point_pos==0),
249                         (cl_boolean)(point_pos==digit_count),
250                         point_pos
251                 );
252 }
253
254 CL_PROVIDE_END(cl_fmt_floatstring)