diff options
Diffstat (limited to 'src/mp-serializer.c')
-rw-r--r-- | src/mp-serializer.c | 617 |
1 files changed, 617 insertions, 0 deletions
diff --git a/src/mp-serializer.c b/src/mp-serializer.c new file mode 100644 index 0000000..356fc55 --- /dev/null +++ b/src/mp-serializer.c @@ -0,0 +1,617 @@ +/* + * Copyright (C) 2010 Robin Sonefors + * Copyright (C) 2008-2011 Robert Ancell. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#include <langinfo.h> +#include <glib.h> +#include <glib-object.h> +#include <string.h> +#include <stdio.h> + +#include "mp-serializer.h" +#include "mp-enums.h" + +enum { + PROP_0, + PROP_SHOW_THOUSANDS_SEPARATORS, + PROP_SHOW_TRAILING_ZEROES, + PROP_NUMBER_FORMAT, + PROP_BASE, +}; + +struct MpSerializerPrivate +{ + gint leading_digits; /* Number of digits to show before radix */ + gint trailing_digits; /* Number of digits to show after radix */ + MpDisplayFormat format; /* Number display mode. */ + gint show_tsep; /* Set if the thousands separator should be shown. */ + gint show_zeroes; /* Set if trailing zeroes should be shown. */ + + gint base; /* Numeric base */ + + gunichar tsep; /* Locale specific thousands separator. */ + gunichar radix; /* Locale specific radix string. */ + gint tsep_count; /* Number of digits between separator. */ +}; + + +G_DEFINE_TYPE(MpSerializer, mp_serializer, G_TYPE_OBJECT); + +MpSerializer * +mp_serializer_new(MpDisplayFormat format, int base, int trailing_digits) +{ + MpSerializer *serializer = g_object_new(mp_serializer_get_type(), /*"number-format", format,*/ NULL); + mp_serializer_set_number_format(serializer, format); + mp_serializer_set_base(serializer, base); + mp_serializer_set_trailing_digits(serializer, trailing_digits); + return serializer; +} + + +static void +mp_cast_to_string_real(MpSerializer *serializer, const MPNumber *x, int base, gboolean force_sign, int *n_digits, GString *string) +{ + static gchar digits[] = "0123456789ABCDEF"; + MPNumber number, integer_component, fractional_component, temp; + int i, last_non_zero; + + if (mp_is_negative(x)) + mp_abs(x, &number); + else + mp_set_from_mp(x, &number); + + /* Add rounding factor */ + mp_set_from_integer(base, &temp); + mp_xpowy_integer(&temp, -(serializer->priv->trailing_digits+1), &temp); + mp_multiply_integer(&temp, base, &temp); + mp_divide_integer(&temp, 2, &temp); + mp_add(&number, &temp, &temp); + + /* If trying to add rounding factor causes overflow, don't add it */ + if (!mp_get_error()) + mp_set_from_mp(&temp, &number); + + /* Split into integer and fractional component */ + mp_floor(&number, &integer_component); + mp_fractional_component(&number, &fractional_component); + + /* Write out the integer component least significant digit to most */ + mp_set_from_mp(&integer_component, &temp); + i = 0; + do { + MPNumber t, t2, t3; + int64_t d; + + if (serializer->priv->base == 10 && serializer->priv->show_tsep && i == serializer->priv->tsep_count) { + g_string_prepend_unichar(string, serializer->priv->tsep); + i = 0; + } + i++; + + mp_divide_integer(&temp, base, &t); + mp_floor(&t, &t); + mp_multiply_integer(&t, base, &t2); + + mp_subtract(&temp, &t2, &t3); + + d = mp_cast_to_int(&t3); + g_string_prepend_c(string, d < 16 ? digits[d] : '?'); + (*n_digits)++; + + mp_set_from_mp(&t, &temp); + } while (!mp_is_zero(&temp)); + + last_non_zero = string->len; + + g_string_append_unichar(string, serializer->priv->radix); + + /* Write out the fractional component */ + mp_set_from_mp(&fractional_component, &temp); + for (i = serializer->priv->trailing_digits; i > 0 && !mp_is_zero(&temp); i--) { + int d; + MPNumber digit; + + mp_multiply_integer(&temp, base, &temp); + mp_floor(&temp, &digit); + d = mp_cast_to_int(&digit); + + g_string_append_c(string, digits[d]); + + if(d != 0) + last_non_zero = string->len; + mp_subtract(&temp, &digit, &temp); + } + + /* Strip trailing zeroes */ + if (!serializer->priv->show_zeroes || serializer->priv->trailing_digits == 0) + g_string_truncate(string, last_non_zero); + + /* Add sign on non-zero values */ + if (strcmp(string->str, "0") != 0 || force_sign) { + if (mp_is_negative(x)) + g_string_prepend(string, "−"); + else if (force_sign) + g_string_prepend(string, "+"); + } + + /* Append base suffix if not in default base */ + if (base != serializer->priv->base) { + const gchar *digits[] = {"₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉"}; + int multiplier = 1; + int b = base; + + while (base / multiplier != 0) + multiplier *= 10; + while (multiplier != 1) { + int d; + multiplier /= 10; + d = b / multiplier; + g_string_append(string, digits[d]); + b -= d * multiplier; + } + } +} + + +static gchar * +mp_cast_to_string(MpSerializer *serializer, const MPNumber *x, int *n_digits) +{ + GString *string; + MPNumber x_real; + gchar *result; + + string = g_string_sized_new(1024); + + mp_real_component(x, &x_real); + mp_cast_to_string_real(serializer, &x_real, serializer->priv->base, FALSE, n_digits, string); + if (mp_is_complex(x)) { + GString *s; + gboolean force_sign = TRUE; + MPNumber x_im; + int n_complex_digits; + + mp_imaginary_component(x, &x_im); + + if (strcmp(string->str, "0") == 0) { + g_string_assign(string, ""); + force_sign = FALSE; + } + + s = g_string_sized_new(1024); + mp_cast_to_string_real(serializer, &x_im, 10, force_sign, &n_complex_digits, s); + if (n_complex_digits > *n_digits) + *n_digits = n_complex_digits; + if (strcmp(s->str, "0") == 0 || strcmp(s->str, "+0") == 0 || strcmp(s->str, "−0") == 0) { + /* Ignore */ + } + else if (strcmp(s->str, "1") == 0) { + g_string_append(string, "i"); + } + else if (strcmp(s->str, "+1") == 0) { + g_string_append(string, "+i"); + } + else if (strcmp(s->str, "−1") == 0) { + g_string_append(string, "−i"); + } + else { + if (strcmp(s->str, "+0") == 0) + g_string_append(string, "+"); + else if (strcmp(s->str, "0") != 0) + g_string_append(string, s->str); + + g_string_append(string, "i"); + } + g_string_free(s, TRUE); + } + + result = g_strndup(string->str, string->len + 1); + g_string_free(string, TRUE); + + return result; +} + + +static int +mp_cast_to_exponential_string_real(MpSerializer *serializer, const MPNumber *x, GString *string, gboolean eng_format, int *n_digits) +{ + gchar *fixed; + MPNumber t, z, base, base3, base10, base10inv, mantissa; + int exponent = 0; + + mp_abs(x, &z); + if (mp_is_negative(x)) + g_string_append(string, "−"); + mp_set_from_mp(&z, &mantissa); + + mp_set_from_integer(serializer->priv->base, &base); + mp_xpowy_integer(&base, 3, &base3); + mp_xpowy_integer(&base, 10, &base10); + mp_set_from_integer(1, &t); + mp_divide(&t, &base10, &base10inv); + + if (!mp_is_zero(&mantissa)) { + while (!eng_format && mp_is_greater_equal(&mantissa, &base10)) { + exponent += 10; + mp_multiply(&mantissa, &base10inv, &mantissa); + } + + while ((!eng_format && mp_is_greater_equal(&mantissa, &base)) || + (eng_format && (mp_is_greater_equal(&mantissa, &base3) || exponent % 3 != 0))) { + exponent += 1; + mp_divide(&mantissa, &base, &mantissa); + } + + while (!eng_format && mp_is_less_than(&mantissa, &base10inv)) { + exponent -= 10; + mp_multiply(&mantissa, &base10, &mantissa); + } + + mp_set_from_integer(1, &t); + while (mp_is_less_than(&mantissa, &t) || (eng_format && exponent % 3 != 0)) { + exponent -= 1; + mp_multiply(&mantissa, &base, &mantissa); + } + } + + fixed = mp_cast_to_string(serializer, &mantissa, n_digits); + g_string_append(string, fixed); + g_free(fixed); + + return exponent; +} + + +static void +append_exponent(GString *string, int exponent) +{ + const gchar *super_digits[] = {"⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹"}; + gchar *super_value, *c; + + if (exponent == 0) + return; + + g_string_append_printf(string, "×10"); // FIXME: Use the current base + if (exponent < 0) { + exponent = -exponent; + g_string_append(string, "⁻"); + } + + super_value = g_strdup_printf("%d", exponent); + for (c = super_value; *c; c++) + g_string_append(string, super_digits[*c - '0']); + g_free (super_value); +} + + +static gchar * +mp_cast_to_exponential_string(MpSerializer *serializer, const MPNumber *x, gboolean eng_format, int *n_digits) +{ + GString *string; + MPNumber x_real; + gchar *result; + int exponent; + + string = g_string_sized_new(1024); + + mp_real_component(x, &x_real); + exponent = mp_cast_to_exponential_string_real(serializer, &x_real, string, eng_format, n_digits); + append_exponent(string, exponent); + + if (mp_is_complex(x)) { + GString *s; + MPNumber x_im; + int n_complex_digits = 0; + + mp_imaginary_component(x, &x_im); + + if (strcmp(string->str, "0") == 0) + g_string_assign(string, ""); + + s = g_string_sized_new(1024); + exponent = mp_cast_to_exponential_string_real(serializer, &x_im, s, eng_format, &n_complex_digits); + if (n_complex_digits > *n_digits) + *n_digits = n_complex_digits; + if (strcmp(s->str, "0") == 0 || strcmp(s->str, "+0") == 0 || strcmp(s->str, "−0") == 0) { + /* Ignore */ + } + else if (strcmp(s->str, "1") == 0) { + g_string_append(string, "i"); + } + else if (strcmp(s->str, "+1") == 0) { + g_string_append(string, "+i"); + } + else if (strcmp(s->str, "−1") == 0) { + g_string_append(string, "−i"); + } + else { + if (strcmp(s->str, "+0") == 0) + g_string_append(string, "+"); + else if (strcmp(s->str, "0") != 0) + g_string_append(string, s->str); + + g_string_append(string, "i"); + } + g_string_free(s, TRUE); + append_exponent(string, exponent); + } + + result = g_strndup(string->str, string->len + 1); + g_string_free(string, TRUE); + + return result; +} + + +gchar * +mp_serializer_to_string(MpSerializer *serializer, const MPNumber *x) +{ + gchar *s0; + int n_digits = 0; + + switch(serializer->priv->format) { + default: + case MP_DISPLAY_FORMAT_AUTOMATIC: + s0 = mp_cast_to_string(serializer, x, &n_digits); + if (n_digits <= serializer->priv->leading_digits) + return s0; + else { + g_free (s0); + return mp_cast_to_exponential_string(serializer, x, FALSE, &n_digits); + } + break; + case MP_DISPLAY_FORMAT_FIXED: + return mp_cast_to_string(serializer, x, &n_digits); + case MP_DISPLAY_FORMAT_SCIENTIFIC: + return mp_cast_to_exponential_string(serializer, x, FALSE, &n_digits); + case MP_DISPLAY_FORMAT_ENGINEERING: + return mp_cast_to_exponential_string(serializer, x, TRUE, &n_digits); + } +} + + +gboolean +mp_serializer_from_string(MpSerializer *serializer, const gchar *str, MPNumber *z) +{ + return mp_set_from_string(str, serializer->priv->base, z); +} + + +void +mp_serializer_set_base(MpSerializer *serializer, gint base) +{ + serializer->priv->base = base; +} + + +int +mp_serializer_get_base(MpSerializer *serializer) +{ + return serializer->priv->base; +} + + +void +mp_serializer_set_radix(MpSerializer *serializer, gunichar radix) +{ + serializer->priv->radix = radix; +} + + +gunichar +mp_serializer_get_radix(MpSerializer *serializer) +{ + return serializer->priv->radix; +} + + +void +mp_serializer_set_thousands_separator(MpSerializer *serializer, gunichar separator) +{ + serializer->priv->tsep = separator; +} + + +gunichar +mp_serializer_get_thousands_separator(MpSerializer *serializer) +{ + return serializer->priv->tsep; +} + + +gint +mp_serializer_get_thousands_separator_count(MpSerializer *serializer) +{ + return serializer->priv->tsep_count; +} + + +void +mp_serializer_set_show_thousands_separators(MpSerializer *serializer, gboolean visible) +{ + serializer->priv->show_tsep = visible; +} + + +gboolean +mp_serializer_get_show_thousands_separators(MpSerializer *serializer) +{ + return serializer->priv->show_tsep; +} + + +void +mp_serializer_set_show_trailing_zeroes(MpSerializer *serializer, gboolean visible) +{ + serializer->priv->show_zeroes = visible; +} + + +gboolean +mp_serializer_get_show_trailing_zeroes(MpSerializer *serializer) +{ + return serializer->priv->show_zeroes; +} + + +int +mp_serializer_get_leading_digits(MpSerializer *serializer) +{ + return serializer->priv->leading_digits; +} + + +void +mp_serializer_set_leading_digits(MpSerializer *serializer, int leading_digits) +{ + serializer->priv->leading_digits = leading_digits; +} + + +int +mp_serializer_get_trailing_digits(MpSerializer *serializer) +{ + return serializer->priv->trailing_digits; +} + + +void +mp_serializer_set_trailing_digits(MpSerializer *serializer, int trailing_digits) +{ + serializer->priv->trailing_digits = trailing_digits; +} + + +MpDisplayFormat +mp_serializer_get_number_format(MpSerializer *serializer) +{ + return serializer->priv->format; +} + + +void +mp_serializer_set_number_format(MpSerializer *serializer, MpDisplayFormat format) +{ + serializer->priv->format = format; +} + + +static void +mp_serializer_set_property(GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MpSerializer *self = MP_SERIALIZER(object); + + switch (prop_id) { + case PROP_SHOW_THOUSANDS_SEPARATORS: + mp_serializer_set_show_thousands_separators(self, g_value_get_boolean(value)); + break; + case PROP_SHOW_TRAILING_ZEROES: + mp_serializer_set_show_trailing_zeroes(self, g_value_get_boolean(value)); + break; + case PROP_BASE: + mp_serializer_set_base(self, g_value_get_int(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + + +static void +mp_serializer_get_property(GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MpSerializer *self = MP_SERIALIZER(object); + + switch (prop_id) { + case PROP_SHOW_THOUSANDS_SEPARATORS: + g_value_set_boolean(value, mp_serializer_get_show_thousands_separators(self)); + break; + case PROP_SHOW_TRAILING_ZEROES: + g_value_set_boolean(value, mp_serializer_get_show_trailing_zeroes(self)); + break; + case PROP_BASE: + g_value_set_int(value, mp_serializer_get_base(self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + + +static void +mp_serializer_class_init(MpSerializerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + object_class->get_property = mp_serializer_get_property; + object_class->set_property = mp_serializer_set_property; + + g_type_class_add_private(klass, sizeof(MpSerializerPrivate)); + + g_object_class_install_property(object_class, + PROP_SHOW_THOUSANDS_SEPARATORS, + g_param_spec_boolean("show-thousands-separators", + "show-thousands-separators", + "Show thousands separators", + TRUE, + G_PARAM_READWRITE)); + g_object_class_install_property(object_class, + PROP_SHOW_TRAILING_ZEROES, + g_param_spec_boolean("show-trailing-zeroes", + "show-trailing-zeroes", + "Show trailing zeroes", + FALSE, + G_PARAM_READWRITE)); + g_object_class_install_property(object_class, + PROP_NUMBER_FORMAT, + g_param_spec_enum("number-format", + "number-format", + "Display format", + math_mp_display_format_get_type(), + MP_DISPLAY_FORMAT_AUTOMATIC, + G_PARAM_READWRITE)); + g_object_class_install_property(object_class, + PROP_BASE, + g_param_spec_int("base", + "base", + "Default number base", + 2, 16, 10, + G_PARAM_READWRITE)); +} + + +static void +mp_serializer_init(MpSerializer *serializer) +{ + gchar *radix, *tsep; + serializer->priv = G_TYPE_INSTANCE_GET_PRIVATE(serializer, mp_serializer_get_type(), MpSerializerPrivate); + + radix = nl_langinfo(RADIXCHAR); + serializer->priv->radix = radix ? g_utf8_get_char(g_locale_to_utf8(radix, -1, NULL, NULL, NULL)) : '.'; + tsep = nl_langinfo(THOUSEP); + if (tsep && tsep[0] != '\0') + serializer->priv->tsep = g_utf8_get_char(g_locale_to_utf8(tsep, -1, NULL, NULL, NULL)); + else + serializer->priv->tsep = ' '; + serializer->priv->tsep_count = 3; + + serializer->priv->base = 10; + serializer->priv->leading_digits = 12; + serializer->priv->trailing_digits = 9; + serializer->priv->show_zeroes = FALSE; + serializer->priv->show_tsep = FALSE; + serializer->priv->format = MP_DISPLAY_FORMAT_AUTOMATIC; +} |