1/*
2 * Copyright (C) 2010 Robin Sonefors
3 * Copyright (C) 2008-2012 Robert Ancell.
4 *
5 * This program is free software: you can redistribute it and/or modify it under
6 * the terms of the GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option) any later
8 * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
9 * license.
10 */
11
12public enum DisplayFormat
13{
14    AUTOMATIC,
15    FIXED,
16    SCIENTIFIC,
17    ENGINEERING
18}
19
20public class Serializer : Object
21{
22    private int leading_digits;      /* Number of digits to show before radix */
23    private int trailing_digits;     /* Number of digits to show after radix */
24    private DisplayFormat format;    /* Number display mode. */
25    private bool show_tsep;          /* Set if the thousands separator should be shown. */
26    private bool show_zeroes;        /* Set if trailing zeroes should be shown. */
27
28    private int number_base;         /* Numeric base */
29    private uint representation_base;/* Representation base. */
30
31    private unichar radix;           /* Locale specific radix string. */
32    private unichar tsep;            /* Locale specific thousands separator. */
33    private int tsep_count;          /* Number of digits between separator. */
34
35    /* is set when an error (for example precision error while converting) occurs */
36    public string? error { get; set; default = null; }
37
38    public Serializer (DisplayFormat format, int number_base, int trailing_digits)
39    {
40        var radix_string = Posix.nl_langinfo (Posix.NLItem.RADIXCHAR);
41        if (radix_string != null && radix_string != "") {
42            var radix_utf8 = radix_string.locale_to_utf8 (-1, null, null);
43            if (radix_utf8 != null)
44                radix = radix_utf8.get_char (0);
45            else
46                radix = '.';
47        }
48        else
49            radix = '.';
50        var tsep_string = Posix.nl_langinfo (Posix.NLItem.THOUSEP);
51        if (tsep_string != null && tsep_string != "") {
52            var tsep_utf8 = tsep_string.locale_to_utf8 (-1, null, null);
53            if (tsep_utf8 != null)
54                tsep = tsep_utf8.get_char (0);
55            else
56                tsep = ' ';
57        }
58        else
59            tsep = ' ';
60        tsep_count = 3;
61
62        this.number_base = number_base;
63        this.representation_base = number_base;
64        leading_digits = 12;
65        this.trailing_digits = trailing_digits;
66        show_zeroes = false;
67        show_tsep = false;
68        this.format = format;
69    }
70
71    public string to_string (Number x)
72    {
73        /* For base conversion equation, use FIXED format. */
74        if (representation_base != number_base)
75        {
76            int n_digits = 0;
77            return cast_to_string (x, ref n_digits);
78        }
79        switch (format)
80        {
81        default:
82        case DisplayFormat.AUTOMATIC:
83            int n_digits = 0;
84            var s0 = cast_to_string (x, ref n_digits);
85            /* Decide leading digits based on number_base. Support 64 bits in programming mode. */
86            switch (get_base ())
87            {
88                /* 64 digits for binary mode. */
89                case 2:
90                    if (n_digits <= 64)
91                        return s0;
92                    else
93                        return cast_to_exponential_string (x, false, ref n_digits);
94                /* 22 digis for octal mode. */
95                case 8:
96                    if (n_digits <= 22)
97                        return s0;
98                    else
99                        return cast_to_exponential_string (x, false, ref n_digits);
100                /* 16 digits for hexadecimal mode. */
101                case 16:
102                    if(n_digits <= 16)
103                        return s0;
104                    else
105                        return cast_to_exponential_string (x, false, ref n_digits);
106                /* Use default leading_digits for base 10 numbers. */
107                case 10:
108                default:
109                    if (n_digits <= leading_digits)
110                        return s0;
111                    else
112                        return cast_to_exponential_string (x, false, ref n_digits);
113            }
114        case DisplayFormat.FIXED:
115            int n_digits = 0;
116            return cast_to_string (x, ref n_digits);
117        case DisplayFormat.SCIENTIFIC:
118            if (representation_base == 10)
119            {
120                int n_digits = 0;
121                return cast_to_exponential_string (x, false, ref n_digits);
122            }
123            else
124            {
125                int n_digits = 0;
126                return cast_to_string (x, ref n_digits);
127            }
128        case DisplayFormat.ENGINEERING:
129            if (representation_base == 10)
130            {
131                int n_digits = 0;
132                return cast_to_exponential_string (x, true, ref n_digits);
133            }
134            else
135            {
136                int n_digits = 0;
137                return cast_to_string (x, ref n_digits);
138            }
139        }
140    }
141
142    public Number? from_string (string str)
143    {
144        // FIXME: Move mp_set_from_string into here
145        return mp_set_from_string (str, number_base);
146    }
147
148    public void set_base (int number_base)
149    {
150        this.number_base = number_base;
151    }
152
153    public int get_base ()
154    {
155        return number_base;
156    }
157
158    public void set_representation_base (uint representation_base)
159    {
160        this.representation_base = representation_base;
161    }
162
163    public uint get_representation_base ()
164    {
165        return representation_base;
166    }
167
168    public void set_radix (unichar radix)
169    {
170        this.radix = radix;
171    }
172
173    public unichar get_radix ()
174    {
175        return radix;
176    }
177
178    public void set_thousands_separator (unichar separator)
179    {
180        tsep = separator;
181    }
182
183    public unichar get_thousands_separator ()
184    {
185        return tsep;
186    }
187
188    public int get_thousands_separator_count ()
189    {
190        return tsep_count;
191    }
192
193    public void set_thousands_separator_count (int count)
194    {
195        tsep_count = count;
196    }
197
198    public void set_show_thousands_separators (bool visible)
199    {
200        show_tsep = visible;
201    }
202
203    public bool get_show_thousands_separators ()
204    {
205        return show_tsep;
206    }
207
208    public void set_show_trailing_zeroes (bool visible)
209    {
210        show_zeroes = visible;
211    }
212
213    public bool get_show_trailing_zeroes ()
214    {
215        return show_zeroes;
216    }
217
218    public int get_leading_digits ()
219    {
220        return leading_digits;
221    }
222
223    public void set_leading_digits (int leading_digits)
224    {
225        this.leading_digits = leading_digits;
226    }
227
228    public int get_trailing_digits ()
229    {
230        return trailing_digits;
231    }
232
233    public void set_trailing_digits (int trailing_digits)
234    {
235        this.trailing_digits = trailing_digits;
236    }
237
238    public DisplayFormat get_number_format ()
239    {
240        return format;
241    }
242
243    public void set_number_format (DisplayFormat format)
244    {
245        this.format = format;
246    }
247
248    private string cast_to_string (Number x, ref int n_digits)
249    {
250        var string = new StringBuilder.sized (1024);
251
252        var x_real = x.real_component ();
253        cast_to_string_real (x_real, (int) representation_base, false, ref n_digits, string);
254        if (x.is_complex ())
255        {
256            var x_im = x.imaginary_component ();
257
258            var force_sign = true;
259            if (string.str == "0")
260            {
261                string.assign ("");
262                force_sign = false;
263            }
264
265            var s = new StringBuilder.sized (1024);
266            int n_complex_digits = 0;
267            cast_to_string_real (x_im, (int) representation_base, force_sign, ref n_complex_digits, s);
268            if (n_complex_digits > n_digits)
269                n_digits = n_complex_digits;
270            if (s.str == "0" || s.str == "+0" || s.str == "−0")
271            {
272                if (string.str == "")
273                {
274                    string.append ("0"); // real component is empty, the imaginary very small, we shouldn't return blank
275                }
276            }
277            else if (s.str == "1")
278            {
279                string.append ("i");
280            }
281            else if (s.str == "+1")
282            {
283                string.append ("+i");
284            }
285            else if (s.str == "−1")
286            {
287                string.append ("−i");
288            }
289            else
290            {
291                if (s.str == "+0")
292                    string.append ("+");
293                else if (s.str != "0")
294                    string.append (s.str);
295
296                string.append ("i");
297            }
298        }
299
300        return string.str;
301    }
302
303    private void cast_to_string_real (Number x, int number_base, bool force_sign, ref int n_digits, StringBuilder string)
304    {
305        const char digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
306
307        var number = x;
308        if (number.is_negative ())
309            number = number.abs ();
310
311        /* Add rounding factor */
312        var temp = new Number.integer (number_base);
313        temp = temp.xpowy_integer (-(trailing_digits+1));
314        temp = temp.multiply_integer (number_base);
315        temp = temp.divide_integer (2);
316        var rounded_number = number.add (temp);
317
318        /* Write out the integer component least significant digit to most */
319        temp = rounded_number.floor ();
320        var i = 0;
321        do
322        {
323            if (number_base == 10 && show_tsep && i == tsep_count)
324            {
325                string.prepend_unichar (tsep);
326                i = 0;
327            }
328            i++;
329
330            var t = temp.divide_integer (number_base);
331            t = t.floor ();
332            var t2 = t.multiply_integer (number_base);
333
334            var t3 = temp.subtract (t2);
335
336            var d = t3.to_integer ();
337
338            if (d < 16 && d >= 0)
339            {
340                string.prepend_c (digits[d]);
341            }
342            else
343            {
344                string.prepend_c ('?');
345                error = _("Overflow: the result couldn’t be calculated");
346                string.assign ("0");
347                break;
348            }
349            n_digits++;
350
351            temp = t;
352        } while (!temp.is_zero ());
353
354        var last_non_zero = string.len;
355
356        string.append_unichar (radix);
357
358        /* Write out the fractional component */
359        temp = rounded_number.fractional_component ();
360        for (i = 0; i < trailing_digits; i++)
361        {
362            if (temp.is_zero ())
363                break;
364
365            temp = temp.multiply_integer (number_base);
366            var digit = temp.floor ();
367            var d = digit.to_integer ();
368
369            string.append_c (digits[d]);
370
371            if (d != 0)
372                last_non_zero = string.len;
373            temp = temp.subtract (digit);
374        }
375
376        /* Strip trailing zeroes */
377        if (!show_zeroes || trailing_digits == 0)
378            string.truncate (last_non_zero);
379
380        /* Add sign on non-zero values */
381        if (string.str != "0" || force_sign)
382        {
383            if (x.is_negative ())
384                string.prepend ("−");
385            else if (force_sign)
386                string.prepend ("+");
387        }
388
389        /* Append base suffix if not in default base */
390        if (number_base != this.number_base)
391        {
392            const string sub_digits[] = {"₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉"};
393            int multiplier = 1;
394            int b = number_base;
395
396            while (number_base / multiplier != 0)
397                multiplier *= 10;
398            while (multiplier != 1)
399            {
400                int d;
401                multiplier /= 10;
402                d = b / multiplier;
403                string.append (sub_digits[d]);
404                b -= d * multiplier;
405            }
406        }
407    }
408
409    private int cast_to_exponential_string_real (Number x, StringBuilder string, bool eng_format, ref int n_digits)
410    {
411        if (x.is_negative ())
412            string.append ("−");
413
414        var mantissa = x.abs ();
415
416        var base_ = new Number.integer (number_base);
417        var base3 = base_.xpowy_integer (3);
418        var base10 = base_.xpowy_integer (10);
419        var t = new Number.integer (1);
420        var base10inv = t.divide (base10);
421
422        var exponent = 0;
423        if (!mantissa.is_zero ())
424        {
425            while (!eng_format && mantissa.compare (base10) >= 0)
426            {
427                exponent += 10;
428                mantissa = mantissa.multiply (base10inv);
429            }
430
431            while ((!eng_format && mantissa.compare (base_) >= 0) ||
432                    (eng_format && (mantissa.compare (base3) >= 0 || exponent % 3 != 0)))
433            {
434                exponent += 1;
435                mantissa = mantissa.divide (base_);
436            }
437
438            while (!eng_format && mantissa.compare (base10inv) < 0)
439            {
440                exponent -= 10;
441                mantissa = mantissa.multiply (base10);
442            }
443
444            t = new Number.integer (1);
445            while (mantissa.compare (t) < 0 || (eng_format && exponent % 3 != 0))
446            {
447                exponent -= 1;
448                mantissa = mantissa.multiply (base_);
449            }
450        }
451
452        string.append (cast_to_string (mantissa, ref n_digits));
453
454        return exponent;
455    }
456
457    private string cast_to_exponential_string (Number x, bool eng_format, ref int n_digits)
458    {
459        var string = new StringBuilder.sized (1024);
460
461        var x_real = x.real_component ();
462        var exponent = cast_to_exponential_string_real (x_real, string, eng_format, ref n_digits);
463        append_exponent (string, exponent);
464
465        if (x.is_complex ())
466        {
467            var x_im = x.imaginary_component ();
468
469            if (string.str == "0")
470                string.assign ("");
471
472            var s = new StringBuilder.sized (1024);
473            int n_complex_digits = 0;
474            exponent = cast_to_exponential_string_real (x_im, s, eng_format, ref n_complex_digits);
475            if (n_complex_digits > n_digits)
476                n_digits = n_complex_digits;
477            if (s.str == "0" || s.str == "+0" || s.str == "−0")
478            {
479                /* Ignore */
480            }
481            else if (s.str == "1")
482            {
483                string.append ("i");
484            }
485            else if (s.str == "+1")
486            {
487                string.append ("+i");
488            }
489            else if (s.str == "−1")
490            {
491                string.append ("−i");
492            }
493            else
494            {
495                if (s.str == "+0")
496                    string.append ("+");
497                else if (s.str != "0")
498                    string.append (s.str);
499
500                string.append ("i");
501            }
502            append_exponent (string, exponent);
503        }
504
505        return string.str;
506    }
507
508    private void append_exponent (StringBuilder string, int exponent)
509    {
510        const unichar super_digits[] = {'⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'};
511
512        if (exponent == 0)
513            return;
514
515        string.append ("×10"); // FIXME: Use the current base
516        if (exponent < 0)
517        {
518            exponent = -exponent;
519            string.append_unichar ('⁻');
520        }
521
522        var super_value = "%d".printf (exponent);
523        for (var i = 0; i < super_value.length; i++)
524            string.append_unichar (super_digits[super_value[i] - '0']);
525    }
526}
527