summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am99
-rw-r--r--src/currency.c244
-rw-r--r--src/currency.h67
-rw-r--r--src/financial.c294
-rw-r--r--src/financial.h41
-rw-r--r--src/gcalccmd.c98
-rw-r--r--src/gcalctool.c260
-rw-r--r--src/math-buttons.c1790
-rw-r--r--src/math-buttons.h62
-rw-r--r--src/math-display.c358
-rw-r--r--src/math-display.h52
-rw-r--r--src/math-equation.c1701
-rw-r--r--src/math-equation.h133
-rw-r--r--src/math-preferences.c401
-rw-r--r--src/math-preferences.h47
-rw-r--r--src/math-variables.c163
-rw-r--r--src/math-variables.h52
-rw-r--r--src/math-window.c527
-rw-r--r--src/math-window.h61
-rw-r--r--src/mp-binary.c208
-rw-r--r--src/mp-convert.c933
-rw-r--r--src/mp-equation-lexer.l120
-rw-r--r--src/mp-equation-parser.y266
-rw-r--r--src/mp-equation-private.h64
-rw-r--r--src/mp-equation.c493
-rw-r--r--src/mp-equation.h77
-rw-r--r--src/mp-private.h47
-rw-r--r--src/mp-trigonometric.c628
-rw-r--r--src/mp.c2092
-rw-r--r--src/mp.h357
-rw-r--r--src/unittest.c761
-rw-r--r--src/unittest.h24
32 files changed, 12520 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..ce63954
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,99 @@
+bin_PROGRAMS = gcalctool gcalccmd
+
+INCLUDES = \
+ -DUI_DIR=\""$(datadir)/gcalctool"\" \
+ -DVERSION=\""$(VERSION)"\" \
+ -DLOCALE_DIR=\""$(localedir)"\" \
+ -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
+ $(WARN_CFLAGS) \
+ $(GCALCTOOL_CFLAGS)
+
+gcalctool_SOURCES = \
+ currency.c \
+ currency.h \
+ gcalctool.c \
+ math-buttons.c \
+ math-buttons.h \
+ math-display.c \
+ math-display.h \
+ math-equation.c \
+ math-equation.h \
+ math-preferences.c \
+ math-preferences.h \
+ math-variables.c \
+ math-variables.h \
+ math-window.c \
+ math-window.h \
+ mp.c \
+ mp.h \
+ mp-binary.c \
+ mp-convert.c \
+ mp-private.h \
+ mp-trigonometric.c \
+ mp-equation.c \
+ mp-equation.h \
+ mp-equation-private.h \
+ mp-equation-lexer.c \
+ mp-equation-lexer.h \
+ mp-equation-parser.c \
+ mp-equation-parser.h \
+ financial.c \
+ financial.h \
+ unittest.c \
+ unittest.h
+
+gcalctool_LDADD = \
+ $(GCALCTOOL_LIBS)
+
+gcalccmd_SOURCES = \
+ gcalccmd.c \
+ mp.c \
+ mp-convert.c \
+ mp-binary.c \
+ mp-trigonometric.c \
+ mp-equation.c \
+ mp-equation-parser.c \
+ mp-equation-lexer.c
+
+gcalccmd_LDADD = \
+ $(GCALCCMD_LIBS) \
+ -lm
+
+CLEANFILES = \
+ mp-equation-parser.h \
+ mp-equation-parser.c \
+ mp-equation-lexer.c \
+ mp-equation-lexer.h
+
+# Generate parser files
+mp-equation-parser.c mp-equation-parser.h: mp-equation-parser.y mp-equation-lexer.h
+ $(AM_V_GEN)$(YACC) -d -o mp-equation-parser.c $(srcdir)/mp-equation-parser.y
+
+# Generate lexer files
+mp-equation-lexer.c mp-equation-lexer.h: mp-equation-lexer.l
+ $(AM_V_GEN)$(LEX) $(srcdir)/mp-equation-lexer.l
+
+# Rebuild parser when source files change
+mp-equation-parser.o: mp-equation-lexer.h
+mp-equation-lexer.o: mp-equation-parser.h
+mp-equation.c: mp-equation-lexer.h mp-equation-parser.h
+
+# Install a symlink between gcalctool and mate-calculator
+install-exec-hook:
+ test -e "$(DESTDIR)$(bindir)/mate-calculator" \
+ || (cd "$(DESTDIR)$(bindir)" && ln -s gcalctool mate-calculator)
+
+# Remove the symlink between gcalctool and mate-calculator
+uninstall-local:
+ test -h "$(DESTDIR)$(bindir)/mate-calculator" \
+ && rm -f "$(DESTDIR)$(bindir)/mate-calculator"
+
+EXTRA_DIST = \
+ mp-equation-parser.y \
+ mp-equation-lexer.l
+
+DISTCLEANFILES = \
+ Makefile.in
+
+test: gcalctool
+ ./gcalctool -u
diff --git a/src/currency.c b/src/currency.c
new file mode 100644
index 0000000..1900a58
--- /dev/null
+++ b/src/currency.c
@@ -0,0 +1,244 @@
+#include <time.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
+
+#include "currency.h"
+#include "mp.h"
+
+typedef struct {
+ char *short_name;
+ MPNumber value;
+} currency;
+
+static currency *currencies = NULL;
+static int currency_count = 0;
+
+static gboolean downloading_rates = FALSE;
+static gboolean loaded_rates = FALSE;
+
+static char*
+get_rate_filepath()
+{
+ return g_build_filename(g_get_user_cache_dir (),
+ "gcalctool",
+ "eurofxref-daily.xml",
+ NULL);
+}
+
+static int
+currency_get_index(const char *short_name)
+{
+ int i;
+ for (i = 0; i < currency_count; i++) {
+ if (!strcmp(short_name, currencies[i].short_name)) {
+ if (mp_is_negative(&currencies[i].value) ||
+ mp_is_zero(&currencies[i].value)) {
+ return -1;
+ } else {
+ return i;
+ }
+ }
+ }
+ return -1;
+}
+
+/* A file needs to be redownloaded if it doesn't exist, or every 7 days.
+ * When an error occur, it probably won't hurt to try to download again.
+ */
+static int
+currency_rates_needs_update()
+{
+ gchar *filename = get_rate_filepath ();
+ struct stat buf;
+ if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) {
+ g_free(filename);
+ return 1;
+ }
+
+ if (g_stat(filename, &buf) == -1) {
+ g_free(filename);
+ return 1;
+ }
+ g_free(filename);
+
+ if (difftime(time(NULL), buf.st_mtime) > (60 * 60 * 24 * 7)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+
+static void
+download_cb(GObject *object, GAsyncResult *result, gpointer user_data)
+{
+ GError *error = NULL;
+
+ if (g_file_copy_finish(G_FILE(object), result, &error))
+ g_debug("Rates updated");
+ else
+ g_warning("Couldn't download currency file: %s", error->message);
+ g_clear_error(&error);
+ downloading_rates = FALSE;
+}
+
+
+static void
+currency_download_rates()
+{
+ gchar *filename, *directory;
+ GFile *source, *dest;
+
+ downloading_rates = TRUE;
+ g_debug("Downloading rates...");
+
+ filename = get_rate_filepath();
+ directory = g_path_get_dirname(filename);
+ g_mkdir_with_parents(directory, 0755);
+ g_free(directory);
+
+ source = g_file_new_for_uri ("http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml");
+ dest = g_file_new_for_path (filename);
+ g_free(filename);
+
+ g_file_copy_async (source, dest, G_FILE_COPY_OVERWRITE, G_PRIORITY_DEFAULT, NULL, NULL, NULL, download_cb, NULL);
+ g_object_unref(source);
+ g_object_unref(dest);
+}
+
+
+static void
+set_rate (xmlNodePtr node, currency *cur)
+{
+ xmlAttrPtr attribute;
+ for (attribute = node->properties; attribute; attribute = attribute->next) {
+ if (strcmp((char *)attribute->name, "currency") == 0) {
+ cur->short_name = (char *)xmlNodeGetContent((xmlNodePtr) attribute);
+ } else if (strcmp ((char *)attribute->name, "rate") == 0) {
+ char *val = (char *)xmlNodeGetContent ((xmlNodePtr) attribute);
+ mp_set_from_string(val, 10, &(cur->value));
+ xmlFree (val);
+ }
+ }
+}
+
+static void
+currency_load_rates()
+{
+ char *filename = get_rate_filepath();
+ xmlDocPtr document;
+ xmlXPathContextPtr xpath_ctx;
+ xmlXPathObjectPtr xpath_obj;
+ int i, len;
+
+ g_return_if_fail(g_file_test(filename, G_FILE_TEST_IS_REGULAR));
+
+ xmlInitParser();
+ document = xmlReadFile(filename, NULL, 0);
+ g_free (filename);
+ if (document == NULL) {
+ fprintf(stderr, "Couldn't parse data file\n");
+ return;
+ }
+
+ xpath_ctx = xmlXPathNewContext(document);
+ if (xpath_ctx == NULL) {
+ xmlFreeDoc(document);
+ fprintf(stderr, "Couldn't create XPath context\n");
+ return;
+ }
+
+ xmlXPathRegisterNs(xpath_ctx,
+ BAD_CAST("xref"),
+ BAD_CAST("http://www.ecb.int/vocabulary/2002-08-01/eurofxref"));
+ xpath_obj = xmlXPathEvalExpression(BAD_CAST("//xref:Cube[@currency][@rate]"),
+ xpath_ctx);
+
+ if (xpath_obj == NULL) {
+ xmlXPathFreeContext(xpath_ctx);
+ xmlFreeDoc(document);
+ fprintf(stderr, "Couldn't create XPath object\n");
+ return;
+ }
+
+ len = (xpath_obj->nodesetval) ? xpath_obj->nodesetval->nodeNr : 0;
+ currency_count = len + 1;
+ currencies = g_slice_alloc0(sizeof(currency) * currency_count);
+ for (i = 0; i < len; i++) {
+ if (xpath_obj->nodesetval->nodeTab[i]->type == XML_ELEMENT_NODE) {
+ set_rate(xpath_obj->nodesetval->nodeTab[i], &currencies[i]);
+ }
+
+ // Avoid accessing removed elements
+ if (xpath_obj->nodesetval->nodeTab[i]->type != XML_NAMESPACE_DECL)
+ xpath_obj->nodesetval->nodeTab[i] = NULL;
+ }
+
+ currencies[len].short_name = g_strdup("EUR");
+ MPNumber foo;
+ mp_set_from_integer(1, &foo);
+ currencies[len].value = foo;
+
+ xmlXPathFreeObject(xpath_obj);
+ xmlXPathFreeContext(xpath_ctx);
+ xmlFreeDoc(document);
+ xmlCleanupParser();
+
+ g_debug("Rates loaded");
+ loaded_rates = TRUE;
+}
+
+
+gboolean
+currency_convert(const MPNumber *from_amount,
+ const char *source_currency, const char *target_currency,
+ MPNumber *to_amount)
+{
+ int from_index, to_index;
+
+ if (downloading_rates)
+ return FALSE;
+
+ /* Update currency if necessary */
+ if (currency_rates_needs_update()) {
+ currency_download_rates();
+ return FALSE;
+ }
+ if (!loaded_rates)
+ currency_load_rates();
+
+ from_index = currency_get_index(source_currency);
+ to_index = currency_get_index(target_currency);
+ if (from_index < 0 || to_index < 0)
+ return FALSE;
+
+ if (mp_is_zero(&currencies[from_index].value) ||
+ mp_is_zero(&currencies[to_index].value)) {
+ mp_set_from_integer(0, to_amount);
+ return FALSE;
+ }
+
+ mp_divide(from_amount, &currencies[from_index].value, to_amount);
+ mp_multiply(to_amount, &currencies[to_index].value, to_amount);
+
+ return TRUE;
+}
+
+void
+currency_free_resources()
+{
+ int i;
+
+ for (i = 0; i < currency_count; i++) {
+ if (currencies[i].short_name != NULL)
+ xmlFree(currencies[i].short_name);
+ }
+ g_slice_free1(currency_count * sizeof(currency), currencies);
+ currencies = NULL;
+ currency_count = 0;
+}
diff --git a/src/currency.h b/src/currency.h
new file mode 100644
index 0000000..02cbfd8
--- /dev/null
+++ b/src/currency.h
@@ -0,0 +1,67 @@
+#ifndef CURRENCY_H
+#define CURRENCY_H
+
+#include <glib/gi18n.h>
+
+#include "mp.h"
+
+struct currency_name {
+ char *short_name;
+ char *symbol;
+ char *long_name;
+};
+
+/*
+ * List taken from http://www.ecb.int/press/pr/date/2008/html/pr081205.en.html
+ * with euro added.
+ */
+static const struct currency_name currency_names[] = {
+ {"AUD", "$", N_("Australian dollar")},
+ {"BGN", "лв", N_("Bulgarian lev")},
+ {"BRL", "R$", N_("Brazilian real")},
+ {"CAD", "$", N_("Canadian dollar")},
+ {"CHF", "Fr", N_("Swiss franc")},
+ {"CNY", "元", N_("Chinese yuan renminbi")},
+ {"CZK", "Kč", N_("Czech koruna")},
+ {"DKK", "kr", N_("Danish krone")},
+ {"EEK", "KR", N_("Estonian kroon")},
+ {"EUR", "€", N_("Euro")},
+ {"GBP", "£", N_("Pound sterling")},
+ {"HKD", "$", N_("Hong Kong dollar")},
+ {"HRK", "kn", N_("Croatian kuna")},
+ {"HUF", "Ft", N_("Hungarian forint")},
+ {"IDR", "Rp", N_("Indonesian rupiah")},
+ {"INR", "Rs", N_("Indian rupee")},
+ {"ISK", "kr", N_("Icelandic krona")},
+ {"JPY", "¥", N_("Japanese yen")},
+ {"KRW", "₩", N_("South Korean won")},
+ {"LTL", "Lt", N_("Lithuanian litas")},
+ {"LVL", "Ls", N_("Latvian lats")},
+ {"MXN", "$", N_("Mexican peso")},
+ {"MYR", "RM", N_("Malaysian ringgit")},
+ {"NOK", "kr", N_("Norwegian krone")},
+ {"NZD", "$", N_("New Zealand dollar")},
+ {"PHP", "₱", N_("Philippine peso")},
+ {"PLN", "zł", N_("Polish zloty")},
+ {"RON", "L", N_("New Romanian leu")},
+ {"RUB", "руб.", N_("Russian rouble")},
+ {"SEK", "kr", N_("Swedish krona")},
+ {"SGD", "$", N_("Singapore dollar")},
+ {"THB", "฿", N_("Thai baht")},
+ {"TRY", "TL", N_("New Turkish lira")},
+ {"USD", "$", N_("US dollar")},
+ {"ZAR", "R", N_("South African rand")},
+ {NULL, NULL}
+};
+
+// FIXME: Should indicate when rates are updated to UI
+
+/* Converts an amount of money from one currency to another */
+gboolean currency_convert(const MPNumber *from_amount,
+ const char *source_currency, const char *target_currency,
+ MPNumber *to_amount);
+
+/* Frees up all allocated resources */
+void currency_free_resources(void);
+
+#endif /* CURRENCY_H */
diff --git a/src/financial.c b/src/financial.c
new file mode 100644
index 0000000..94a21c0
--- /dev/null
+++ b/src/financial.c
@@ -0,0 +1,294 @@
+/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <glib/gi18n.h>
+
+#include "financial.h"
+#include "mp.h"
+
+static void
+calc_ctrm(MathEquation *equation, MPNumber *t, MPNumber *pint, MPNumber *fv, MPNumber *pv)
+{
+
+/* Cterm - pint (periodic interest rate).
+ * fv (future value).
+ * pv (present value).
+ *
+ * RESULT = log(fv / pv) / log(1 + pint)
+ */
+ MPNumber MP1, MP2, MP3, MP4;
+
+ mp_divide(fv, pv, &MP1);
+ mp_ln(&MP1, &MP2);
+ mp_add_integer(pint, 1, &MP3);
+ mp_ln(&MP3, &MP4);
+ mp_divide(&MP2, &MP4, t);
+}
+
+
+static void
+calc_ddb(MathEquation *equation, MPNumber *t, MPNumber *cost, MPNumber *life, MPNumber *period)
+{
+
+/* Ddb - cost (amount paid for asset).
+ * life (useful life of the asset).
+ * period (time period for depreciation allowance).
+ *
+ * bv = 0.0;
+ * for (i = 0; i < life; i++)
+ * {
+ * VAL = ((cost - bv) * 2) / life
+ * bv += VAL
+ * }
+ * RESULT = VAL
+ *
+ */
+
+ int i;
+ int len;
+ MPNumber MPbv, MP1, MP2;
+
+ mp_set_from_integer(0, &MPbv);
+ len = mp_cast_to_int(period);
+ for (i = 0; i < len; i++) {
+ mp_subtract(cost, &MPbv, &MP1);
+ mp_multiply_integer(&MP1, 2, &MP2);
+ mp_divide(&MP2, life, t);
+ mp_set_from_mp(&MPbv, &MP1);
+ mp_add(&MP1, t, &MPbv); /* TODO: why result is MPbv, for next loop? */
+ }
+
+ if (len >= 0) {
+ math_equation_set_status (equation, ("Error: the number of periods must be positive"));
+ mp_set_from_integer(0, t);
+ }
+}
+
+
+static void
+calc_fv(MathEquation *equation, MPNumber *t, MPNumber *pmt, MPNumber *pint, MPNumber *n)
+{
+
+/* Fv - pmt (periodic payment).
+ * pint (periodic interest rate).
+ * n (number of periods).
+ *
+ * RESULT = pmt * (pow(1 + pint, n) - 1) / pint
+ */
+
+ MPNumber MP1, MP2, MP3, MP4;
+
+ mp_add_integer(pint, 1, &MP1);
+ mp_xpowy(&MP1, n, &MP2);
+ mp_add_integer(&MP2, -1, &MP3);
+ mp_multiply(pmt, &MP3, &MP4);
+ mp_divide(&MP4, pint, t);
+}
+
+
+static void
+calc_gpm(MathEquation *equation, MPNumber *t, MPNumber *cost, MPNumber *margin)
+{
+
+/* Gpm - cost (cost of sale).
+ * margin (gross profit margin.
+ *
+ * RESULT = cost / (1 - margin)
+ */
+
+ MPNumber MP1, MP2;
+
+ mp_set_from_integer(1, &MP1);
+ mp_subtract(&MP1, margin, &MP2);
+ mp_divide(cost, &MP2, t);
+}
+
+
+static void
+calc_pmt(MathEquation *equation, MPNumber *t, MPNumber *prin, MPNumber *pint, MPNumber *n)
+{
+
+/* Pmt - prin (principal).
+ * pint (periodic interest rate).
+ * n (term).
+ *
+ * RESULT = prin * (pint / (1 - pow(pint + 1, -1 * n)))
+ */
+
+ MPNumber MP1, MP2, MP3, MP4;
+
+ mp_add_integer(pint, 1, &MP1);
+ mp_multiply_integer(n, -1, &MP2);
+ mp_xpowy(&MP1, &MP2, &MP3);
+ mp_multiply_integer(&MP3, -1, &MP4);
+ mp_add_integer(&MP4, 1, &MP1);
+ mp_divide(pint, &MP1, &MP2);
+ mp_multiply(prin, &MP2, t);
+}
+
+
+static void
+calc_pv(MathEquation *equation, MPNumber *t, MPNumber *pmt, MPNumber *pint, MPNumber *n)
+{
+
+/* Pv - pmt (periodic payment).
+ * pint (periodic interest rate).
+ * n (term).
+ *
+ * RESULT = pmt * (1 - pow(1 + pint, -1 * n)) / pint
+ */
+
+ MPNumber MP1, MP2, MP3, MP4;
+
+ mp_add_integer(pint, 1, &MP1);
+ mp_multiply_integer(n, -1, &MP2);
+ mp_xpowy(&MP1, &MP2, &MP3);
+ mp_multiply_integer(&MP3, -1, &MP4);
+ mp_add_integer(&MP4, 1, &MP1);
+ mp_divide(&MP1, pint, &MP2);
+ mp_multiply(pmt, &MP2, t);
+}
+
+
+static void
+calc_rate(MathEquation *equation, MPNumber *t, MPNumber *fv, MPNumber *pv, MPNumber *n)
+{
+
+/* Rate - fv (future value).
+ * pv (present value).
+ * n (term).
+ *
+ * RESULT = pow(fv / pv, 1 / n) - 1
+ */
+
+ MPNumber MP1, MP2, MP3, MP4;
+
+ mp_divide(fv, pv, &MP1);
+ mp_set_from_integer(1, &MP2);
+ mp_divide(&MP2, n, &MP3);
+ mp_xpowy(&MP1, &MP3, &MP4);
+ mp_add_integer(&MP4, -1, t);
+}
+
+
+static void
+calc_sln(MathEquation *equation, MPNumber *t, MPNumber *cost, MPNumber *salvage, MPNumber *life)
+{
+
+/* Sln - cost (cost of the asset).
+ * salvage (salvage value of the asset).
+ * life (useful life of the asset).
+ *
+ * RESULT = (cost - salvage) / life
+ */
+
+ MPNumber MP1;
+ mp_subtract(cost, salvage, &MP1);
+ mp_divide(&MP1, life, t);
+}
+
+
+static void
+calc_syd(MathEquation *equation, MPNumber *t, MPNumber *cost, MPNumber *salvage, MPNumber *life, MPNumber *period)
+{
+
+/* Syd - cost (cost of the asset).
+ * salvage (salvage value of the asset).
+ * life (useful life of the asset).
+ * period (period for which depreciation is computed).
+ *
+ * RESULT = (cost - salvage) * (life - period + 1) /
+ * (life * (life + 1)) / 2
+ */
+
+ MPNumber MP1, MP2, MP3, MP4;
+
+ mp_subtract(life, period, &MP2);
+ mp_add_integer(&MP2, 1, &MP3);
+ mp_add_integer(life, 1, &MP2);
+ mp_multiply(life, &MP2, &MP4);
+ mp_set_from_integer(2, &MP2);
+ mp_divide(&MP4, &MP2, &MP1);
+ mp_divide(&MP3, &MP1, &MP2);
+ mp_subtract(cost, salvage, &MP1);
+ mp_multiply(&MP1, &MP2, t);
+}
+
+
+static void
+calc_term(MathEquation *equation, MPNumber *t, MPNumber *pmt, MPNumber *fv, MPNumber *pint)
+{
+
+/* Term - pmt (periodic payment).
+ * fv (future value).
+ * pint (periodic interest rate).
+ *
+ * RESULT = log(1 + (fv * pint / pmt)) / log(1 + pint)
+ */
+
+ MPNumber MP1, MP2, MP3, MP4;
+
+ mp_add_integer(pint, 1, &MP1);
+ mp_ln(&MP1, &MP2);
+ mp_multiply(fv, pint, &MP1);
+ mp_divide(&MP1, pmt, &MP3);
+ mp_add_integer(&MP3, 1, &MP4);
+ mp_ln(&MP4, &MP1);
+ mp_divide(&MP1, &MP2, t);
+}
+
+
+void
+do_finc_expression(MathEquation *equation, int function, MPNumber *arg1, MPNumber *arg2, MPNumber *arg3, MPNumber *arg4)
+{
+ MPNumber result;
+ switch (function) {
+ case FINC_CTRM_DIALOG:
+ calc_ctrm(equation, &result, arg1, arg2, arg3);
+ break;
+ case FINC_DDB_DIALOG:
+ calc_ddb(equation, &result, arg1, arg2, arg3);
+ break;
+ case FINC_FV_DIALOG:
+ calc_fv(equation, &result, arg1, arg2, arg3);
+ break;
+ case FINC_GPM_DIALOG:
+ calc_gpm(equation, &result, arg1, arg2);
+ break;
+ case FINC_PMT_DIALOG:
+ calc_pmt(equation, &result, arg1, arg2, arg3);
+ break;
+ case FINC_PV_DIALOG:
+ calc_pv(equation, &result, arg1, arg2, arg3);
+ break;
+ case FINC_RATE_DIALOG:
+ calc_rate(equation, &result, arg1, arg2, arg3);
+ break;
+ case FINC_SLN_DIALOG:
+ calc_sln(equation, &result, arg1, arg2, arg3);
+ break;
+ case FINC_SYD_DIALOG:
+ calc_syd(equation, &result, arg1, arg2, arg3, arg4);
+ break;
+ case FINC_TERM_DIALOG:
+ calc_term(equation, &result, arg1, arg2, arg3);
+ break;
+ }
+ math_equation_set_number(equation, &result);
+}
diff --git a/src/financial.h b/src/financial.h
new file mode 100644
index 0000000..6aa8f3c
--- /dev/null
+++ b/src/financial.h
@@ -0,0 +1,41 @@
+/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef FINANCIAL_H
+#define FINANCIAL_H
+
+#include "mp.h"
+#include "math-equation.h"
+
+void do_finc_expression(MathEquation *equation, int function, MPNumber *arg1, MPNumber *arg2, MPNumber *arg3, MPNumber *arg4);
+
+enum finc_dialogs {
+ FINC_CTRM_DIALOG,
+ FINC_DDB_DIALOG,
+ FINC_FV_DIALOG,
+ FINC_GPM_DIALOG,
+ FINC_PMT_DIALOG,
+ FINC_PV_DIALOG,
+ FINC_RATE_DIALOG,
+ FINC_SLN_DIALOG,
+ FINC_SYD_DIALOG,
+ FINC_TERM_DIALOG
+};
+
+#endif /* FINANCIAL_H */
diff --git a/src/gcalccmd.c b/src/gcalccmd.c
new file mode 100644
index 0000000..9af9a58
--- /dev/null
+++ b/src/gcalccmd.c
@@ -0,0 +1,98 @@
+/* $Header$
+ *
+ * Copyright (c) 2009 Rich Burridge
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <time.h>
+
+#include "mp-equation.h"
+
+#define MAXLINE 1024
+
+static void
+solve(const char *equation)
+{
+ int ret;
+ MPEquationOptions options;
+ MPNumber z;
+ char result_str[MAXLINE];
+
+ memset(&options, 0, sizeof(options));
+ options.base = 10;
+ options.wordlen = 32;
+ options.angle_units = MP_DEGREES;
+
+ ret = mp_equation_parse(equation, &options, &z, NULL);
+
+ if (ret == PARSER_ERR_MP)
+ fprintf(stderr, "Error %s\n", mp_get_error());
+ else if (ret)
+ fprintf(stderr, "Error %d\n", ret);
+ else {
+ mp_cast_to_string(&z, 10, 10, 9, 1, result_str, MAXLINE);
+ printf("%s\n", result_str);
+ }
+}
+
+
+/* Adjust user input equation string before solving it. */
+static void
+str_adjust(char *str)
+{
+ int i, j = 0;
+
+ str[strlen(str)-1] = '\0'; /* Remove newline at end of string. */
+ for (i = 0; str[i] != '\0'; i++) { /* Remove whitespace. */
+ if (str[i] != ' ' && str[i] != '\t')
+ str[j++] = str[i];
+ }
+ str[j] = '\0';
+ if (j > 0 && str[j-1] == '=') /* Remove trailing '=' (if present). */
+ str[j-1] = '\0';
+}
+
+int
+main(int argc, char **argv)
+{
+ char *equation, *line;
+ size_t nbytes = MAXLINE;
+
+ /* Seed random number generator. */
+ srand48((long) time((time_t *) 0));
+
+ equation = (char *) malloc(MAXLINE * sizeof(char));
+ while (1) {
+ printf("> ");
+ line = fgets(equation, nbytes, stdin);
+
+ if (line != NULL)
+ str_adjust(equation);
+
+ if (line == NULL || strcmp(equation, "exit") == 0 || strcmp(equation, "quit") == 0 || strlen(equation) == 0)
+ break;
+
+ solve(equation);
+ }
+ free(equation);
+
+ return 0;
+}
diff --git a/src/gcalctool.c b/src/gcalctool.c
new file mode 100644
index 0000000..4e40f6a
--- /dev/null
+++ b/src/gcalctool.c
@@ -0,0 +1,260 @@
+/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "currency.h"
+#include "unittest.h"
+#include "math-window.h"
+#include "mp-equation.h"
+
+static GSettings *settings = NULL;
+
+static MathWindow *window;
+
+static void
+version(const gchar *progname)
+{
+ /* NOTE: Is not translated so can be easily parsed */
+ fprintf(stderr, "%1$s %2$s\n", progname, VERSION);
+}
+
+
+static void
+solve(const char *equation)
+{
+ MPEquationOptions options;
+ MPErrorCode error;
+ MPNumber result;
+ char result_str[1024];
+
+ memset(&options, 0, sizeof(options));
+ options.base = 10;
+ options.wordlen = 32;
+ options.angle_units = MP_DEGREES;
+
+ error = mp_equation_parse(equation, &options, &result, NULL);
+ if(error == PARSER_ERR_MP) {
+ fprintf(stderr, "Error: %s\n", mp_get_error());
+ exit(1);
+ }
+ else if(error != 0) {
+ fprintf(stderr, "Error: %s\n", mp_error_code_to_string(error));
+ exit(1);
+ }
+ else {
+ mp_cast_to_string(&result, 10, 10, 9, 1, result_str, 1024);
+ printf("%s\n", result_str);
+ exit(0);
+ }
+}
+
+
+static void
+usage(const gchar *progname, gboolean show_application, gboolean show_gtk)
+{
+ fprintf(stderr,
+ /* Description on how to use gcalctool displayed on command-line */
+ _("Usage:\n"
+ " %s — Perform mathematical calculations"), progname);
+
+ fprintf(stderr,
+ "\n\n");
+
+ fprintf(stderr,
+ /* Description on gcalctool command-line help options displayed on command-line */
+ _("Help Options:\n"
+ " -v, --version Show release version\n"
+ " -h, -?, --help Show help options\n"
+ " --help-all Show all help options\n"
+ " --help-gtk Show GTK+ options"));
+ fprintf(stderr,
+ "\n\n");
+
+ if (show_gtk) {
+ fprintf(stderr,
+ /* Description on gcalctool command-line GTK+ options displayed on command-line */
+ _("GTK+ Options:\n"
+ " --class=CLASS Program class as used by the window manager\n"
+ " --name=NAME Program name as used by the window manager\n"
+ " --screen=SCREEN X screen to use\n"
+ " --sync Make X calls synchronous\n"
+ " --gtk-module=MODULES Load additional GTK+ modules\n"
+ " --g-fatal-warnings Make all warnings fatal"));
+ fprintf(stderr,
+ "\n\n");
+ }
+
+ if (show_application) {
+ fprintf(stderr,
+ /* Description on gcalctool application options displayed on command-line */
+ _("Application Options:\n"
+ " -u, --unittest Perform unit tests\n"
+ " -s, --solve <equation> Solve the given equation"));
+ fprintf(stderr,
+ "\n\n");
+ }
+}
+
+
+static void
+get_options(int argc, char *argv[])
+{
+ int i;
+ char *progname, *arg;
+
+ progname = g_path_get_basename(argv[0]);
+
+ for (i = 1; i < argc; i++) {
+ arg = argv[i];
+
+ if (strcmp(arg, "-v") == 0 ||
+ strcmp(arg, "--version") == 0) {
+ version(progname);
+ exit(0);
+ }
+ else if (strcmp(arg, "-h") == 0 ||
+ strcmp(arg, "-?") == 0 ||
+ strcmp(arg, "--help") == 0) {
+ usage(progname, TRUE, FALSE);
+ exit(0);
+ }
+ else if (strcmp(arg, "--help-all") == 0) {
+ usage(progname, TRUE, TRUE);
+ exit(0);
+ }
+ else if (strcmp(arg, "--help-gtk") == 0) {
+ usage(progname, FALSE, TRUE);
+ exit(0);
+ }
+ else if (strcmp(arg, "-s") == 0 ||
+ strcmp(arg, "--solve") == 0) {
+ i++;
+ if (i >= argc) {
+ fprintf(stderr,
+ /* Error printed to stderr when user uses --solve argument without an equation */
+ _("Argument --solve requires an equation to solve"));
+ fprintf(stderr, "\n");
+ exit(1);
+ }
+ else
+ solve(argv[i]);
+ }
+ else if (strcmp(arg, "-u") == 0 ||
+ strcmp(arg, "--unittest") == 0) {
+ unittest();
+ }
+ else {
+ fprintf(stderr,
+ /* Error printed to stderr when user provides an unknown command-line argument */
+ _("Unknown argument '%s'"), arg);
+ fprintf(stderr, "\n");
+ usage(progname, TRUE, FALSE);
+ exit(1);
+ }
+ }
+}
+
+
+static void
+quit_cb(MathWindow *window)
+{
+ MathEquation *equation;
+ MathButtons *buttons;
+
+ equation = math_window_get_equation(window);
+ buttons = math_window_get_buttons(window);
+
+ g_settings_set_int(settings, "accuracy", math_equation_get_accuracy(equation));
+ g_settings_set_int(settings, "word-size", math_equation_get_word_size(equation));
+ g_settings_set_int(settings, "base", math_buttons_get_programming_base(buttons));
+ g_settings_set_boolean(settings, "show-thousands", math_equation_get_show_thousands_separators(equation));
+ g_settings_set_boolean(settings, "show-zeroes", math_equation_get_show_trailing_zeroes(equation));
+ g_settings_set_enum(settings, "number-format", math_equation_get_number_format(equation));
+ g_settings_set_enum(settings, "angle-units", math_equation_get_angle_units(equation));
+ g_settings_set_enum(settings, "button-mode", math_buttons_get_mode(buttons));
+ g_settings_set_string(settings, "source-currency", math_equation_get_source_currency(equation));
+ g_settings_set_string(settings, "target-currency", math_equation_get_target_currency(equation));
+ g_settings_sync();
+
+ currency_free_resources();
+ gtk_main_quit();
+}
+
+
+int
+main(int argc, char **argv)
+{
+ MathEquation *equation;
+ int accuracy = 9, word_size = 64, base = 10;
+ gboolean show_tsep = FALSE, show_zeroes = FALSE;
+ DisplayFormat number_format;
+ MPAngleUnit angle_units;
+ ButtonMode button_mode;
+ gchar *source_currency, *target_currency;
+
+ g_type_init();
+
+ bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+ textdomain(GETTEXT_PACKAGE);
+
+ /* Seed random number generator. */
+ srand48((long) time((time_t *) 0));
+
+ get_options(argc, argv);
+
+ settings = g_settings_new ("org.mate.gcalctool");
+ accuracy = g_settings_get_int(settings, "accuracy");
+ word_size = g_settings_get_int(settings, "word-size");
+ base = g_settings_get_int(settings, "base");
+ show_tsep = g_settings_get_boolean(settings, "show-thousands");
+ show_zeroes = g_settings_get_boolean(settings, "show-zeroes");
+ number_format = g_settings_get_enum(settings, "number-format");
+ angle_units = g_settings_get_enum(settings, "angle-units");
+ button_mode = g_settings_get_enum(settings, "button-mode");
+ source_currency = g_settings_get_string(settings, "source-currency");
+ target_currency = g_settings_get_string(settings, "target-currency");
+
+ equation = math_equation_new();
+ math_equation_set_accuracy(equation, accuracy);
+ math_equation_set_word_size(equation, word_size);
+ math_equation_set_show_thousands_separators(equation, show_tsep);
+ math_equation_set_show_trailing_zeroes(equation, show_zeroes);
+ math_equation_set_number_format(equation, number_format);
+ math_equation_set_angle_units(equation, angle_units);
+ math_equation_set_source_currency(equation, source_currency);
+ math_equation_set_target_currency(equation, target_currency);
+ g_free(source_currency);
+ g_free(target_currency);
+
+ gtk_init(&argc, &argv);
+
+ window = math_window_new(equation);
+ g_signal_connect(G_OBJECT(window), "quit", G_CALLBACK(quit_cb), NULL);
+ math_buttons_set_programming_base(math_window_get_buttons(window), base);
+ math_buttons_set_mode(math_window_get_buttons(window), button_mode); // FIXME: We load the basic buttons even if we immediately switch to the next type
+
+ gtk_widget_show(GTK_WIDGET(window));
+ gtk_main();
+
+ return(0);
+}
diff --git a/src/math-buttons.c b/src/math-buttons.c
new file mode 100644
index 0000000..01abb1e
--- /dev/null
+++ b/src/math-buttons.c
@@ -0,0 +1,1790 @@
+/* Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <glib/gi18n.h>
+
+#include "math-buttons.h"
+#include "financial.h"
+#include "currency.h"
+
+enum {
+ PROP_0,
+ PROP_EQUATION,
+ PROP_MODE
+};
+
+static GType button_mode_type;
+
+#define MAXBITS 64 /* Bit panel: number of bit fields. */
+
+struct MathButtonsPrivate
+{
+ MathEquation *equation;
+
+ ButtonMode mode;
+ gint programming_base;
+
+ GtkBuilder *basic_ui, *advanced_ui, *financial_ui, *programming_ui;
+
+ GdkColor color_numbers, color_action, color_operator, color_function, color_memory, color_group;
+
+ GtkWidget *bas_panel, *adv_panel, *fin_panel, *prog_panel;
+ GtkWidget *active_panel;
+
+ GtkWidget *shift_left_menu, *shift_right_menu;
+
+ GtkWidget *function_menu;
+
+ GList *superscript_toggles;
+ GList *subscript_toggles;
+
+ GtkWidget *angle_combo;
+ GtkWidget *angle_label;
+
+ GtkWidget *base_combo;
+ GtkWidget *base_label;
+ GtkWidget *bit_panel;
+ GtkWidget *bit_labels[MAXBITS];
+
+ GtkWidget *source_currency_combo;
+ GtkWidget *target_currency_combo;
+ GtkWidget *currency_label;
+
+ GtkWidget *character_code_dialog;
+ GtkWidget *character_code_entry;
+};
+
+G_DEFINE_TYPE (MathButtons, math_buttons, GTK_TYPE_VBOX);
+
+#define UI_BASIC_FILE UI_DIR "/buttons-basic.ui"
+#define UI_ADVANCED_FILE UI_DIR "/buttons-advanced.ui"
+#define UI_FINANCIAL_FILE UI_DIR "/buttons-financial.ui"
+#define UI_PROGRAMMING_FILE UI_DIR "/buttons-programming.ui"
+
+#define GET_WIDGET(ui, name) \
+ GTK_WIDGET(gtk_builder_get_object((ui), (name)))
+
+#define WM_WIDTH_FACTOR 10
+#define WM_HEIGHT_FACTOR 30
+
+typedef enum
+{
+ NUMBER,
+ NUMBER_BOLD,
+ OPERATOR,
+ FUNCTION,
+ MEMORY,
+ GROUP,
+ ACTION
+} ButtonClass;
+
+typedef struct {
+ const char *widget_name;
+ const char *data;
+ ButtonClass class;
+ const char *tooltip;
+} ButtonData;
+
+static ButtonData button_data[] = {
+ {"pi", "π", NUMBER,
+ /* Tooltip for the Pi button */
+ N_("Pi [Ctrl+P]")},
+ {"eulers_number", "e", NUMBER,
+ /* Tooltip for the Euler's Number button */
+ N_("Euler’s Number")},
+ {"imaginary", "i", NUMBER, NULL},
+ {"numeric_point", NULL, NUMBER, NULL},
+ {"subscript", NULL, NUMBER_BOLD,
+ /* Tooltip for the subscript button */
+ N_("Subscript mode [Alt]")},
+ {"superscript", NULL, NUMBER_BOLD,
+ /* Tooltip for the superscript button */
+ N_("Superscript mode [Ctrl]")},
+ {"exponential", NULL, NUMBER_BOLD,
+ /* Tooltip for the scientific exponent button */
+ N_("Scientific exponent [Ctrl+E]")},
+ {"add", "+", OPERATOR,
+ /* Tooltip for the add button */
+ N_("Add [+]")},
+ {"subtract", "−", OPERATOR,
+ /* Tooltip for the subtract button */
+ N_("Subtract [-]")},
+ {"multiply", "×", OPERATOR,
+ /* Tooltip for the multiply button */
+ N_("Multiply [*]")},
+ {"divide", "÷", OPERATOR,
+ /* Tooltip for the divide button */
+ N_("Divide [/]")},
+ {"modulus_divide", " mod ", OPERATOR,
+ /* Tooltip for the modulus divide button */
+ N_("Modulus divide")},
+ {"function", NULL, FUNCTION,
+ /* Tooltip for the additional functions button */
+ N_("Additional Functions")},
+ {"x_pow_y", "^", FUNCTION,
+ /* Tooltip for the exponent button */
+ N_("Exponent [^ or **]")},
+ {"x_squared", "²", FUNCTION,
+ /* Tooltip for the square button */
+ N_("Square [Ctrl+2]")},
+ {"percentage", "%", NUMBER,
+ /* Tooltip for the percentage button */
+ N_("Percentage [%]")},
+ {"factorial", "!", FUNCTION,
+ /* Tooltip for the factorial button */
+ N_("Factorial [!]")},
+ {"abs", "|", FUNCTION,
+ /* Tooltip for the absolute value button */
+ N_("Absolute value [|]")},
+ {"arg", "Arg ", FUNCTION,
+ /* Tooltip for the complex argument component button */
+ N_("Complex argument")},
+ {"conjugate", "conj ", FUNCTION,
+ /* Tooltip for the complex conjugate button */
+ N_("Complex conjugate")},
+ {"root", "√", FUNCTION,
+ /* Tooltip for the root button */
+ N_("Root [Ctrl+R]")},
+ {"square_root", "√", FUNCTION,
+ /* Tooltip for the square root button */
+ N_("Square root [Ctrl+R]")},
+ {"logarithm", "log ", FUNCTION,
+ /* Tooltip for the logarithm button */
+ N_("Logarithm")},
+ {"natural_logarithm", "ln ", FUNCTION,
+ /* Tooltip for the natural logarithm button */
+ N_("Natural Logarithm")},
+ {"sine", "sin ", FUNCTION,
+ /* Tooltip for the sine button */
+ N_("Sine")},
+ {"cosine", "cos ", FUNCTION,
+ /* Tooltip for the cosine button */
+ N_("Cosine")},
+ {"tangent", "tan ", FUNCTION,
+ /* Tooltip for the tangent button */
+ N_("Tangent")},
+ {"hyperbolic_sine", "sinh ", FUNCTION,
+ /* Tooltip for the hyperbolic sine button */
+ N_("Hyperbolic Sine")},
+ {"hyperbolic_cosine", "cosh ", FUNCTION,
+ /* Tooltip for the hyperbolic cosine button */
+ N_("Hyperbolic Cosine")},
+ {"hyperbolic_tangent", "tanh ", FUNCTION,
+ /* Tooltip for the hyperbolic tangent button */
+ N_("Hyperbolic Tangent")},
+ {"inverse", "⁻¹", FUNCTION,
+ /* Tooltip for the inverse button */
+ N_("Inverse [Ctrl+I]")},
+ {"and", "∧", OPERATOR,
+ /* Tooltip for the boolean AND button */
+ N_("Boolean AND")},
+ {"or", "∨", OPERATOR,
+ /* Tooltip for the boolean OR button */
+ N_("Boolean OR")},
+ {"xor", "⊻", OPERATOR,
+ /* Tooltip for the exclusive OR button */
+ N_("Boolean Exclusive OR")},
+ {"not", "¬", FUNCTION,
+ /* Tooltip for the boolean NOT button */
+ N_("Boolean NOT")},
+ {"integer_portion", "int ", FUNCTION,
+ /* Tooltip for the integer component button */
+ N_("Integer Component")},
+ {"fractional_portion", "frac ", FUNCTION,
+ /* Tooltip for the fractional component button */
+ N_("Fractional Component")},
+ {"real_portion", "Re ", FUNCTION,
+ /* Tooltip for the real component button */
+ N_("Real Component")},
+ {"imaginary_portion", "Im ", FUNCTION,
+ /* Tooltip for the imaginary component button */
+ N_("Imaginary Component")},
+ {"ones_complement", "ones ", FUNCTION,
+ /* Tooltip for the ones complement button */
+ N_("Ones Complement")},
+ {"twos_complement", "twos ", FUNCTION,
+ /* Tooltip for the twos complement button */
+ N_("Twos Complement")},
+ {"trunc", "trunc ", FUNCTION,
+ /* Tooltip for the truncate button */
+ N_("Truncate")},
+ {"start_group", "(", GROUP,
+ /* Tooltip for the start group button */
+ N_("Start Group [(]")},
+ {"end_group", ")", GROUP,
+ /* Tooltip for the end group button */
+ N_("End Group [)]")},
+ {"store", NULL, MEMORY,
+ /* Tooltip for the assign variable button */
+ N_("Assign Variable")},
+ {"recall", NULL, MEMORY,
+ /* Tooltip for the insert variable button */
+ N_("Insert Variable")},
+ {"character", NULL, MEMORY,
+ /* Tooltip for the insert character code button */
+ N_("Insert Character Code")},
+ {"result", NULL, ACTION,
+ /* Tooltip for the solve button */
+ N_("Calculate Result")},
+ {"factor", NULL, ACTION,
+ /* Tooltip for the factor button */
+ N_("Factorize [Ctrl+F]")},
+ {"clear", NULL, GROUP,
+ /* Tooltip for the clear button */
+ N_("Clear Display [Escape]")},
+ {"undo", NULL, GROUP,
+ /* Tooltip for the undo button */
+ N_("Undo [Ctrl+Z]")},
+ {"shift_left", NULL, ACTION,
+ /* Tooltip for the shift left button */
+ N_("Shift Left")},
+ {"shift_right", NULL, ACTION,
+ /* Tooltip for the shift right button */
+ N_("Shift Right")},
+ {"finc_compounding_term", NULL, FUNCTION,
+ /* Tooltip for the compounding term button */
+ N_("Compounding Term")},
+ {"finc_double_declining_depreciation", NULL, FUNCTION,
+ /* Tooltip for the double declining depreciation button */
+ N_("Double Declining Depreciation")},
+ {"finc_future_value", NULL, FUNCTION,
+ /* Tooltip for the future value button */
+ N_("Future Value")},
+ {"finc_term", NULL, FUNCTION,
+ /* Tooltip for the financial term button */
+ N_("Financial Term")},
+ {"finc_sum_of_the_years_digits_depreciation", NULL, FUNCTION,
+ /* Tooltip for the sum of the years digits depreciation button */
+ N_("Sum of the Years Digits Depreciation")},
+ {"finc_straight_line_depreciation", NULL, FUNCTION,
+ /* Tooltip for the straight line depreciation button */
+ N_("Straight Line Depreciation")},
+ {"finc_periodic_interest_rate", NULL, FUNCTION,
+ /* Tooltip for the periodic interest rate button */
+ N_("Periodic Interest Rate")},
+ {"finc_present_value", NULL, FUNCTION,
+ /* Tooltip for the present value button */
+ N_("Present Value")},
+ {"finc_periodic_payment", NULL, FUNCTION,
+ /* Tooltip for the periodic payment button */
+ N_("Periodic Payment")},
+ {"finc_gross_profit_margin", NULL, FUNCTION,
+ /* Tooltip for the gross profit margin button */
+ N_("Gross Profit Margin")},
+ {NULL, NULL, 0, NULL}
+};
+
+typedef enum {
+ CURRENCY_TARGET_UPPER,
+ CURRENCY_TARGET_LOWER
+} CurrencyTargetRow;
+
+/* The names of each field in the dialogs for the financial functions */
+static char *finc_dialog_fields[][5] = {
+ {"ctrm_pint", "ctrm_fv", "ctrm_pv", NULL, NULL},
+ {"ddb_cost", "ddb_life", "ddb_period", NULL, NULL},
+ {"fv_pmt", "fv_pint", "fv_n", NULL, NULL},
+ {"gpm_cost", "gpm_margin", NULL, NULL, NULL},
+ {"pmt_prin", "pmt_pint", "pmt_n", NULL, NULL},
+ {"pv_pmt", "pv_pint", "pv_n", NULL, NULL},
+ {"rate_fv", "rate_pv", "rate_n", NULL, NULL},
+ {"sln_cost", "sln_salvage", "sln_life", NULL, NULL},
+ {"syd_cost", "syd_salvage", "syd_life", "syd_period", NULL},
+ {"term_pmt", "term_fv", "term_pint", NULL, NULL},
+ {NULL, NULL, NULL, NULL, NULL}
+};
+
+
+MathButtons *
+math_buttons_new(MathEquation *equation)
+{
+ return g_object_new (math_buttons_get_type(), "equation", equation, NULL);
+}
+
+
+static void
+set_tint (GtkWidget *widget, GdkColor *tint, gint alpha)
+{
+ GtkStyle *style;
+ int j;
+
+ if (!widget)
+ return;
+
+ gtk_widget_ensure_style(widget);
+ style = gtk_widget_get_style(widget);
+
+ for (j = 0; j < 5; j++) {
+ GdkColor color;
+
+ color.red = (style->bg[j].red * (10 - alpha) + tint->red * alpha) / 10;
+ color.green = (style->bg[j].green * (10 - alpha) + tint->green * alpha) / 10;
+ color.blue = (style->bg[j].blue * (10 - alpha) + tint->blue * alpha) / 10;
+ gdk_colormap_alloc_color(gdk_colormap_get_system(), &color, FALSE, TRUE);
+ gtk_widget_modify_bg(widget, j, &color);
+ }
+}
+
+
+static void
+set_data(GtkBuilder *ui, const gchar *object_name, const gchar *name, const char *value)
+{
+ GObject *object;
+ object = gtk_builder_get_object(ui, object_name);
+ if (object)
+ g_object_set_data(object, name, GINT_TO_POINTER(value));
+}
+
+
+static void
+set_int_data(GtkBuilder *ui, const gchar *object_name, const gchar *name, gint value)
+{
+ GObject *object;
+ object = gtk_builder_get_object(ui, object_name);
+ if (object)
+ g_object_set_data(object, name, GINT_TO_POINTER(value));
+}
+
+
+static void
+load_finc_dialogs(MathButtons *buttons)
+{
+ int i, j;
+
+ set_int_data(buttons->priv->financial_ui, "ctrm_dialog", "finc_dialog", FINC_CTRM_DIALOG);
+ set_int_data(buttons->priv->financial_ui, "ddb_dialog", "finc_dialog", FINC_DDB_DIALOG);
+ set_int_data(buttons->priv->financial_ui, "fv_dialog", "finc_dialog", FINC_FV_DIALOG);
+ set_int_data(buttons->priv->financial_ui, "gpm_dialog", "finc_dialog", FINC_GPM_DIALOG);
+ set_int_data(buttons->priv->financial_ui, "pmt_dialog", "finc_dialog", FINC_PMT_DIALOG);
+ set_int_data(buttons->priv->financial_ui, "pv_dialog", "finc_dialog", FINC_PV_DIALOG);
+ set_int_data(buttons->priv->financial_ui, "rate_dialog", "finc_dialog", FINC_RATE_DIALOG);
+ set_int_data(buttons->priv->financial_ui, "sln_dialog", "finc_dialog", FINC_SLN_DIALOG);
+ set_int_data(buttons->priv->financial_ui, "syd_dialog", "finc_dialog", FINC_SYD_DIALOG);
+ set_int_data(buttons->priv->financial_ui, "term_dialog", "finc_dialog", FINC_TERM_DIALOG);
+
+ for (i = 0; finc_dialog_fields[i][0] != NULL; i++) {
+ for (j = 0; finc_dialog_fields[i][j]; j++) {
+ GObject *o;
+ o = gtk_builder_get_object(buttons->priv->financial_ui, finc_dialog_fields[i][j]);
+ if(!o)
+ printf("missing '%s'\n", finc_dialog_fields[i][j]);
+ g_object_set_data(o, "finc_field", GINT_TO_POINTER(j));
+ g_object_set_data(o, "finc_dialog", GINT_TO_POINTER(i));
+ }
+ }
+}
+
+
+static void
+update_angle_label (MathButtons *buttons)
+{
+ MPNumber x;
+ MPNumber pi, max_value, min_value, fraction, input, output;
+ char *label, input_text[1024], output_text[1024];
+
+ if (!buttons->priv->angle_label)
+ return;
+
+ if (!math_equation_get_number(buttons->priv->equation, &x))
+ return;
+
+ mp_get_pi(&pi);
+ switch (math_equation_get_angle_units(buttons->priv->equation)) {
+ default:
+ case MP_DEGREES:
+ label = g_strdup("");
+ break;
+ case MP_RADIANS:
+ /* Clip to the range ±2π */
+ mp_multiply_integer(&pi, 2, &max_value);
+ mp_invert_sign(&max_value, &min_value);
+ if (!mp_is_equal(&x, &max_value) && !mp_is_equal(&x, &min_value)) {
+ mp_divide(&x, &max_value, &fraction);
+ mp_fractional_component(&fraction, &fraction);
+ mp_multiply(&fraction, &max_value, &input);
+ }
+ else {
+ mp_set_from_mp(&x, &input);
+ mp_set_from_integer(mp_is_negative(&input) ? -1 : 1, &fraction);
+ }
+ mp_cast_to_string(&input, 10, 10, 2, false, input_text, 1024);
+
+ mp_multiply_integer(&fraction, 360, &output);
+ mp_cast_to_string(&output, 10, 10, 2, false, output_text, 1024);
+ label = g_strdup_printf("%s radians = %s degrees", input_text, output_text);
+ break;
+ case MP_GRADIANS:
+ /* Clip to the range ±400 */
+ mp_set_from_integer(400, &max_value);
+ mp_invert_sign(&max_value, &min_value);
+ if (!mp_is_equal(&x, &max_value) && !mp_is_equal(&x, &min_value)) {
+ mp_divide(&x, &max_value, &fraction);
+ mp_fractional_component(&fraction, &fraction);
+ mp_multiply(&fraction, &max_value, &input);
+ }
+ else {
+ mp_set_from_mp(&x, &input);
+ mp_set_from_integer(mp_is_negative(&input) ? -1 : 1, &fraction);
+ }
+
+ mp_cast_to_string(&input, 10, 10, 2, false, input_text, 1024);
+
+ mp_multiply_integer(&fraction, 360, &output);
+ mp_cast_to_string(&output, 10, 10, 2, false, output_text, 1024);
+ label = g_strdup_printf("%s gradians = %s degrees", input_text, output_text);
+ break;
+ }
+
+ gtk_label_set_text(GTK_LABEL(buttons->priv->angle_label), label);
+ g_free(label);
+}
+
+
+static void
+update_bit_panel(MathButtons *buttons)
+{
+ MPNumber x;
+ gboolean enabled;
+ guint64 bits;
+ int i;
+ GString *label;
+ gint base;
+
+ if (!buttons->priv->bit_panel)
+ return;
+
+ enabled = math_equation_get_number(buttons->priv->equation, &x);
+
+ if (enabled) {
+ MPNumber max, fraction;
+
+ mp_set_from_unsigned_integer(G_MAXUINT64, &max);
+ mp_fractional_part(&x, &fraction);
+ if (mp_is_negative(&x) || mp_is_greater_than(&x, &max) || !mp_is_zero(&fraction))
+ enabled = FALSE;
+ else
+ bits = mp_cast_to_unsigned_int(&x);
+ }
+
+ gtk_widget_set_sensitive(buttons->priv->bit_panel, enabled);
+ gtk_widget_set_sensitive(buttons->priv->base_label, enabled);
+
+ if (!enabled)
+ return;
+
+ for (i = 0; i < MAXBITS; i++) {
+ const gchar *label;
+
+ if (bits & (1LL << (MAXBITS-i-1)))
+ label = " 1";
+ else
+ label = " 0";
+ gtk_label_set_text(GTK_LABEL(buttons->priv->bit_labels[i]), label);
+ }
+
+ base = math_equation_get_base(buttons->priv->equation);
+ label = g_string_new("");
+ if (base != 8) {
+ if (label->len != 0)
+ g_string_append(label, " = ");
+ g_string_append_printf(label, "%lo", bits);
+ g_string_append(label, "₈");
+ }
+ if (base != 10) {
+ if (label->len != 0)
+ g_string_append(label, " = ");
+ g_string_append_printf(label, "%lu", bits);
+ g_string_append(label, "₁₀");
+ }
+ if (base != 16) {
+ if (label->len != 0)
+ g_string_append(label, " = ");
+ g_string_append_printf(label, "%lX", bits);
+ g_string_append(label, "₁₆");
+ }
+
+ gtk_label_set_text(GTK_LABEL(buttons->priv->base_label), label->str);
+ g_string_free(label, TRUE);
+}
+
+
+static void
+update_currency_label(MathButtons *buttons)
+{
+ MPNumber x, value;
+ char *label;
+
+ if (!buttons->priv->currency_label)
+ return;
+
+ if (!math_equation_get_number(buttons->priv->equation, &x))
+ return;
+
+ if (currency_convert(&x,
+ math_equation_get_source_currency(buttons->priv->equation),
+ math_equation_get_target_currency(buttons->priv->equation),
+ &value)) {
+ char input_text[1024], output_text[1024];
+ const char *source_symbol, *target_symbol;
+ int i;
+
+ mp_cast_to_string(&x, 10, 10, 2, false, input_text, 1024);
+ mp_cast_to_string(&value, 10, 10, 2, false, output_text, 1024);
+
+ for (i = 0; strcmp(math_equation_get_source_currency(buttons->priv->equation), currency_names[i].short_name) != 0; i++);
+ source_symbol = currency_names[i].symbol;
+ for (i = 0; strcmp(math_equation_get_target_currency(buttons->priv->equation), currency_names[i].short_name) != 0; i++);
+ target_symbol = currency_names[i].symbol;
+
+ label = g_strdup_printf("%s%s = %s%s",
+ source_symbol, input_text,
+ target_symbol, output_text);
+ }
+ else
+ label = g_strdup("");
+
+ gtk_label_set_text(GTK_LABEL(buttons->priv->currency_label), label);
+ g_free(label);
+}
+
+
+static void
+display_changed_cb(MathEquation *equation, GParamSpec *spec, MathButtons *buttons)
+{
+ update_angle_label(buttons);
+ update_currency_label(buttons);
+ update_bit_panel(buttons);
+}
+
+
+static void
+angle_unit_combobox_changed_cb(GtkWidget *combo, MathButtons *buttons)
+{
+ MPAngleUnit value;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
+ gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter);
+ gtk_tree_model_get(model, &iter, 1, &value, -1);
+ math_equation_set_angle_units(buttons->priv->equation, value);
+}
+
+
+static void
+angle_unit_cb(MathEquation *equation, GParamSpec *spec, MathButtons *buttons)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean valid;
+
+ model = gtk_combo_box_get_model(GTK_COMBO_BOX(buttons->priv->angle_combo));
+ valid = gtk_tree_model_get_iter_first(model, &iter);
+
+ while (valid) {
+ gint v;
+
+ gtk_tree_model_get(model, &iter, 1, &v, -1);
+ if (v == math_equation_get_angle_units(buttons->priv->equation))
+ break;
+ valid = gtk_tree_model_iter_next(model, &iter);
+ }
+ if (!valid)
+ valid = gtk_tree_model_get_iter_first(model, &iter);
+
+ gtk_combo_box_set_active_iter(GTK_COMBO_BOX(buttons->priv->angle_combo), &iter);
+}
+
+
+static void
+base_combobox_changed_cb(GtkWidget *combo, MathButtons *buttons)
+{
+ gint value;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
+ gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter);
+ gtk_tree_model_get(model, &iter, 1, &value, -1);
+
+ math_equation_set_base(buttons->priv->equation, value);
+}
+
+
+static void
+base_changed_cb(MathEquation *equation, GParamSpec *spec, MathButtons *buttons)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean valid;
+
+ if (buttons->priv->mode != PROGRAMMING)
+ return;
+
+ model = gtk_combo_box_get_model(GTK_COMBO_BOX(buttons->priv->base_combo));
+ valid = gtk_tree_model_get_iter_first(model, &iter);
+ buttons->priv->programming_base = math_equation_get_base(buttons->priv->equation);
+
+ while (valid) {
+ gint v;
+
+ gtk_tree_model_get(model, &iter, 1, &v, -1);
+ if (v == buttons->priv->programming_base)
+ break;
+ valid = gtk_tree_model_iter_next(model, &iter);
+ }
+ if (!valid)
+ valid = gtk_tree_model_get_iter_first(model, &iter);
+
+ gtk_combo_box_set_active_iter(GTK_COMBO_BOX(buttons->priv->base_combo), &iter);
+}
+
+
+static void
+source_currency_combo_changed_cb(GtkWidget *combo, MathButtons *buttons)
+{
+ gchar *value;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
+ gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter);
+ gtk_tree_model_get(model, &iter, 0, &value, -1);
+
+ math_equation_set_source_currency(buttons->priv->equation, value);
+ g_free (value);
+}
+
+
+static void
+source_currency_changed_cb(MathEquation *equation, GParamSpec *spec, MathButtons *buttons)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean valid;
+
+ if (buttons->priv->mode != FINANCIAL)
+ return;
+
+ model = gtk_combo_box_get_model(GTK_COMBO_BOX(buttons->priv->source_currency_combo));
+ valid = gtk_tree_model_get_iter_first(model, &iter);
+
+ while (valid) {
+ gchar *v;
+ gboolean matched;
+
+ gtk_tree_model_get(model, &iter, 0, &v, -1);
+ matched = strcmp (math_equation_get_source_currency(buttons->priv->equation), v) == 0;
+ g_free (v);
+ if (matched)
+ break;
+ valid = gtk_tree_model_iter_next(model, &iter);
+ }
+ if (!valid)
+ valid = gtk_tree_model_get_iter_first(model, &iter);
+
+ gtk_combo_box_set_active_iter(GTK_COMBO_BOX(buttons->priv->source_currency_combo), &iter);
+ update_currency_label(buttons);
+}
+
+
+static void
+target_currency_combo_changed_cb(GtkWidget *combo, MathButtons *buttons)
+{
+ gchar *value;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
+ gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter);
+ gtk_tree_model_get(model, &iter, 0, &value, -1);
+
+ math_equation_set_target_currency(buttons->priv->equation, value);
+ g_free (value);
+}
+
+
+static void
+target_currency_changed_cb(MathEquation *equation, GParamSpec *spec, MathButtons *buttons)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean valid;
+
+ if (buttons->priv->mode != FINANCIAL)
+ return;
+
+ model = gtk_combo_box_get_model(GTK_COMBO_BOX(buttons->priv->target_currency_combo));
+ valid = gtk_tree_model_get_iter_first(model, &iter);
+
+ while (valid) {
+ gchar *v;
+ gboolean matched;
+
+ gtk_tree_model_get(model, &iter, 0, &v, -1);
+ matched = strcmp (math_equation_get_target_currency(buttons->priv->equation), v) == 0;
+ g_free (v);
+ if (matched)
+ break;
+ valid = gtk_tree_model_iter_next(model, &iter);
+ }
+ if (!valid)
+ valid = gtk_tree_model_get_iter_first(model, &iter);
+
+ gtk_combo_box_set_active_iter(GTK_COMBO_BOX(buttons->priv->target_currency_combo), &iter);
+ update_currency_label(buttons);
+}
+
+
+static GtkWidget *
+load_mode(MathButtons *buttons, ButtonMode mode)
+{
+ GtkBuilder *builder, **builder_ptr;
+ gint i;
+ gchar *name;
+ const gchar *builder_file;
+ static gchar *objects[] = { "button_panel", "character_code_dialog", "currency_dialog",
+ "ctrm_dialog", "ddb_dialog", "fv_dialog", "gpm_dialog",
+ "pmt_dialog", "pv_dialog", "rate_dialog", "sln_dialog",
+ "syd_dialog", "term_dialog", "adjustment1", "adjustment2", NULL };
+ GtkWidget *widget, **panel;
+ GError *error = NULL;
+
+ switch (mode) {
+ case BASIC:
+ builder_ptr = &buttons->priv->basic_ui;
+ builder_file = UI_BASIC_FILE;
+ panel = &buttons->priv->bas_panel;
+ break;
+ case ADVANCED:
+ builder_ptr = &buttons->priv->advanced_ui;
+ builder_file = UI_ADVANCED_FILE;
+ panel = &buttons->priv->adv_panel;
+ break;
+ case FINANCIAL:
+ builder_ptr = &buttons->priv->financial_ui;
+ builder_file = UI_FINANCIAL_FILE;
+ panel = &buttons->priv->fin_panel;
+ break;
+ case PROGRAMMING:
+ builder_ptr = &buttons->priv->programming_ui;
+ builder_file = UI_PROGRAMMING_FILE;
+ panel = &buttons->priv->prog_panel;
+ break;
+ }
+
+ if (*panel)
+ return *panel;
+
+ builder = *builder_ptr = gtk_builder_new();
+ // FIXME: Show dialog if failed to load
+ gtk_builder_add_objects_from_file(builder, builder_file, objects, &error);
+ if (error) {
+ g_warning("Error loading button UI: %s", error->message);
+ g_clear_error(&error);
+ }
+ *panel = GET_WIDGET(builder, "button_panel");
+ gtk_box_pack_end(GTK_BOX(buttons), *panel, FALSE, TRUE, 0);
+
+ /* Configure buttons */
+ for (i = 0; button_data[i].widget_name != NULL; i++) {
+ GObject *object;
+ GtkWidget *button;
+
+ name = g_strdup_printf("calc_%s_button", button_data[i].widget_name);
+ object = gtk_builder_get_object(*builder_ptr, name);
+ g_free(name);
+
+ if (!object)
+ continue;
+ button = GTK_WIDGET(object);
+ if (button_data[i].data)
+ g_object_set_data(object, "calc_text", (gpointer) button_data[i].data);
+
+ if (button_data[i].tooltip)
+ gtk_widget_set_tooltip_text(button, _(button_data[i].tooltip));
+
+ atk_object_set_name (gtk_widget_get_accessible (button), button_data[i].widget_name);
+
+ switch (button_data[i].class) {
+ case NUMBER:
+ set_tint(button, &buttons->priv->color_numbers, 1);
+ break;
+ case NUMBER_BOLD:
+ set_tint(button, &buttons->priv->color_numbers, 2);
+ break;
+ case OPERATOR:
+ set_tint(button, &buttons->priv->color_operator, 1);
+ break;
+ case FUNCTION:
+ set_tint(button, &buttons->priv->color_function, 1);
+ break;
+ case MEMORY:
+ set_tint(button, &buttons->priv->color_memory, 1);
+ break;
+ case GROUP:
+ set_tint(button, &buttons->priv->color_group, 1);
+ break;
+ case ACTION:
+ set_tint(button, &buttons->priv->color_action, 2);
+ break;
+ }
+ }
+
+ /* Set special button data */
+ for (i = 0; i < 16; i++) {
+ GtkWidget *button;
+
+ name = g_strdup_printf("calc_%d_button", i);
+ button = GET_WIDGET(builder, name);
+ if (button) {
+ g_object_set_data(G_OBJECT(button), "calc_digit", GINT_TO_POINTER(i));
+ set_tint(button, &buttons->priv->color_numbers, 1);
+ gtk_button_set_label(GTK_BUTTON(button), math_equation_get_digit_text(buttons->priv->equation, i));
+ }
+ g_free(name);
+ }
+ widget = GET_WIDGET(builder, "calc_numeric_point_button");
+ if (widget)
+ gtk_button_set_label(GTK_BUTTON(widget), math_equation_get_numeric_point_text(buttons->priv->equation));
+
+ widget = GET_WIDGET(builder, "calc_superscript_button");
+ if (widget) {
+ buttons->priv->superscript_toggles = g_list_append(buttons->priv->superscript_toggles, widget);
+ if (math_equation_get_number_mode(buttons->priv->equation) == SUPERSCRIPT)
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE);
+ }
+ widget = GET_WIDGET(builder, "calc_subscript_button");
+ if (widget) {
+ buttons->priv->subscript_toggles = g_list_append(buttons->priv->subscript_toggles, widget);
+ if (math_equation_get_number_mode(buttons->priv->equation) == SUBSCRIPT)
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE);
+ }
+
+ if (mode == ADVANCED) {
+ GtkListStore *model;
+ GtkTreeIter iter;
+ GtkCellRenderer *renderer;
+
+ buttons->priv->angle_label = GET_WIDGET(builder, "angle_label");
+
+ buttons->priv->angle_combo = GET_WIDGET(builder, "angle_units_combo");
+ model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
+ gtk_combo_box_set_model(GTK_COMBO_BOX(buttons->priv->angle_combo), GTK_TREE_MODEL(model));
+ gtk_list_store_append(GTK_LIST_STORE(model), &iter);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0,
+ /* Advanced buttons: Angle unit combo box: Use degrees for trigonometric calculations */
+ _("Degrees"), 1, MP_DEGREES, -1);
+ gtk_list_store_append(GTK_LIST_STORE(model), &iter);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0,
+ /* Advanced buttons: Angle unit combo box: Use radians for trigonometric calculations */
+ _("Radians"), 1, MP_RADIANS, -1);
+ gtk_list_store_append(GTK_LIST_STORE(model), &iter);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0,
+ /* Advanced buttons: Angle unit combo box: Use gradians for trigonometric calculations */
+ _("Gradians"), 1, MP_GRADIANS, -1);
+ renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(buttons->priv->angle_combo), renderer, TRUE);
+ gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(buttons->priv->angle_combo), renderer, "text", 0);
+
+ g_signal_connect(buttons->priv->angle_combo, "changed", G_CALLBACK(angle_unit_combobox_changed_cb), buttons);
+ g_signal_connect(buttons->priv->equation, "notify::angle-units", G_CALLBACK(angle_unit_cb), buttons);
+ angle_unit_cb(buttons->priv->equation, NULL, buttons);
+ }
+
+ if (mode == PROGRAMMING) {
+ GtkListStore *model;
+ GtkTreeIter iter;
+ GtkCellRenderer *renderer;
+
+ buttons->priv->base_label = GET_WIDGET(builder, "base_label");
+ buttons->priv->character_code_dialog = GET_WIDGET(builder, "character_code_dialog");
+ buttons->priv->character_code_entry = GET_WIDGET(builder, "character_code_entry");
+
+ buttons->priv->bit_panel = GET_WIDGET(builder, "bit_table");
+ for (i = 0; i < MAXBITS; i++) {
+ name = g_strdup_printf("bit_label_%d", i);
+ buttons->priv->bit_labels[i] = GET_WIDGET(builder, name);
+ g_free(name);
+ name = g_strdup_printf("bit_eventbox_%d", i);
+ set_int_data(builder, name, "bit_index", i);
+ }
+
+ buttons->priv->base_combo = GET_WIDGET(builder, "base_combo");
+ model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
+ gtk_combo_box_set_model(GTK_COMBO_BOX(buttons->priv->base_combo), GTK_TREE_MODEL(model));
+ gtk_list_store_append(GTK_LIST_STORE(model), &iter);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0,
+ /* Number display mode combo: Binary, e.g. 10011010010₂ */
+ _("Binary"), 1, 2, -1);
+ gtk_list_store_append(GTK_LIST_STORE(model), &iter);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0,
+ /* Number display mode combo: Octal, e.g. 2322₈ */
+ _("Octal"), 1, 8, -1);
+ gtk_list_store_append(GTK_LIST_STORE(model), &iter);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0,
+ /* Number display mode combo: Decimal, e.g. 1234 */
+ _("Decimal"), 1, 10, -1);
+ gtk_list_store_append(GTK_LIST_STORE(model), &iter);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0,
+ /* Number display mode combo: Hexadecimal, e.g. 4D2₁₆ */
+ _("Hexadecimal"), 1, 16, -1);
+ renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(buttons->priv->base_combo), renderer, TRUE);
+ gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(buttons->priv->base_combo), renderer, "text", 0);
+
+ g_signal_connect(buttons->priv->base_combo, "changed", G_CALLBACK(base_combobox_changed_cb), buttons);
+ g_signal_connect(buttons->priv->equation, "notify::base", G_CALLBACK(base_changed_cb), buttons);
+ base_changed_cb(buttons->priv->equation, NULL, buttons);
+ }
+
+ /* Setup financial functions */
+ if (mode == FINANCIAL) {
+ GtkListStore *model;
+ GtkCellRenderer *renderer;
+
+ load_finc_dialogs(buttons);
+
+ buttons->priv->source_currency_combo = GET_WIDGET(builder, "source_currency_combo");
+ buttons->priv->target_currency_combo = GET_WIDGET(builder, "target_currency_combo");
+ buttons->priv->currency_label = GET_WIDGET(builder, "currency_label");
+
+ model = gtk_list_store_new(1, G_TYPE_STRING);
+
+ for (i = 0; currency_names[i].short_name != NULL; i++) {
+ GtkTreeIter iter;
+
+ gtk_list_store_append(GTK_LIST_STORE(model), &iter);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, currency_names[i].short_name, -1);
+ }
+
+ gtk_combo_box_set_model(GTK_COMBO_BOX(buttons->priv->source_currency_combo), GTK_TREE_MODEL(model));
+ gtk_combo_box_set_model(GTK_COMBO_BOX(buttons->priv->target_currency_combo), GTK_TREE_MODEL(model));
+
+ renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(buttons->priv->source_currency_combo), renderer, TRUE);
+ gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(buttons->priv->source_currency_combo), renderer, "text", 0);
+ renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(buttons->priv->target_currency_combo), renderer, TRUE);
+ gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(buttons->priv->target_currency_combo), renderer, "text", 0);
+
+ g_signal_connect(buttons->priv->source_currency_combo, "changed", G_CALLBACK(source_currency_combo_changed_cb), buttons);
+ g_signal_connect(buttons->priv->target_currency_combo, "changed", G_CALLBACK(target_currency_combo_changed_cb), buttons);
+ g_signal_connect(buttons->priv->equation, "notify::source-currency", G_CALLBACK(source_currency_changed_cb), buttons);
+ g_signal_connect(buttons->priv->equation, "notify::target-currency", G_CALLBACK(target_currency_changed_cb), buttons);
+ source_currency_changed_cb(buttons->priv->equation, NULL, buttons);
+ target_currency_changed_cb(buttons->priv->equation, NULL, buttons);
+
+ set_data(builder, "calc_finc_compounding_term_button", "finc_dialog", "ctrm_dialog");
+ set_data(builder, "calc_finc_double_declining_depreciation_button", "finc_dialog", "ddb_dialog");
+ set_data(builder, "calc_finc_future_value_button", "finc_dialog", "fv_dialog");
+ set_data(builder, "calc_finc_gross_profit_margin_button", "finc_dialog", "gpm_dialog");
+ set_data(builder, "calc_finc_periodic_payment_button", "finc_dialog", "pmt_dialog");
+ set_data(builder, "calc_finc_present_value_button", "finc_dialog", "pv_dialog");
+ set_data(builder, "calc_finc_periodic_interest_rate_button", "finc_dialog", "rate_dialog");
+ set_data(builder, "calc_finc_straight_line_depreciation_button", "finc_dialog", "sln_dialog");
+ set_data(builder, "calc_finc_sum_of_the_years_digits_depreciation_button", "finc_dialog", "syd_dialog");
+ set_data(builder, "calc_finc_term_button", "finc_dialog", "term_dialog");
+ }
+
+ gtk_builder_connect_signals(builder, buttons);
+
+ display_changed_cb(buttons->priv->equation, NULL, buttons);
+
+ return *panel;
+}
+
+
+
+static void
+load_buttons(MathButtons *buttons)
+{
+ GtkWidget *panel;
+
+ if (!gtk_widget_get_visible(GTK_WIDGET(buttons)))
+ return;
+
+ panel = load_mode(buttons, buttons->priv->mode);
+ if (buttons->priv->active_panel == panel)
+ return;
+
+ /* Hide old buttons */
+ if (buttons->priv->active_panel)
+ gtk_widget_hide(buttons->priv->active_panel);
+
+ /* Load and display new buttons */
+ buttons->priv->active_panel = panel;
+ if (panel)
+ gtk_widget_show(panel);
+}
+
+
+void
+math_buttons_set_mode(MathButtons *buttons, ButtonMode mode)
+{
+ ButtonMode old_mode;
+
+ if (buttons->priv->mode == mode)
+ return;
+
+ old_mode = buttons->priv->mode;
+ buttons->priv->mode = mode;
+
+ if (mode == PROGRAMMING)
+ math_equation_set_base(buttons->priv->equation, buttons->priv->programming_base);
+ else
+ math_equation_set_base(buttons->priv->equation, 10);
+
+ load_buttons(buttons);
+
+ g_object_notify(G_OBJECT(buttons), "mode");
+}
+
+
+ButtonMode
+math_buttons_get_mode(MathButtons *buttons)
+{
+ return buttons->priv->mode;
+}
+
+
+void
+math_buttons_set_programming_base(MathButtons *buttons, gint base)
+{
+ buttons->priv->programming_base = base;
+}
+
+
+gint
+math_buttons_get_programming_base(MathButtons *buttons)
+{
+ return buttons->priv->programming_base;
+}
+
+
+void exponent_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+exponent_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ math_equation_insert_exponent(buttons->priv->equation);
+}
+
+
+void subtract_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+subtract_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ math_equation_insert_subtract(buttons->priv->equation);
+}
+
+
+void button_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+button_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ math_equation_insert(buttons->priv->equation, g_object_get_data(G_OBJECT(widget), "calc_text"));
+}
+
+
+void solve_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+solve_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ math_equation_solve(buttons->priv->equation);
+}
+
+
+void clear_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+clear_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ math_equation_clear(buttons->priv->equation);
+}
+
+
+void delete_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+delete_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ math_equation_delete(buttons->priv->equation);
+}
+
+
+void undo_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+undo_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ math_equation_undo(buttons->priv->equation);
+}
+
+
+static void
+shift_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ math_equation_shift(buttons->priv->equation, GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "shiftcount")));
+}
+
+
+static void
+button_menu_position_func(GtkMenu *menu, gint *x, gint *y,
+ gboolean *push_in, gpointer user_data)
+{
+ GtkWidget *button = user_data;
+ GtkAllocation allocation;
+ GdkPoint loc;
+ gint border;
+
+ gdk_window_get_origin(gtk_widget_get_window(button), &loc.x, &loc.y);
+ border = gtk_container_get_border_width(GTK_CONTAINER(button));
+ gtk_widget_get_allocation(button, &allocation);
+ *x = loc.x + allocation.x + border;
+ *y = loc.y + allocation.y + border;
+}
+
+
+static void
+popup_button_menu(GtkWidget *widget, GtkMenu *menu)
+{
+ gtk_menu_popup(menu, NULL, NULL,
+ button_menu_position_func, widget, 1, gtk_get_current_event_time());
+}
+
+
+static void
+save_variable_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ printf("save\n");
+}
+
+
+static void
+delete_variable_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ printf("delete\n");
+}
+
+
+static GtkWidget *
+make_register_menu_item(MathButtons *buttons, const gchar *name, const MPNumber *value, gboolean can_modify, GCallback callback)
+{
+ gchar text[1024] = "", *mstr;
+ GtkWidget *item, *label;
+
+ if (value) {
+ display_make_number(buttons->priv->equation, text, 1024, value);
+ mstr = g_strdup_printf("<span weight=\"bold\">%s</span> = %s", name, text);
+ }
+ else
+ mstr = g_strdup_printf("<span weight=\"bold\">%s</span>", name);
+ label = gtk_label_new(mstr);
+ gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+ g_free(mstr);
+
+ item = gtk_menu_item_new();
+
+ // FIXME: Buttons don't work inside menus...
+ if (0) {//can_modify) {
+ GtkWidget *hbox, *button;
+
+ hbox = gtk_hbox_new(FALSE, 6);
+ gtk_container_add(GTK_CONTAINER(item), hbox);
+
+ gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
+
+ button = gtk_button_new();
+ gtk_button_set_image(GTK_BUTTON(button), gtk_image_new_from_stock(GTK_STOCK_DELETE, GTK_ICON_SIZE_MENU));
+ gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
+ gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, TRUE, 0);
+ g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(delete_variable_cb), buttons);
+
+ button = gtk_button_new();
+ gtk_button_set_image(GTK_BUTTON(button), gtk_image_new_from_stock(GTK_STOCK_SAVE, GTK_ICON_SIZE_MENU));
+ gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
+ gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, TRUE, 0);
+ g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(save_variable_cb), buttons);
+ }
+ else
+ gtk_container_add(GTK_CONTAINER(item), label);
+
+ g_object_set_data(G_OBJECT(item), "register_id", g_strdup(name)); // FIXME: Memory leak
+ g_signal_connect(item, "activate", callback, buttons);
+
+ return item;
+}
+
+
+static void
+store_menu_cb(GtkMenuItem *menu, MathButtons *buttons)
+{
+ math_equation_store(buttons->priv->equation, g_object_get_data(G_OBJECT(menu), "register_id"));
+}
+
+
+void store_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+store_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ int i;
+ GtkWidget *menu;
+ GtkWidget *item;
+ gchar **names;
+
+ menu = gtk_menu_new();
+ gtk_menu_set_reserve_toggle_size(GTK_MENU(menu), FALSE);
+ set_tint(menu, &buttons->priv->color_memory, 1);
+
+ names = math_variables_get_names(math_equation_get_variables(buttons->priv->equation));
+ if (names[0] == NULL) {
+ item = gtk_menu_item_new_with_label(/* Text shown in store menu when no variables defined */
+ _("No variables defined"));
+ gtk_widget_set_sensitive(item, FALSE);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+ }
+ for (i = 0; names[i]; i++) {
+ MPNumber *value;
+ value = math_variables_get_value(math_equation_get_variables(buttons->priv->equation), names[i]);
+ item = make_register_menu_item(buttons, names[i], value, TRUE, G_CALLBACK(store_menu_cb));
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+ }
+
+ g_strfreev(names);
+
+ // FIXME
+ //item = gtk_menu_item_new_with_label(_("Add variable"));
+ //gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+ gtk_widget_show_all(menu);
+ popup_button_menu(widget, GTK_MENU(menu));
+}
+
+
+static void
+recall_menu_cb(GtkMenuItem *menu, MathButtons *buttons)
+{
+ math_equation_recall(buttons->priv->equation, g_object_get_data(G_OBJECT(menu), "register_id"));
+}
+
+
+void recall_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+recall_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ int i;
+ GtkWidget *menu;
+ GtkWidget *item;
+ gchar **names;
+
+ menu = gtk_menu_new();
+ gtk_menu_set_reserve_toggle_size(GTK_MENU(menu), FALSE);
+ set_tint(menu, &buttons->priv->color_memory, 1);
+
+ names = math_variables_get_names(math_equation_get_variables(buttons->priv->equation));
+ if (names[0] == NULL) {
+ item = gtk_menu_item_new_with_label(/* Text shown in recall menu when no variables defined */
+ _("No variables defined"));
+ gtk_widget_set_sensitive(item, FALSE);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+ }
+ for (i = 0; names[i]; i++) {
+ MPNumber *value;
+ value = math_variables_get_value(math_equation_get_variables(buttons->priv->equation), names[i]);
+ item = make_register_menu_item(buttons, names[i], value, TRUE, G_CALLBACK(recall_menu_cb));
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+ }
+
+ g_strfreev(names);
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
+ item = make_register_menu_item(buttons, "ans", math_equation_get_answer(buttons->priv->equation), FALSE, G_CALLBACK(recall_menu_cb));
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+ item = make_register_menu_item(buttons, "rand", NULL, FALSE, G_CALLBACK(recall_menu_cb));
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+ gtk_widget_show_all(menu);
+ popup_button_menu(widget, GTK_MENU(menu));
+}
+
+
+void shift_left_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+shift_left_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ if (!buttons->priv->shift_left_menu) {
+ gint i;
+ GtkWidget *menu;
+
+ menu = buttons->priv->shift_left_menu = gtk_menu_new();
+ gtk_menu_set_reserve_toggle_size(GTK_MENU(menu), FALSE);
+ set_tint(menu, &buttons->priv->color_action, 1);
+
+ for (i = 1; i < 16; i++) {
+ GtkWidget *item, *label;
+ gchar *format, *text;
+
+ if (i < 10) {
+ /* Left Shift Popup: Menu item to shift left by n places (n < 10) */
+ format = ngettext("_%d place", "_%d places", i);
+ }
+ else {
+ /* Left Shift Popup: Menu item to shift left by n places (n >= 10) */
+ format = ngettext("%d place", "%d places", i);
+ }
+ text = g_strdup_printf(format, i);
+ label = gtk_label_new_with_mnemonic(text);
+
+ item = gtk_menu_item_new();
+ g_object_set_data(G_OBJECT(item), "shiftcount", GINT_TO_POINTER(i));
+ gtk_container_add(GTK_CONTAINER(item), label);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+ g_signal_connect(item, "activate", G_CALLBACK(shift_cb), buttons);
+
+ gtk_widget_show(label);
+ gtk_widget_show(item);
+ g_free(text);
+ }
+ }
+
+ popup_button_menu(widget, GTK_MENU(buttons->priv->shift_left_menu));
+}
+
+
+void shift_right_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+shift_right_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ if (!buttons->priv->shift_right_menu) {
+ gint i;
+ GtkWidget *menu;
+
+ menu = buttons->priv->shift_right_menu = gtk_menu_new();
+ gtk_menu_set_reserve_toggle_size(GTK_MENU(menu), FALSE);
+ set_tint(menu, &buttons->priv->color_action, 1);
+
+ for (i = 1; i < 16; i++) {
+ GtkWidget *item, *label;
+ gchar *format, *text;
+
+ if (i < 10) {
+ /* Right Shift Popup: Menu item to shift right by n places (n < 10) */
+ format = ngettext("_%d place", "_%d places", i);
+ }
+ else {
+ /* Right Shift Popup: Menu item to shift right by n places (n >= 10) */
+ format = ngettext("%d place", "%d places", i);
+ }
+ text = g_strdup_printf(format, i);
+ label = gtk_label_new_with_mnemonic(text);
+
+ item = gtk_menu_item_new();
+ g_object_set_data(G_OBJECT(item), "shiftcount", GINT_TO_POINTER(-i));
+ gtk_container_add(GTK_CONTAINER(item), label);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+ g_signal_connect(item, "activate", G_CALLBACK(shift_cb), buttons);
+
+ gtk_widget_show(label);
+ gtk_widget_show(item);
+ g_free(text);
+ }
+ }
+
+ popup_button_menu(widget, GTK_MENU(buttons->priv->shift_right_menu));
+}
+
+
+static void
+insert_function_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ math_equation_insert(buttons->priv->equation, g_object_get_data(G_OBJECT(widget), "function"));
+}
+
+
+void function_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+function_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ if (!buttons->priv->function_menu) {
+ gint i;
+ GtkWidget *menu;
+ struct
+ {
+ gchar *name, *function;
+ } functions[] =
+ {
+ { /* Tooltip for the integer component button */
+ N_("Integer Component"), "int " },
+ { /* Tooltip for the fractional component button */
+ N_("Fractional Component"), "frac " },
+ { /* Tooltip for the round button */
+ N_("Round"), "round " },
+ { /* Tooltip for the floor button */
+ N_("Floor"), "floor " },
+ { /* Tooltip for the ceiling button */
+ N_("Ceiling"), "ceil " },
+ { /* Tooltip for the ceiling button */
+ N_("Sign"), "sgn " },
+ { NULL, NULL }
+ };
+
+ menu = buttons->priv->function_menu = gtk_menu_new();
+ gtk_menu_set_reserve_toggle_size(GTK_MENU(menu), FALSE);
+ set_tint(menu, &buttons->priv->color_function, 1);
+
+ for (i = 0; functions[i].name != NULL; i++) {
+ GtkWidget *item;
+
+ item = gtk_menu_item_new_with_label(_(functions[i].name));
+ g_object_set_data(G_OBJECT(item), "function", g_strdup (functions[i].function));
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+ g_signal_connect(item, "activate", G_CALLBACK(insert_function_cb), buttons);
+ gtk_widget_show(item);
+ }
+ }
+
+ popup_button_menu(widget, GTK_MENU(buttons->priv->function_menu));
+}
+
+
+void factorize_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+factorize_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ math_equation_factorize (buttons->priv->equation);
+}
+
+
+void digit_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+digit_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ math_equation_insert_digit(buttons->priv->equation, GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "calc_digit")));
+}
+
+
+void numeric_point_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+numeric_point_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ math_equation_insert_numeric_point(buttons->priv->equation);
+}
+
+
+
+void finc_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+finc_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ gchar *name;
+
+ name = g_object_get_data(G_OBJECT(widget), "finc_dialog");
+ gtk_dialog_run(GTK_DIALOG(GET_WIDGET(buttons->priv->financial_ui, name)));
+ gtk_widget_hide(GTK_WIDGET(GET_WIDGET(buttons->priv->financial_ui, name)));
+}
+
+
+void insert_character_code_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+insert_character_code_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ gtk_window_present(GTK_WINDOW(buttons->priv->character_code_dialog));
+}
+
+
+void finc_activate_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+finc_activate_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ gint dialog, field;
+
+ dialog = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "finc_dialog"));
+ field = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "finc_field"));
+
+ if (finc_dialog_fields[dialog][field+1] == NULL) {
+ GtkWidget *dialog_widget;
+ dialog_widget = gtk_widget_get_toplevel(widget);
+ if (gtk_widget_is_toplevel (dialog_widget)) {
+ gtk_dialog_response(GTK_DIALOG(dialog_widget),
+ GTK_RESPONSE_OK);
+ return;
+ }
+ }
+ else {
+ GtkWidget *next_widget;
+ next_widget = GET_WIDGET(buttons->priv->financial_ui, finc_dialog_fields[dialog][field+1]);
+ gtk_widget_grab_focus(next_widget);
+ }
+}
+
+
+void finc_response_cb(GtkWidget *widget, gint response_id, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+finc_response_cb(GtkWidget *widget, gint response_id, MathButtons *buttons)
+{
+ int dialog;
+ int i;
+ MPNumber arg[4];
+ GtkWidget *entry;
+
+ if (response_id != GTK_RESPONSE_OK)
+ return;
+
+ dialog = GPOINTER_TO_INT (g_object_get_data(G_OBJECT(widget), "finc_dialog"));
+
+ for (i = 0; i < 4; i++) {
+ if (finc_dialog_fields[dialog][i] == NULL) {
+ continue;
+ }
+ entry = GET_WIDGET(buttons->priv->financial_ui, finc_dialog_fields[dialog][i]);
+ mp_set_from_string(gtk_entry_get_text(GTK_ENTRY(entry)), 10, &arg[i]);
+ gtk_entry_set_text(GTK_ENTRY(entry), "0");
+ }
+ gtk_widget_grab_focus(GET_WIDGET(buttons->priv->financial_ui, finc_dialog_fields[dialog][0]));
+
+ do_finc_expression(buttons->priv->equation, dialog, &arg[0], &arg[1], &arg[2], &arg[3]);
+}
+
+
+void character_code_dialog_response_cb(GtkWidget *dialog, gint response_id, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+character_code_dialog_response_cb(GtkWidget *dialog, gint response_id, MathButtons *buttons)
+{
+ const gchar *text;
+
+ text = gtk_entry_get_text(GTK_ENTRY(buttons->priv->character_code_entry));
+
+ if (response_id == GTK_RESPONSE_OK) {
+ MPNumber x;
+ int i = 0;
+
+ mp_set_from_integer(0, &x);
+ while (TRUE) {
+ mp_add_integer(&x, text[i], &x);
+ if (text[i+1]) {
+ mp_shift(&x, 8, &x);
+ i++;
+ }
+ else
+ break;
+ }
+
+ math_equation_insert_number(buttons->priv->equation, &x);
+ }
+
+ gtk_widget_hide(dialog);
+}
+
+
+void character_code_dialog_activate_cb(GtkWidget *entry, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+character_code_dialog_activate_cb(GtkWidget *entry, MathButtons *buttons)
+{
+ character_code_dialog_response_cb(buttons->priv->character_code_dialog, GTK_RESPONSE_OK, buttons);
+}
+
+
+gboolean character_code_dialog_delete_cb(GtkWidget *dialog, GdkEvent *event, MathButtons *buttons);
+G_MODULE_EXPORT
+gboolean
+character_code_dialog_delete_cb(GtkWidget *dialog, GdkEvent *event, MathButtons *buttons)
+{
+ character_code_dialog_response_cb(dialog, GTK_RESPONSE_CANCEL, buttons);
+ return TRUE;
+}
+
+
+gboolean bit_toggle_cb(GtkWidget *event_box, GdkEventButton *event, MathButtons *buttons);
+G_MODULE_EXPORT
+gboolean
+bit_toggle_cb(GtkWidget *event_box, GdkEventButton *event, MathButtons *buttons)
+{
+ math_equation_toggle_bit(buttons->priv->equation, GPOINTER_TO_INT(g_object_get_data(G_OBJECT(event_box), "bit_index")));
+ return TRUE;
+}
+
+
+
+void set_superscript_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+set_superscript_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
+ math_equation_set_number_mode(buttons->priv->equation, SUPERSCRIPT);
+ else if (math_equation_get_number_mode(buttons->priv->equation) == SUPERSCRIPT)
+ math_equation_set_number_mode(buttons->priv->equation, NORMAL);
+}
+
+
+void set_subscript_cb(GtkWidget *widget, MathButtons *buttons);
+G_MODULE_EXPORT
+void
+set_subscript_cb(GtkWidget *widget, MathButtons *buttons)
+{
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
+ math_equation_set_number_mode(buttons->priv->equation, SUBSCRIPT);
+ else if (math_equation_get_number_mode(buttons->priv->equation) == SUBSCRIPT)
+ math_equation_set_number_mode(buttons->priv->equation, NORMAL);
+}
+
+
+static void
+number_mode_changed_cb(MathEquation *equation, GParamSpec *spec, MathButtons *buttons)
+{
+ GList *i;
+ NumberMode mode;
+
+ mode = math_equation_get_number_mode(equation);
+
+ for (i = buttons->priv->superscript_toggles; i; i = i->next) {
+ GtkWidget *widget = i->data;
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), mode == SUPERSCRIPT);
+ }
+ for (i = buttons->priv->subscript_toggles; i; i = i->next) {
+ GtkWidget *widget = i->data;
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), mode == SUBSCRIPT);
+ }
+}
+
+
+static void
+math_buttons_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MathButtons *self;
+
+ self = MATH_BUTTONS (object);
+
+ switch (prop_id) {
+ case PROP_EQUATION:
+ self->priv->equation = g_value_get_object (value);
+ math_buttons_set_mode(self, self->priv->mode);
+ g_signal_connect(self->priv->equation, "notify::display", G_CALLBACK(display_changed_cb), self);
+ g_signal_connect(self->priv->equation, "notify::number-mode", G_CALLBACK(number_mode_changed_cb), self);
+ g_signal_connect(self->priv->equation, "notify::angle-units", G_CALLBACK(display_changed_cb), self);
+ g_signal_connect(self->priv->equation, "notify::number-format", G_CALLBACK(display_changed_cb), self);
+ number_mode_changed_cb(self->priv->equation, NULL, self);
+ display_changed_cb(self->priv->equation, NULL, self);
+ break;
+ case PROP_MODE:
+ math_buttons_set_mode(self, g_value_get_int (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+math_buttons_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MathButtons *self;
+
+ self = MATH_BUTTONS (object);
+
+ switch (prop_id) {
+ case PROP_EQUATION:
+ g_value_set_object (value, self->priv->equation);
+ break;
+ case PROP_MODE:
+ g_value_set_int (value, self->priv->mode);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+math_buttons_class_init (MathButtonsClass *klass)
+{
+ static GEnumValue button_mode_values[] =
+ {
+ {BASIC, "basic", "basic"},
+ {ADVANCED, "advanced", "advanced"},
+ {FINANCIAL, "financial", "financial"},
+ {PROGRAMMING, "programming", "programming"},
+ {0, NULL, NULL}
+ };
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = math_buttons_get_property;
+ object_class->set_property = math_buttons_set_property;
+
+ g_type_class_add_private (klass, sizeof (MathButtonsPrivate));
+
+ button_mode_type = g_enum_register_static("ButtonMode", button_mode_values);
+
+ g_object_class_install_property (object_class,
+ PROP_EQUATION,
+ g_param_spec_object ("equation",
+ "equation",
+ "Equation being controlled",
+ math_equation_get_type(),
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (object_class,
+ PROP_MODE,
+ g_param_spec_enum ("mode",
+ "mode",
+ "Button mode",
+ button_mode_type,
+ BASIC,
+ G_PARAM_READWRITE));
+}
+
+
+static void
+math_buttons_init (MathButtons *buttons)
+{
+ buttons->priv = G_TYPE_INSTANCE_GET_PRIVATE (buttons, math_buttons_get_type(), MathButtonsPrivate);
+ buttons->priv->programming_base = 10;
+ gdk_color_parse("#0000FF", &buttons->priv->color_numbers);
+ gdk_color_parse("#00FF00", &buttons->priv->color_action);
+ gdk_color_parse("#FF0000", &buttons->priv->color_operator);
+ gdk_color_parse("#00FFFF", &buttons->priv->color_function);
+ gdk_color_parse("#FF00FF", &buttons->priv->color_memory);
+ gdk_color_parse("#FFFFFF", &buttons->priv->color_group);
+ g_signal_connect(G_OBJECT(buttons), "show", G_CALLBACK(load_buttons), NULL);
+}
diff --git a/src/math-buttons.h b/src/math-buttons.h
new file mode 100644
index 0000000..7e85e33
--- /dev/null
+++ b/src/math-buttons.h
@@ -0,0 +1,62 @@
+/* Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef MATH_BUTTONS_H
+#define MATH_BUTTONS_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include "math-equation.h"
+
+G_BEGIN_DECLS
+
+#define MATH_BUTTONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), math_buttons_get_type(), MathButtons))
+
+typedef struct MathButtonsPrivate MathButtonsPrivate;
+
+typedef struct
+{
+ GtkVBox parent_instance;
+ MathButtonsPrivate *priv;
+} MathButtons;
+
+typedef struct
+{
+ GtkVBoxClass parent_class;
+} MathButtonsClass;
+
+typedef enum {
+ BASIC,
+ ADVANCED,
+ FINANCIAL,
+ PROGRAMMING
+} ButtonMode;
+
+GType math_buttons_get_type(void);
+
+MathButtons *math_buttons_new(MathEquation *equation);
+
+void math_buttons_set_mode(MathButtons *buttons, ButtonMode mode);
+
+ButtonMode math_buttons_get_mode(MathButtons *buttons);
+
+void math_buttons_set_programming_base(MathButtons *buttons, gint base);
+
+gint math_buttons_get_programming_base(MathButtons *buttons);
+
+#endif /* MATH_BUTTONS_H */
diff --git a/src/math-display.c b/src/math-display.c
new file mode 100644
index 0000000..62f0218
--- /dev/null
+++ b/src/math-display.c
@@ -0,0 +1,358 @@
+/* Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "math-display.h"
+
+enum {
+ PROP_0,
+ PROP_EQUATION
+};
+
+struct MathDisplayPrivate
+{
+ /* Equation being displayed */
+ MathEquation *equation;
+
+ /* Display widget */
+ GtkWidget *text_view;
+
+ /* Buffer that shows errors etc */
+ GtkTextBuffer *info_buffer;
+};
+
+G_DEFINE_TYPE (MathDisplay, math_display, GTK_TYPE_VBOX);
+
+#define GET_WIDGET(ui, name) GTK_WIDGET(gtk_builder_get_object(ui, name))
+
+MathDisplay *
+math_display_new()
+{
+ return g_object_new (math_display_get_type(), "equation", math_equation_new(), NULL);
+}
+
+
+MathDisplay *
+math_display_new_with_equation(MathEquation *equation)
+{
+ return g_object_new (math_display_get_type(), "equation", equation, NULL);
+}
+
+
+MathEquation *
+math_display_get_equation(MathDisplay *display)
+{
+ return display->priv->equation;
+}
+
+
+static gboolean
+display_key_press_cb(GtkWidget *widget, GdkEventKey *event, MathDisplay *display)
+{
+ int state;
+ guint32 c;
+
+ state = event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK);
+ c = gdk_keyval_to_unicode(event->keyval);
+
+ /* Solve on enter */
+ if (event->keyval == GDK_Return || event->keyval == GDK_KP_Enter) {
+ math_equation_solve(display->priv->equation);
+ return TRUE;
+ }
+
+ /* Clear on escape */
+ if ((event->keyval == GDK_Escape && state == 0) ||
+ (event->keyval == GDK_BackSpace && state == GDK_CONTROL_MASK) ||
+ (event->keyval == GDK_Delete && state == GDK_SHIFT_MASK)) {
+ math_equation_clear(display->priv->equation);
+ return TRUE;
+ }
+
+ /* Substitute */
+ if (state == 0) {
+ if (c == '*') {
+ math_equation_insert(display->priv->equation, "×");
+ return TRUE;
+ }
+ if (c == '/') {
+ math_equation_insert(display->priv->equation, "÷");
+ return TRUE;
+ }
+ if (c == '-') {
+ math_equation_insert_subtract(display->priv->equation);
+ return TRUE;
+ }
+ }
+
+ /* Shortcuts */
+ if (state == GDK_CONTROL_MASK) {
+ switch(event->keyval)
+ {
+ case GDK_bracketleft:
+ math_equation_insert(display->priv->equation, "⌈");
+ return TRUE;
+ case GDK_bracketright:
+ math_equation_insert(display->priv->equation, "⌉");
+ return TRUE;
+ case GDK_e:
+ math_equation_insert_exponent(display->priv->equation);
+ return TRUE;
+ case GDK_f:
+ math_equation_factorize(display->priv->equation);
+ return TRUE;
+ case GDK_i:
+ math_equation_insert(display->priv->equation, "⁻¹");
+ return TRUE;
+ case GDK_p:
+ math_equation_insert(display->priv->equation, "π");
+ return TRUE;
+ case GDK_r:
+ math_equation_insert(display->priv->equation, "√");
+ return TRUE;
+ case GDK_u:
+ math_equation_insert(display->priv->equation, "µ");
+ return TRUE;
+ case GDK_minus:
+ math_equation_insert(display->priv->equation, "⁻");
+ return TRUE;
+ case GDK_apostrophe:
+ math_equation_insert(display->priv->equation, "°");
+ return TRUE;
+ }
+ }
+ if (state == GDK_MOD1_MASK) {
+ switch(event->keyval)
+ {
+ case GDK_bracketleft:
+ math_equation_insert(display->priv->equation, "⌊");
+ return TRUE;
+ case GDK_bracketright:
+ math_equation_insert(display->priv->equation, "⌋");
+ return TRUE;
+ }
+ }
+
+ if (state == GDK_CONTROL_MASK || math_equation_get_number_mode(display->priv->equation) == SUPERSCRIPT) {
+ switch(event->keyval)
+ {
+ case GDK_0:
+ math_equation_insert(display->priv->equation, "⁰");
+ return TRUE;
+ case GDK_1:
+ math_equation_insert(display->priv->equation, "¹");
+ return TRUE;
+ case GDK_2:
+ math_equation_insert(display->priv->equation, "²");
+ return TRUE;
+ case GDK_3:
+ math_equation_insert(display->priv->equation, "³");
+ return TRUE;
+ case GDK_4:
+ math_equation_insert(display->priv->equation, "⁴");
+ return TRUE;
+ case GDK_5:
+ math_equation_insert(display->priv->equation, "⁵");
+ return TRUE;
+ case GDK_6:
+ math_equation_insert(display->priv->equation, "⁶");
+ return TRUE;
+ case GDK_7:
+ math_equation_insert(display->priv->equation, "⁷");
+ return TRUE;
+ case GDK_8:
+ math_equation_insert(display->priv->equation, "⁸");
+ return TRUE;
+ case GDK_9:
+ math_equation_insert(display->priv->equation, "⁹");
+ return TRUE;
+ }
+ }
+ else if (state == GDK_MOD1_MASK || math_equation_get_number_mode(display->priv->equation) == SUBSCRIPT) {
+ switch(event->keyval)
+ {
+ case GDK_0:
+ math_equation_insert(display->priv->equation, "₀");
+ return TRUE;
+ case GDK_1:
+ math_equation_insert(display->priv->equation, "₁");
+ return TRUE;
+ case GDK_2:
+ math_equation_insert(display->priv->equation, "₂");
+ return TRUE;
+ case GDK_3:
+ math_equation_insert(display->priv->equation, "₃");
+ return TRUE;
+ case GDK_4:
+ math_equation_insert(display->priv->equation, "₄");
+ return TRUE;
+ case GDK_5:
+ math_equation_insert(display->priv->equation, "₅");
+ return TRUE;
+ case GDK_6:
+ math_equation_insert(display->priv->equation, "₆");
+ return TRUE;
+ case GDK_7:
+ math_equation_insert(display->priv->equation, "₇");
+ return TRUE;
+ case GDK_8:
+ math_equation_insert(display->priv->equation, "₈");
+ return TRUE;
+ case GDK_9:
+ math_equation_insert(display->priv->equation, "₉");
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+static gboolean
+key_press_cb(MathDisplay *display, GdkEventKey *event)
+{
+ gboolean result;
+ g_signal_emit_by_name(display->priv->text_view, "key-press-event", event, &result);
+ return result;
+}
+
+
+static void
+status_changed_cb(MathEquation *equation, GParamSpec *spec, MathDisplay *display)
+{
+ gtk_text_buffer_set_text(display->priv->info_buffer, math_equation_get_status(equation), -1);
+}
+
+
+static void
+create_gui(MathDisplay *display)
+{
+ GtkWidget *info_view;
+ PangoFontDescription *font_desc;
+
+ g_signal_connect(display, "key-press-event", G_CALLBACK(key_press_cb), display);
+
+ display->priv->text_view = gtk_text_view_new_with_buffer(GTK_TEXT_BUFFER(display->priv->equation));
+ gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(display->priv->text_view), GTK_WRAP_WORD);
+ gtk_text_view_set_accepts_tab(GTK_TEXT_VIEW(display->priv->text_view), FALSE);
+ gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(display->priv->text_view), 8);
+ gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(display->priv->text_view), 2);
+ /* TEMP: Disabled for now as GTK+ doesn't properly render a right aligned right margin, see bug #482688 */
+ /*gtk_text_view_set_right_margin(GTK_TEXT_VIEW(display->priv->text_view), 6);*/
+ gtk_text_view_set_justification(GTK_TEXT_VIEW(display->priv->text_view), GTK_JUSTIFY_RIGHT);
+ gtk_widget_ensure_style(display->priv->text_view);
+ font_desc = pango_font_description_copy(gtk_widget_get_style(display->priv->text_view)->font_desc);
+ pango_font_description_set_size(font_desc, 16 * PANGO_SCALE);
+ gtk_widget_modify_font(display->priv->text_view, font_desc);
+ pango_font_description_free(font_desc);
+ gtk_widget_set_name(display->priv->text_view, "displayitem");
+ atk_object_set_role(gtk_widget_get_accessible(display->priv->text_view), ATK_ROLE_EDITBAR);
+ //FIXME:<property name="AtkObject::accessible-description" translatable="yes" comments="Accessible description for the area in which results are displayed">Result Region</property>
+ g_signal_connect(display->priv->text_view, "key-press-event", G_CALLBACK(display_key_press_cb), display);
+ gtk_box_pack_start(GTK_BOX(display), display->priv->text_view, TRUE, TRUE, 0);
+
+ info_view = gtk_text_view_new();
+ gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(info_view), GTK_WRAP_WORD);
+ gtk_widget_set_can_focus(info_view, TRUE); // FIXME: This should be FALSE but it locks the cursor inside the main view for some reason
+ gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(info_view), FALSE); // FIXME: Just here so when incorrectly gets focus doesn't look editable
+ gtk_text_view_set_editable(GTK_TEXT_VIEW(info_view), FALSE);
+ gtk_text_view_set_justification(GTK_TEXT_VIEW(info_view), GTK_JUSTIFY_RIGHT);
+ /* TEMP: Disabled for now as GTK+ doesn't properly render a right aligned right margin, see bug #482688 */
+ /*gtk_text_view_set_right_margin(GTK_TEXT_VIEW(info_view), 6);*/
+ gtk_box_pack_start(GTK_BOX(display), info_view, FALSE, TRUE, 0);
+ display->priv->info_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(info_view));
+
+ gtk_widget_show(info_view);
+ gtk_widget_show(display->priv->text_view);
+
+ g_signal_connect(display->priv->equation, "notify::status", G_CALLBACK(status_changed_cb), display);
+ status_changed_cb(display->priv->equation, NULL, display);
+}
+
+
+static void
+math_display_set_property(GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MathDisplay *self;
+
+ self = MATH_DISPLAY (object);
+
+ switch (prop_id) {
+ case PROP_EQUATION:
+ self->priv->equation = g_value_get_object (value);
+ create_gui(self);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+math_display_get_property(GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MathDisplay *self;
+
+ self = MATH_DISPLAY (object);
+
+ switch (prop_id) {
+ case PROP_EQUATION:
+ g_value_set_object (value, self->priv->equation);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+math_display_class_init (MathDisplayClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = math_display_get_property;
+ object_class->set_property = math_display_set_property;
+
+ g_type_class_add_private (klass, sizeof (MathDisplayPrivate));
+
+ g_object_class_install_property(object_class,
+ PROP_EQUATION,
+ g_param_spec_object("equation",
+ "equation",
+ "Equation being displayed",
+ math_equation_get_type(),
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+
+static void
+math_display_init(MathDisplay *display)
+{
+ display->priv = G_TYPE_INSTANCE_GET_PRIVATE (display, math_display_get_type(), MathDisplayPrivate);
+}
diff --git a/src/math-display.h b/src/math-display.h
new file mode 100644
index 0000000..f933d37
--- /dev/null
+++ b/src/math-display.h
@@ -0,0 +1,52 @@
+/* Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef MATH_DISPLAY_H
+#define MATH_DISPLAY_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "math-equation.h"
+
+G_BEGIN_DECLS
+
+#define MATH_DISPLAY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), math_display_get_type(), MathDisplay))
+
+typedef struct MathDisplayPrivate MathDisplayPrivate;
+
+typedef struct
+{
+ GtkVBox parent_instance;
+ MathDisplayPrivate *priv;
+} MathDisplay;
+
+typedef struct
+{
+ GtkVBoxClass parent_class;
+} MathDisplayClass;
+
+GType math_display_get_type(void);
+
+MathDisplay *math_display_new(void);
+
+MathDisplay *math_display_new_with_equation(MathEquation *equation);
+
+MathEquation *math_display_get_equation(MathDisplay *display);
+
+#endif /* MATH_DISPLAY_H */
diff --git a/src/math-equation.c b/src/math-equation.c
new file mode 100644
index 0000000..af68cd1
--- /dev/null
+++ b/src/math-equation.c
@@ -0,0 +1,1701 @@
+/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+#include <errno.h>
+#include <glib.h>
+#include <langinfo.h>
+#include <locale.h>
+
+#include "math-equation.h"
+
+#include "mp.h"
+#include "mp-equation.h"
+#include "currency.h"
+
+
+enum {
+ PROP_0,
+ PROP_STATUS,
+ PROP_DISPLAY,
+ PROP_EQUATION,
+ PROP_NUMBER_MODE,
+ PROP_ACCURACY,
+ PROP_SHOW_THOUSANDS_SEPARATORS,
+ PROP_SHOW_TRAILING_ZEROES,
+ PROP_NUMBER_FORMAT,
+ PROP_BASE,
+ PROP_WORD_SIZE,
+ PROP_ANGLE_UNITS,
+ PROP_SOURCE_CURRENCY,
+ PROP_TARGET_CURRENCY
+};
+
+static GType number_mode_type, number_format_type, angle_unit_type;
+
+#define MAX_DIGITS 512
+
+/* Expression mode state */
+typedef struct {
+ MPNumber ans; /* Previously calculated answer */
+ gchar *expression; /* Expression entered by user */
+ gint ans_start, ans_end; /* Start and end characters for ans variable in expression */
+ gint cursor; /* ??? */
+ NumberMode number_mode; /* ??? */
+ gboolean can_super_minus; /* TRUE if entering minus can generate a superscript minus */
+ gboolean entered_multiply; /* Last insert was a multiply character */
+ gchar *status; /* Equation status */
+} MathEquationState;
+
+struct MathEquationPrivate
+{
+ GtkTextTag *ans_tag;
+
+ gint show_tsep; /* Set if the thousands separator should be shown. */
+ gint show_zeroes; /* Set if trailing zeroes should be shown. */
+ DisplayFormat format; /* Number display mode. */
+ gint accuracy; /* Number of digits to show */
+ gint word_size; /* Word size in bits */
+ MPAngleUnit angle_units; /* Units for trigonometric functions */
+ char *source_currency;
+ char *target_currency;
+ gint base; /* Numeric base */
+ NumberMode number_mode; /* ??? */
+ gboolean can_super_minus; /* TRUE if entering minus can generate a superscript minus */
+
+ const char *digits[16]; /* Localized digit values */
+ const char *radix; /* Locale specific radix string. */
+ const char *tsep; /* Locale specific thousands separator. */
+ gint tsep_count; /* Number of digits between separator. */
+
+ GtkTextMark *ans_start, *ans_end;
+
+ MathEquationState state; /* Equation state */
+ GList *undo_stack; /* History of expression mode states */
+ GList *redo_stack;
+ gboolean in_undo_operation;
+
+ gboolean in_reformat;
+
+ gboolean in_delete;
+
+ MathVariables *variables;
+};
+
+G_DEFINE_TYPE (MathEquation, math_equation, GTK_TYPE_TEXT_BUFFER);
+
+
+MathEquation *
+math_equation_new()
+{
+ return g_object_new (math_equation_get_type(), NULL);
+}
+
+
+MathVariables *
+math_equation_get_variables(MathEquation *equation)
+{
+ return equation->priv->variables;
+}
+
+
+static void
+get_ans_offsets(MathEquation *equation, gint *start, gint *end)
+{
+ GtkTextIter iter;
+
+ if (!equation->priv->ans_start) {
+ *start = *end = -1;
+ return;
+ }
+
+ gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_start);
+ *start = gtk_text_iter_get_offset(&iter);
+ gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_end);
+ *end = gtk_text_iter_get_offset(&iter);
+}
+
+
+static void
+reformat_ans(MathEquation *equation)
+{
+ if (!equation->priv->ans_start)
+ return;
+
+ gchar *orig_ans_text;
+ gchar ans_text[MAX_DIGITS];
+ GtkTextIter ans_start, ans_end;
+
+ gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_start, equation->priv->ans_start);
+ gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_end, equation->priv->ans_end);
+ orig_ans_text = gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &ans_start, &ans_end, FALSE);
+ display_make_number(equation, ans_text, MAX_DIGITS, &equation->priv->state.ans);
+ if (strcmp(orig_ans_text, ans_text) != 0) {
+ gint start;
+
+ equation->priv->in_undo_operation = TRUE;
+ equation->priv->in_reformat = TRUE;
+
+ start = gtk_text_iter_get_offset(&ans_start);
+ gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation), &ans_start, &ans_end);
+ gtk_text_buffer_insert_with_tags(GTK_TEXT_BUFFER(equation), &ans_end, ans_text, -1, equation->priv->ans_tag, NULL);
+
+ /* There seems to be a bug in the marks as they alternate being the correct and incorrect ways. Reset them */
+ gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &ans_start, start);
+ gtk_text_buffer_move_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_start, &ans_start);
+ gtk_text_buffer_move_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_end, &ans_end);
+
+ equation->priv->in_reformat = FALSE;
+ equation->priv->in_undo_operation = FALSE;
+ }
+ gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_start, equation->priv->ans_start);
+ gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_end, equation->priv->ans_end);
+ g_free(orig_ans_text);
+}
+
+
+/* NOTE: Not efficent but easy to write */
+// FIXME: This is just a lexer - use the same lexer as the solver
+static void
+reformat_base(MathEquation *equation, gint old_base)
+{
+ gunichar sub_zero, sub_nine;
+ gchar *text, *read_iter;
+ gboolean in_number = FALSE, have_radix = FALSE;
+ gint offset = 0, offset_step = 0, max_digit = 0, base = -1, base_offset = 0;
+ gint ans_start, ans_end;
+
+ if (equation->priv->base == old_base)
+ return;
+
+ sub_zero = g_utf8_get_char("₀");
+ sub_nine = g_utf8_get_char("₉");
+
+ read_iter = text = math_equation_get_display(equation);
+ get_ans_offsets(equation, &ans_start, &ans_end);
+ while (TRUE) {
+ gunichar c;
+ gint digit = -1, sub_digit = -1;
+
+ /* See what digit this character is */
+ c = g_utf8_get_char(read_iter);
+ if (c >= sub_zero && c <= sub_nine)
+ sub_digit = c - sub_zero;
+ else if (c >= 'a' && c <= 'z')
+ digit = c - 'a';
+ else if (c >= 'A' && c <= 'Z')
+ digit = c - 'A';
+ else
+ digit = g_unichar_digit_value(c);
+
+ /* Don't mess with ans */
+ if (offset >= ans_start && offset <= ans_end) {
+ digit = -1;
+ sub_digit = -1;
+ }
+
+ if (in_number && digit >= 0) {
+ if (digit > max_digit)
+ max_digit = digit;
+ }
+ else if (in_number && sub_digit >= 0) {
+ if (base < 0) {
+ base_offset = offset;
+ base = 0;
+ }
+
+ base = base * 10 + sub_digit;
+ }
+ else if (in_number) {
+ /* Allow one radix inside a number */
+ if (!have_radix && base < 0 && strncmp(read_iter, equation->priv->radix, strlen(equation->priv->radix)) == 0) {
+ have_radix = TRUE;
+ read_iter += strlen(equation->priv->radix);
+ offset += g_utf8_strlen(equation->priv->radix, -1);
+ continue;
+ }
+
+ /* If had no base then insert it */
+ if (base < 0) {
+ GtkTextIter iter;
+ gint multiplier = 1;
+ gint b = old_base;
+ const char *digits[] = {"₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉"};
+
+ equation->priv->in_undo_operation = TRUE;
+ equation->priv->in_reformat = TRUE;
+
+ while (b / multiplier != 0)
+ multiplier *= 10;
+ while (multiplier != 1) {
+ int d;
+ multiplier /= 10;
+ d = b / multiplier;
+ gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &iter, offset + offset_step);
+ gtk_text_buffer_insert(GTK_TEXT_BUFFER(equation), &iter, digits[d], -1);
+ offset_step++;
+ b -= d * multiplier;
+ }
+
+ equation->priv->in_reformat = FALSE;
+ equation->priv->in_undo_operation = FALSE;
+ }
+ /* Remove the base if the current value */
+ else if (max_digit < base && base == equation->priv->base) {
+ GtkTextIter start, end;
+
+ equation->priv->in_undo_operation = TRUE;
+ equation->priv->in_reformat = TRUE;
+
+ gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &start, base_offset + offset_step);
+ gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &end, offset + offset_step);
+ gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation), &start, &end);
+ offset_step -= offset - base_offset;
+
+ equation->priv->in_reformat = FALSE;
+ equation->priv->in_undo_operation = FALSE;
+ }
+
+ in_number = FALSE;
+ }
+ else if (digit >= 0) {
+ in_number = TRUE;
+ have_radix = FALSE;
+ base = -1;
+ max_digit = digit;
+ }
+
+ if (c == '\0')
+ break;
+
+ read_iter = g_utf8_next_char(read_iter);
+ offset++;
+ }
+
+ g_free(text);
+}
+
+
+static void
+reformat_separators(MathEquation *equation)
+{
+#if 0
+ gchar *text, *read_iter;
+ gboolean in_number = FALSE, in_fraction = FALSE;
+
+ text = math_equation_get_display(equation);
+
+ /* Find numbers in display, and modify if necessary */
+ read_iter = text;
+ while(*read_iter != '\0') {
+ gunichar c;
+
+ c = g_utf8_get_char(read_iter);
+
+ if (strncmp(read_iter, equation->priv->tsep, strlen(equation->priv->tsep)) == 0)
+ ;
+ read_iter = g_utf8_next_char(read_iter);
+ }
+
+ g_free(text);
+#endif
+}
+
+
+static void
+reformat_display(MathEquation *equation, gint old_base)
+{
+ /* Change ans */
+ reformat_ans(equation);
+
+ /* Add/remove base suffixes if have changed base */
+ reformat_base(equation, old_base);
+
+ /* Add/remove thousands separators */
+ reformat_separators(equation);
+}
+
+
+static MathEquationState *
+get_current_state(MathEquation *equation)
+{
+ MathEquationState *state;
+ gint ans_start = -1, ans_end = -1;
+
+ state = g_malloc0(sizeof(MathEquationState));
+
+ if (equation->priv->ans_start)
+ {
+ GtkTextIter iter;
+ gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_start);
+ ans_start = gtk_text_iter_get_offset(&iter);
+ gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, equation->priv->ans_end);
+ ans_end = gtk_text_iter_get_offset(&iter);
+ }
+
+ mp_set_from_mp(&equation->priv->state.ans, &state->ans);
+ state->expression = math_equation_get_display(equation);
+ state->ans_start = ans_start;
+ state->ans_end = ans_end;
+ g_object_get(G_OBJECT(equation), "cursor-position", &state->cursor, NULL);
+ state->number_mode = equation->priv->number_mode;
+ state->can_super_minus = equation->priv->can_super_minus;
+ state->entered_multiply = equation->priv->state.entered_multiply;
+ state->status = g_strdup(equation->priv->state.status);
+
+ return state;
+}
+
+
+static void
+free_state(MathEquationState *state)
+{
+ g_free(state->expression);
+ g_free(state->status);
+ g_free(state);
+}
+
+
+static void
+math_equation_push_undo_stack(MathEquation *equation)
+{
+ GList *link;
+ MathEquationState *state;
+
+ if (equation->priv->in_undo_operation)
+ return;
+
+ math_equation_set_status(equation, "");
+
+ /* Can't redo anymore */
+ for (link = equation->priv->redo_stack; link; link = link->next) {
+ state = link->data;
+ free_state(state);
+ }
+ g_list_free(equation->priv->redo_stack);
+ equation->priv->redo_stack = NULL;
+
+ state = get_current_state(equation);
+ equation->priv->undo_stack = g_list_prepend(equation->priv->undo_stack, state);
+}
+
+
+static void
+clear_ans(MathEquation *equation, gboolean remove_tag)
+{
+ if (!equation->priv->ans_start)
+ return;
+
+ if (remove_tag) {
+ GtkTextIter start, end;
+
+ gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &start, equation->priv->ans_start);
+ gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &end, equation->priv->ans_end);
+ gtk_text_buffer_remove_tag(GTK_TEXT_BUFFER(equation), equation->priv->ans_tag, &start, &end);
+ }
+
+ gtk_text_buffer_delete_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_start);
+ gtk_text_buffer_delete_mark(GTK_TEXT_BUFFER(equation), equation->priv->ans_end);
+ equation->priv->ans_start = NULL;
+ equation->priv->ans_end = NULL;
+}
+
+
+static void
+apply_state(MathEquation *equation, MathEquationState *state)
+{
+ GtkTextIter cursor;
+
+ /* Disable undo detection */
+ equation->priv->in_undo_operation = TRUE;
+
+ mp_set_from_mp(&state->ans, &equation->priv->state.ans);
+
+ gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), state->expression, -1);
+ gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &cursor, state->cursor);
+ gtk_text_buffer_place_cursor(GTK_TEXT_BUFFER(equation), &cursor);
+ clear_ans(equation, FALSE);
+ if (state->ans_start >= 0) {
+ GtkTextIter start, end;
+
+ gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &start, state->ans_start);
+ equation->priv->ans_start = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &start, FALSE);
+ gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &end, state->ans_end);
+ equation->priv->ans_end = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &end, TRUE);
+ gtk_text_buffer_apply_tag(GTK_TEXT_BUFFER(equation), equation->priv->ans_tag, &start, &end);
+ }
+
+ math_equation_set_number_mode(equation, state->number_mode);
+ equation->priv->can_super_minus = state->can_super_minus;
+ equation->priv->state.entered_multiply = state->entered_multiply;
+ math_equation_set_status(equation, state->status);
+
+ equation->priv->in_undo_operation = FALSE;
+}
+
+
+void
+math_equation_copy(MathEquation *equation)
+{
+ GtkTextIter start, end;
+ gchar *text;
+
+ if (!gtk_text_buffer_get_selection_bounds(GTK_TEXT_BUFFER(equation), &start, &end))
+ gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end);
+
+ text = gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &start, &end, FALSE);
+ gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), text, -1);
+ g_free (text);
+}
+
+
+static void
+on_paste(GtkClipboard *clipboard, const gchar *text, gpointer data)
+{
+ MathEquation *equation = data;
+ math_equation_insert (equation, text);
+}
+
+
+void
+math_equation_paste(MathEquation *equation)
+{
+ gtk_clipboard_request_text(gtk_clipboard_get(GDK_NONE), on_paste, equation);
+}
+
+
+void
+math_equation_undo(MathEquation *equation)
+{
+ GList *link;
+ MathEquationState *state;
+
+ if (!equation->priv->undo_stack) {
+ math_equation_set_status(equation,
+ /* Error shown when trying to undo with no undo history */
+ _("No undo history"));
+ return;
+ }
+
+ link = equation->priv->undo_stack;
+ equation->priv->undo_stack = g_list_remove_link(equation->priv->undo_stack, link);
+ state = link->data;
+ g_list_free(link);
+
+ equation->priv->redo_stack = g_list_prepend(equation->priv->redo_stack, get_current_state(equation));
+
+ apply_state(equation, state);
+ free_state(state);
+}
+
+
+void
+math_equation_redo(MathEquation *equation)
+{
+ GList *link;
+ MathEquationState *state;
+
+ if (!equation->priv->redo_stack) {
+ math_equation_set_status(equation,
+ /* Error shown when trying to redo with no redo history */
+ _("No redo history"));
+ return;
+ }
+
+ link = equation->priv->redo_stack;
+ equation->priv->redo_stack = g_list_remove_link(equation->priv->redo_stack, link);
+ state = link->data;
+ g_list_free(link);
+
+ equation->priv->undo_stack = g_list_prepend(equation->priv->undo_stack, get_current_state(equation));
+
+ apply_state(equation, state);
+ free_state(state);
+}
+
+
+const gchar *
+math_equation_get_digit_text(MathEquation *equation, guint digit)
+{
+ return equation->priv->digits[digit];
+}
+
+
+const gchar *
+math_equation_get_numeric_point_text(MathEquation *equation)
+{
+ return equation->priv->radix;
+}
+
+
+const gchar *math_equation_get_thousands_separator_text(MathEquation *equation)
+{
+ return equation->priv->tsep;
+}
+
+
+void
+math_equation_set_accuracy(MathEquation *equation, gint accuracy)
+{
+ if (equation->priv->accuracy == accuracy)
+ return;
+ equation->priv->accuracy = accuracy;
+ reformat_display(equation, equation->priv->base);
+ g_object_notify(G_OBJECT(equation), "accuracy");
+}
+
+
+gint
+math_equation_get_accuracy(MathEquation *equation)
+{
+ return equation->priv->accuracy;
+}
+
+
+void
+math_equation_set_show_thousands_separators(MathEquation *equation, gboolean visible)
+{
+ if ((equation->priv->show_tsep && visible) || (!equation->priv->show_tsep && !visible))
+ return;
+ equation->priv->show_tsep = visible;
+ reformat_display(equation, equation->priv->base);
+ g_object_notify(G_OBJECT(equation), "show-thousands-separators");
+}
+
+
+gboolean
+math_equation_get_show_thousands_separators(MathEquation *equation)
+{
+ return equation->priv->show_tsep;
+}
+
+
+void
+math_equation_set_show_trailing_zeroes(MathEquation *equation, gboolean visible)
+{
+ if ((equation->priv->show_zeroes && visible) || (!equation->priv->show_zeroes && !visible))
+ return;
+ equation->priv->show_zeroes = visible;
+ reformat_display(equation, equation->priv->base);
+ g_object_notify(G_OBJECT(equation), "show-trailing-zeroes");
+}
+
+
+gboolean
+math_equation_get_show_trailing_zeroes(MathEquation *equation)
+{
+ return equation->priv->show_zeroes;
+}
+
+
+void
+math_equation_set_number_format(MathEquation *equation, DisplayFormat format)
+{
+ if (equation->priv->format == format)
+ return;
+
+ equation->priv->format = format;
+ reformat_display(equation, equation->priv->base);
+ g_object_notify(G_OBJECT(equation), "number-format");
+}
+
+
+DisplayFormat
+math_equation_get_number_format(MathEquation *equation)
+{
+ return equation->priv->format;
+}
+
+
+void
+math_equation_set_base(MathEquation *equation, gint base)
+{
+ gint old_base;
+
+ if (equation->priv->base == base)
+ return;
+
+ old_base = equation->priv->base;
+ equation->priv->base = base;
+ reformat_display(equation, old_base);
+ g_object_notify(G_OBJECT(equation), "base");
+}
+
+
+gint
+math_equation_get_base(MathEquation *equation)
+{
+ return equation->priv->base;
+}
+
+
+void
+math_equation_set_word_size(MathEquation *equation, gint word_size)
+{
+ if (equation->priv->word_size == word_size)
+ return;
+ equation->priv->word_size = word_size;
+ g_object_notify(G_OBJECT(equation), "word-size");
+}
+
+
+gint
+math_equation_get_word_size(MathEquation *equation)
+{
+ return equation->priv->word_size;
+}
+
+
+void
+math_equation_set_angle_units(MathEquation *equation, MPAngleUnit angle_units)
+{
+ if (equation->priv->angle_units == angle_units)
+ return;
+ equation->priv->angle_units = angle_units;
+ g_object_notify(G_OBJECT(equation), "angle-units");
+}
+
+
+MPAngleUnit
+math_equation_get_angle_units(MathEquation *equation)
+{
+ return equation->priv->angle_units;
+}
+
+
+void
+math_equation_set_source_currency(MathEquation *equation, const gchar *currency)
+{
+ // FIXME: Pick based on locale
+ if (!currency || currency[0] == '\0')
+ currency = currency_names[0].short_name;
+
+ if (strcmp(equation->priv->source_currency, currency) == 0)
+ return;
+ g_free(equation->priv->source_currency);
+ equation->priv->source_currency = g_strdup(currency);
+ g_object_notify(G_OBJECT(equation), "source-currency");
+}
+
+const gchar *
+math_equation_get_source_currency(MathEquation *equation)
+{
+ return equation->priv->source_currency;
+}
+
+
+void
+math_equation_set_target_currency(MathEquation *equation, const gchar *currency)
+{
+ // FIXME: Pick based on locale
+ if (!currency || currency[0] == '\0')
+ currency = currency_names[0].short_name;
+
+ if (strcmp(equation->priv->target_currency, currency) == 0)
+ return;
+ g_free(equation->priv->target_currency);
+ equation->priv->target_currency = g_strdup(currency);
+ g_object_notify(G_OBJECT(equation), "target-currency");
+}
+
+
+const gchar *
+math_equation_get_target_currency(MathEquation *equation)
+{
+ return equation->priv->target_currency;
+}
+
+
+void
+math_equation_set_status(MathEquation *equation, const gchar *status)
+{
+ if (strcmp(equation->priv->state.status, status) == 0)
+ return;
+
+ g_free(equation->priv->state.status);
+ equation->priv->state.status = g_strdup(status);
+ g_object_notify(G_OBJECT(equation), "status");
+}
+
+
+const gchar *
+math_equation_get_status(MathEquation *equation)
+{
+ return equation->priv->state.status;
+}
+
+
+gboolean
+math_equation_is_empty(MathEquation *equation)
+{
+ return gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation)) == 0;
+}
+
+
+gboolean
+math_equation_is_result(MathEquation *equation)
+{
+ char *text;
+ gboolean result;
+
+ text = math_equation_get_equation(equation);
+ result = strcmp(text, "ans") == 0;
+ g_free(text);
+
+ return result;
+}
+
+
+gchar *
+math_equation_get_display(MathEquation *equation)
+{
+ GtkTextIter start, end;
+
+ gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end);
+ return gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &start, &end, FALSE);
+}
+
+
+gchar *
+math_equation_get_equation(MathEquation *equation)
+{
+ char *text, *t;
+ gint ans_start, ans_end;
+
+ text = math_equation_get_display(equation);
+
+ /* No ans to substitute */
+ if(!equation->priv->ans_start)
+ return text;
+
+ get_ans_offsets(equation, &ans_start, &ans_end);
+ t = g_strdup_printf("%.*sans%s", (int)(g_utf8_offset_to_pointer(text, ans_start) - text), text, g_utf8_offset_to_pointer(text, ans_end));
+ g_free(text);
+ return t;
+}
+
+
+gboolean
+math_equation_get_number(MathEquation *equation, MPNumber *z)
+{
+ gchar *text;
+ gboolean result;
+
+ text = math_equation_get_display(equation);
+ result = !mp_set_from_string(text, equation->priv->base, z);
+ g_free (text);
+
+ return result;
+}
+
+
+void
+math_equation_set_number_mode(MathEquation *equation, NumberMode mode)
+{
+ if (equation->priv->number_mode == mode)
+ return;
+
+ equation->priv->can_super_minus = mode == SUPERSCRIPT;
+
+ equation->priv->number_mode = mode;
+ g_object_notify(G_OBJECT(equation), "number-mode");
+}
+
+
+NumberMode
+math_equation_get_number_mode(MathEquation *equation)
+{
+ return equation->priv->number_mode;
+}
+
+
+const MPNumber *
+math_equation_get_answer(MathEquation *equation)
+{
+ return &equation->priv->state.ans;
+}
+
+
+void
+math_equation_store(MathEquation *equation, const gchar *name)
+{
+ MPNumber t;
+
+ if (!math_equation_get_number(equation, &t))
+ math_equation_set_status(equation, _("No sane value to store"));
+ else
+ math_variables_set_value(equation->priv->variables, name, &t);
+}
+
+
+void
+math_equation_recall(MathEquation *equation, const gchar *name)
+{
+ math_equation_insert(equation, name);
+}
+
+
+void
+math_equation_set(MathEquation *equation, const gchar *text)
+
+{
+ gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), text, -1);
+ clear_ans(equation, FALSE);
+}
+
+
+void
+math_equation_set_number(MathEquation *equation, const MPNumber *x)
+{
+ char text[MAX_DIGITS];
+ GtkTextIter start, end;
+
+ /* Show the number in the user chosen format */
+ display_make_number(equation, text, MAX_DIGITS, x);
+ gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), text, -1);
+ mp_set_from_mp(x, &equation->priv->state.ans);
+
+ /* Mark this text as the answer variable */
+ gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end);
+ clear_ans(equation, FALSE);
+ equation->priv->ans_start = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &start, FALSE);
+ equation->priv->ans_end = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &end, TRUE);
+ gtk_text_buffer_apply_tag(GTK_TEXT_BUFFER(equation), equation->priv->ans_tag, &start, &end);
+}
+
+
+void
+math_equation_insert(MathEquation *equation, const gchar *text)
+{
+ /* Replace ** with ^ (not on all keyboards) */
+ if (!gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(equation)) &&
+ strcmp(text, "×") == 0 && equation->priv->state.entered_multiply) {
+ GtkTextIter iter;
+
+ gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, gtk_text_buffer_get_insert(GTK_TEXT_BUFFER(equation)));
+ gtk_text_buffer_backspace(GTK_TEXT_BUFFER(equation), &iter, TRUE, TRUE);
+ gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(equation), "^", -1);
+ return;
+ }
+
+ /* Start new equation when entering digits after existing result */
+ if(math_equation_is_result(equation) && g_unichar_isdigit(g_utf8_get_char(text)))
+ gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1);
+
+ /* Can't enter superscript minus after entering digits */
+ if (strstr("⁰¹²³⁴⁵⁶⁷⁸⁹", text) != NULL || strcmp("⁻", text) == 0)
+ equation->priv->can_super_minus = FALSE;
+
+ /* Disable super/subscript mode when finished entering */
+ if (strstr("⁻⁰¹²³⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉", text) == NULL)
+ math_equation_set_number_mode(equation, NORMAL);
+
+ // FIXME: Add thousands separators
+
+ gtk_text_buffer_delete_selection(GTK_TEXT_BUFFER(equation), FALSE, FALSE);
+ gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(equation), text, -1);
+}
+
+
+void
+math_equation_insert_digit(MathEquation *equation, guint digit)
+{
+ static const char *subscript_digits[] = {"₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉", NULL};
+ static const char *superscript_digits[] = {"⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹", NULL};
+
+ if (equation->priv->number_mode == NORMAL || digit >= 10)
+ math_equation_insert(equation, math_equation_get_digit_text(equation, digit));
+ else if (equation->priv->number_mode == SUPERSCRIPT)
+ math_equation_insert(equation, superscript_digits[digit]);
+ else if (equation->priv->number_mode == SUBSCRIPT)
+ math_equation_insert(equation, subscript_digits[digit]);
+}
+
+
+void
+math_equation_insert_numeric_point(MathEquation *equation)
+{
+ math_equation_insert(equation, math_equation_get_numeric_point_text(equation));
+}
+
+
+void
+math_equation_insert_number(MathEquation *equation, const MPNumber *x)
+{
+ char text[MAX_DIGITS];
+ display_make_number(equation, text, MAX_DIGITS, x);
+ math_equation_insert(equation, text);
+}
+
+
+void
+math_equation_insert_exponent(MathEquation *equation)
+{
+ math_equation_insert(equation, "×10");
+ math_equation_set_number_mode(equation, SUPERSCRIPT);
+}
+
+
+void
+math_equation_insert_subtract(MathEquation *equation)
+{
+ if (equation->priv->number_mode == SUPERSCRIPT && equation->priv->can_super_minus) {
+ math_equation_insert(equation, "⁻");
+ equation->priv->can_super_minus = FALSE;
+ }
+ else {
+ math_equation_insert(equation, "−");
+ math_equation_set_number_mode(equation, NORMAL);
+ }
+}
+
+
+static int
+variable_is_defined(const char *name, void *data)
+{
+ MathEquation *equation = data;
+ char *c, *lower_name;
+
+ lower_name = strdup(name);
+ for (c = lower_name; *c; c++)
+ *c = tolower(*c);
+
+ if (strcmp(lower_name, "rand") == 0 ||
+ strcmp(lower_name, "ans") == 0) {
+ g_free (lower_name);
+ return 1;
+ }
+ g_free (lower_name);
+
+ return math_variables_get_value(equation->priv->variables, name) != NULL;
+}
+
+
+static int
+get_variable(const char *name, MPNumber *z, void *data)
+{
+ char *c, *lower_name;
+ int result = 1;
+ MathEquation *equation = data;
+ MPNumber *t;
+
+ lower_name = strdup(name);
+ for (c = lower_name; *c; c++)
+ *c = tolower(*c);
+
+ if (strcmp(lower_name, "rand") == 0)
+ mp_set_from_random(z);
+ else if (strcmp(lower_name, "ans") == 0)
+ mp_set_from_mp(&equation->priv->state.ans, z);
+ else {
+ t = math_variables_get_value(equation->priv->variables, name);
+ if (t)
+ mp_set_from_mp(t, z);
+ else
+ result = 0;
+ }
+
+ free(lower_name);
+
+ return result;
+}
+
+
+static void
+set_variable(const char *name, const MPNumber *x, void *data)
+{
+ MathEquation *equation = data;
+ /* FIXME: Don't allow writing to built-in variables, e.g. ans, rand, sin, ... */
+ math_variables_set_value(equation->priv->variables, name, x);
+}
+
+
+static int
+convert(const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z, void *data)
+{
+ return currency_convert(x, x_units, z_units, z);
+}
+
+
+static int
+parse(MathEquation *equation, const char *text, MPNumber *z, char **error_token)
+{
+ MPEquationOptions options;
+
+ memset(&options, 0, sizeof(options));
+ options.base = equation->priv->base;
+ options.wordlen = equation->priv->word_size;
+ options.angle_units = equation->priv->angle_units;
+ options.variable_is_defined = variable_is_defined;
+ options.get_variable = get_variable;
+ options.set_variable = set_variable;
+ options.convert = convert;
+ options.callback_data = equation;
+
+ return mp_equation_parse(text, &options, z, error_token);
+}
+
+
+void
+math_equation_solve(MathEquation *equation)
+{
+ MPNumber z;
+ gint result, n_brackets = 0;
+ gchar *c, *text, *error_token = NULL, *message = NULL;
+ GString *equation_text;
+
+ if (math_equation_is_empty(equation))
+ return;
+
+ /* If showing a result return to the equation that caused it */
+ // FIXME: Result may not be here due to solve (i.e. the user may have entered "ans")
+ if (math_equation_is_result(equation)) {
+ math_equation_undo(equation);
+ return;
+ }
+
+ math_equation_set_number_mode(equation, NORMAL);
+
+ text = math_equation_get_equation(equation);
+ equation_text = g_string_new(text);
+ g_free(text);
+ /* Count the number of brackets and automatically add missing closing brackets */
+ for (c = equation_text->str; *c; c++) {
+ if (*c == '(')
+ n_brackets++;
+ else if (*c == ')')
+ n_brackets--;
+ }
+ while (n_brackets > 0) {
+ g_string_append_c(equation_text, ')');
+ n_brackets--;
+ }
+
+
+ result = parse(equation, equation_text->str, &z, &error_token);
+ g_string_free(equation_text, TRUE);
+
+ switch (result) {
+ case PARSER_ERR_NONE:
+ math_equation_set_number(equation, &z);
+ break;
+
+ case PARSER_ERR_OVERFLOW:
+ message = g_strdup(/* Error displayed to user when they perform a bitwise operation on numbers greater than the current word */
+ _("Overflow. Try a bigger word size"));
+ break;
+
+ case PARSER_ERR_UNKNOWN_VARIABLE:
+ message = g_strdup_printf(/* Error displayed to user when they an unknown variable is entered */
+ _("Unknown variable '%s'"), error_token);
+ break;
+
+ case PARSER_ERR_UNKNOWN_FUNCTION:
+ message = g_strdup_printf(/* Error displayed to user when an unknown function is entered */
+ _("Function '%s' is not defined"), error_token);
+ break;
+
+ case PARSER_ERR_UNKNOWN_CONVERSION:
+ message = g_strdup(/* Error displayed to user when an conversion with unknown units is attempted */
+ _("Unknown conversion"));
+ break;
+
+ case PARSER_ERR_MP:
+ message = g_strdup(mp_get_error());
+ break;
+
+ default:
+ message = g_strdup(/* Error displayed to user when they enter an invalid calculation */
+ _("Malformed expression"));
+ break;
+ }
+
+ if (error_token)
+ free(error_token);
+
+ if (message) {
+ math_equation_set_status(equation, message);
+ g_free(message);
+ }
+}
+
+
+void
+math_equation_factorize(MathEquation *equation)
+{
+ MPNumber x;
+ GList *factors, *factor;
+ GString *text;
+
+ if (!math_equation_get_number(equation, &x) || !mp_is_integer(&x)) {
+ /* Error displayed when trying to factorize a non-integer value */
+ math_equation_set_status(equation, _("Need an integer to factorize"));
+ return;
+ }
+
+ factors = mp_factorize(&x);
+
+ text = g_string_new("");
+
+ for (factor = factors; factor; factor = factor->next) {
+ gchar temp[MAX_DIGITS];
+ MPNumber *n;
+
+ n = factor->data;
+ display_make_number(equation, temp, MAX_DIGITS, n);
+ g_string_append(text, temp);
+ if (factor->next)
+ g_string_append(text, "×");
+ g_slice_free(MPNumber, n);
+ }
+ g_list_free(factors);
+
+ math_equation_set(equation, text->str);
+ g_string_free(text, TRUE);
+}
+
+
+void
+math_equation_delete(MathEquation *equation)
+{
+ gint cursor;
+ GtkTextIter start, end;
+
+ g_object_get(G_OBJECT(equation), "cursor-position", &cursor, NULL);
+ if (cursor >= gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation)))
+ return;
+
+ gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &start, cursor);
+ gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &end, cursor+1);
+ gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation), &start, &end);
+}
+
+
+void
+math_equation_backspace(MathEquation *equation)
+{
+ /* Can't delete empty display */
+ if (math_equation_is_empty(equation))
+ return;
+
+ if (gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(equation)))
+ gtk_text_buffer_delete_selection(GTK_TEXT_BUFFER(equation), FALSE, FALSE);
+ else {
+ GtkTextIter iter;
+ gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &iter, gtk_text_buffer_get_insert(GTK_TEXT_BUFFER(equation)));
+ gtk_text_buffer_backspace(GTK_TEXT_BUFFER(equation), &iter, TRUE, TRUE);
+ }
+}
+
+
+void
+math_equation_clear(MathEquation *equation)
+{
+ math_equation_set_number_mode(equation, NORMAL);
+ gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1);
+ clear_ans(equation, FALSE);
+}
+
+
+void
+math_equation_shift(MathEquation *equation, gint count)
+{
+ MPNumber z;
+
+ if (!math_equation_get_number(equation, &z)) {
+ math_equation_set_status(equation,
+ /* This message is displayed in the status bar when a bit
+ shift operation is performed and the display does not contain a number */
+ _("No sane value to bitwise shift"));
+ return;
+ }
+
+ mp_shift(&z, count, &z);
+ math_equation_set_number(equation, &z);
+}
+
+
+void
+math_equation_toggle_bit(MathEquation *equation, guint bit)
+{
+ MPNumber x;
+ guint64 bits;
+ gboolean result;
+
+ result = math_equation_get_number(equation, &x);
+ if (result) {
+ MPNumber max;
+ mp_set_from_unsigned_integer(G_MAXUINT64, &max);
+ if (mp_is_negative(&x) || mp_is_greater_than(&x, &max))
+ result = FALSE;
+ else
+ bits = mp_cast_to_unsigned_int(&x);
+ }
+
+ if (!result) {
+ math_equation_set_status(equation,
+ /* Message displayed when cannot toggle bit in display*/
+ _("Displayed value not an integer"));
+ return;
+ }
+
+ bits ^= (1LL << (63 - bit));
+
+ mp_set_from_unsigned_integer(bits, &x);
+
+ // FIXME: Only do this if in ans format, otherwise set text in same format as previous number
+ math_equation_set_number(equation, &x);
+}
+
+
+/* Convert MP number to character string. */
+//FIXME: What to do with this?
+void
+display_make_number(MathEquation *equation, char *target, int target_len, const MPNumber *x)
+{
+ switch(equation->priv->format) {
+ case FIX:
+ mp_cast_to_string(x, equation->priv->base, equation->priv->base, equation->priv->accuracy, !equation->priv->show_zeroes, target, target_len);
+ break;
+ case SCI:
+ mp_cast_to_exponential_string(x, equation->priv->base, equation->priv->base, equation->priv->accuracy, !equation->priv->show_zeroes, false, target, target_len);
+ break;
+ case ENG:
+ mp_cast_to_exponential_string(x, equation->priv->base, equation->priv->base, equation->priv->accuracy, !equation->priv->show_zeroes, true, target, target_len);
+ break;
+ }
+}
+
+
+static void
+math_equation_set_property(GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MathEquation *self;
+
+ self = MATH_EQUATION (object);
+
+ switch (prop_id) {
+ case PROP_STATUS:
+ math_equation_set_status(self, g_value_get_string(value));
+ break;
+ case PROP_DISPLAY:
+ math_equation_set(self, g_value_get_string(value));
+ break;
+ case PROP_NUMBER_MODE:
+ math_equation_set_number_mode(self, g_value_get_int(value));
+ break;
+ case PROP_ACCURACY:
+ math_equation_set_accuracy(self, g_value_get_int(value));
+ break;
+ case PROP_SHOW_THOUSANDS_SEPARATORS:
+ math_equation_set_show_thousands_separators(self, g_value_get_boolean(value));
+ break;
+ case PROP_SHOW_TRAILING_ZEROES:
+ math_equation_set_show_trailing_zeroes(self, g_value_get_boolean(value));
+ break;
+ case PROP_NUMBER_FORMAT:
+ math_equation_set_number_format(self, g_value_get_int(value));
+ break;
+ case PROP_BASE:
+ math_equation_set_base(self, g_value_get_int(value));
+ break;
+ case PROP_WORD_SIZE:
+ math_equation_set_word_size(self, g_value_get_int(value));
+ break;
+ case PROP_ANGLE_UNITS:
+ math_equation_set_angle_units(self, g_value_get_int(value));
+ break;
+ case PROP_SOURCE_CURRENCY:
+ math_equation_set_source_currency(self, g_value_get_string(value));
+ break;
+ case PROP_TARGET_CURRENCY:
+ math_equation_set_target_currency(self, g_value_get_string(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+math_equation_get_property(GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MathEquation *self;
+ gchar *text;
+
+ self = MATH_EQUATION (object);
+
+ switch (prop_id) {
+ case PROP_STATUS:
+ g_value_set_string(value, self->priv->state.status);
+ break;
+ case PROP_DISPLAY:
+ text = math_equation_get_display(self);
+ g_value_set_string(value, text);
+ g_free(text);
+ break;
+ case PROP_EQUATION:
+ text = math_equation_get_equation(self);
+ g_value_set_string(value, text);
+ g_free(text);
+ break;
+ case PROP_NUMBER_MODE:
+ g_value_set_enum(value, self->priv->number_mode);
+ break;
+ case PROP_ACCURACY:
+ g_value_set_int(value, self->priv->accuracy);
+ break;
+ case PROP_SHOW_THOUSANDS_SEPARATORS:
+ g_value_set_boolean(value, self->priv->show_tsep);
+ break;
+ case PROP_SHOW_TRAILING_ZEROES:
+ g_value_set_boolean(value, self->priv->show_zeroes);
+ break;
+ case PROP_NUMBER_FORMAT:
+ g_value_set_enum(value, self->priv->format);
+ break;
+ case PROP_BASE:
+ g_value_set_int(value, math_equation_get_base(self));
+ break;
+ case PROP_WORD_SIZE:
+ g_value_set_int(value, self->priv->word_size);
+ break;
+ case PROP_ANGLE_UNITS:
+ g_value_set_enum(value, self->priv->angle_units);
+ break;
+ case PROP_SOURCE_CURRENCY:
+ g_value_set_string(value, self->priv->source_currency);
+ break;
+ case PROP_TARGET_CURRENCY:
+ g_value_set_string(value, self->priv->target_currency);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+math_equation_class_init (MathEquationClass *klass)
+{
+ static GEnumValue number_mode_values[] =
+ {
+ {NORMAL, "normal", "normal"},
+ {SUPERSCRIPT, "superscript", "superscript"},
+ {SUBSCRIPT, "subscript", "subscript"},
+ {0, NULL, NULL}
+ };
+ static GEnumValue number_format_values[] =
+ {
+ {FIX, "fixed-point", "fixed-point"},
+ {SCI, "scientific", "scientific"},
+ {ENG, "engineering", "engineering"},
+ {0, NULL, NULL}
+ };
+ static GEnumValue angle_unit_values[] =
+ {
+ {MP_RADIANS, "radians", "radians"},
+ {MP_DEGREES, "degrees", "degrees"},
+ {MP_GRADIANS, "gradians", "gradians"},
+ {0, NULL, NULL}
+ };
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = math_equation_get_property;
+ object_class->set_property = math_equation_set_property;
+
+ g_type_class_add_private (klass, sizeof (MathEquationPrivate));
+
+ number_mode_type = g_enum_register_static("NumberMode", number_mode_values);
+ number_format_type = g_enum_register_static("DisplayFormat", number_format_values);
+ angle_unit_type = g_enum_register_static("AngleUnit", angle_unit_values);
+
+ g_object_class_install_property(object_class,
+ PROP_STATUS,
+ g_param_spec_string("status",
+ "status",
+ "Equation status",
+ "",
+ G_PARAM_READWRITE));
+ g_object_class_install_property(object_class,
+ PROP_DISPLAY,
+ g_param_spec_string("display",
+ "display",
+ "Displayed equation text",
+ "",
+ G_PARAM_READWRITE));
+ g_object_class_install_property(object_class,
+ PROP_EQUATION,
+ g_param_spec_string("equation",
+ "equation",
+ "Equation text",
+ "",
+ G_PARAM_READABLE));
+ g_object_class_install_property(object_class,
+ PROP_NUMBER_MODE,
+ g_param_spec_enum("number-mode",
+ "number-mode",
+ "Input number mode",
+ number_mode_type,
+ NORMAL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property(object_class,
+ PROP_ACCURACY,
+ g_param_spec_int("accuracy",
+ "accuracy",
+ "Display accuracy",
+ 0, 20, 9,
+ G_PARAM_READWRITE));
+ 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",
+ number_format_type,
+ FIX,
+ G_PARAM_READWRITE));
+ g_object_class_install_property(object_class,
+ PROP_BASE,
+ g_param_spec_int("base",
+ "base",
+ "Default number base (derived from number-format)",
+ 2, 16, 10,
+ G_PARAM_READWRITE));
+ g_object_class_install_property(object_class,
+ PROP_WORD_SIZE,
+ g_param_spec_int("word-size",
+ "word-size",
+ "Word size in bits",
+ 8, 64, 64,
+ G_PARAM_READWRITE));
+ g_object_class_install_property(object_class,
+ PROP_ANGLE_UNITS,
+ g_param_spec_enum("angle-units",
+ "angle-units",
+ "Angle units",
+ angle_unit_type,
+ MP_DEGREES,
+ G_PARAM_READWRITE));
+ g_object_class_install_property(object_class,
+ PROP_SOURCE_CURRENCY,
+ g_param_spec_string("source-currency",
+ "source-currency",
+ "Source Currency",
+ "",
+ G_PARAM_READWRITE));
+ g_object_class_install_property(object_class,
+ PROP_TARGET_CURRENCY,
+ g_param_spec_string("target-currency",
+ "target-currency",
+ "target Currency",
+ "",
+ G_PARAM_READWRITE));
+}
+
+
+static void
+pre_insert_text_cb (MathEquation *equation,
+ GtkTextIter *location,
+ gchar *text,
+ gint len,
+ gpointer user_data)
+{
+ gunichar c;
+
+ if (equation->priv->in_reformat)
+ return;
+
+ /* If following a delete then have already pushed undo stack (GtkTextBuffer
+ doesn't indicate replace operations so we have to infer them) */
+ if (!equation->priv->in_delete)
+ math_equation_push_undo_stack(equation);
+
+ /* Clear result on next digit entered if cursor at end of line */
+ // FIXME Cursor
+ c = g_utf8_get_char(text);
+ if (g_unichar_isdigit(c) && math_equation_is_result(equation)) {
+ gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1);
+ clear_ans(equation, FALSE);
+ gtk_text_buffer_get_end_iter(GTK_TEXT_BUFFER(equation), location);
+ }
+
+ if (equation->priv->ans_start) {
+ gint ans_start, ans_end;
+ gint offset;
+
+ offset = gtk_text_iter_get_offset(location);
+ get_ans_offsets(equation, &ans_start, &ans_end);
+
+ /* Inserted inside ans */
+ if (offset > ans_start && offset < ans_end)
+ clear_ans(equation, TRUE);
+ }
+}
+
+
+static gboolean
+on_delete(MathEquation *equation)
+{
+ equation->priv->in_delete = FALSE;
+ return FALSE;
+}
+
+
+static void
+pre_delete_range_cb (MathEquation *equation,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ gpointer user_data)
+{
+ if (equation->priv->in_reformat)
+ return;
+
+ math_equation_push_undo_stack(equation);
+
+ equation->priv->in_delete = TRUE;
+ g_idle_add((GSourceFunc)on_delete, equation);
+
+ if (equation->priv->ans_start) {
+ gint ans_start, ans_end;
+ gint start_offset, end_offset;
+
+ start_offset = gtk_text_iter_get_offset(start);
+ end_offset = gtk_text_iter_get_offset(end);
+ get_ans_offsets(equation, &ans_start, &ans_end);
+
+ /* Deleted part of ans */
+ if (start_offset < ans_end && end_offset > ans_start)
+ clear_ans(equation, TRUE);
+ }
+}
+
+
+static void
+insert_text_cb (MathEquation *equation,
+ GtkTextIter *location,
+ gchar *text,
+ gint len,
+ gpointer user_data)
+{
+ if (equation->priv->in_reformat)
+ return;
+
+ equation->priv->state.entered_multiply = strcmp(text, "×") == 0;
+ g_object_notify(G_OBJECT(equation), "display");
+}
+
+
+static void
+delete_range_cb (MathEquation *equation,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ gpointer user_data)
+{
+ if (equation->priv->in_reformat)
+ return;
+
+ // FIXME: A replace will emit this both for delete-range and insert-text, can it be avoided?
+ g_object_notify(G_OBJECT(equation), "display");
+}
+
+
+static void
+math_equation_init(MathEquation *equation)
+{
+ /* Digits localized for the given language */
+ const char *digit_values = _("0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F");
+ const char *default_digits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};
+ gchar *radix, *tsep;
+ gchar **digits;
+ gboolean use_default_digits = FALSE;
+ int i;
+
+ equation->priv = G_TYPE_INSTANCE_GET_PRIVATE (equation, math_equation_get_type(), MathEquationPrivate);
+
+ // FIXME: Causes error
+ // (process:18573): Gtk-CRITICAL **: set_table: assertion buffer->tag_table == NULL' failed
+ equation->priv->ans_tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(equation), NULL, "weight", PANGO_WEIGHT_BOLD, NULL);
+
+ g_signal_connect(equation, "insert-text", G_CALLBACK(pre_insert_text_cb), equation);
+ g_signal_connect(equation, "delete-range", G_CALLBACK(pre_delete_range_cb), equation);
+ g_signal_connect_after(equation, "insert-text", G_CALLBACK(insert_text_cb), equation);
+ g_signal_connect_after(equation, "delete-range", G_CALLBACK(delete_range_cb), equation);
+
+ digits = g_strsplit(digit_values, ",", -1);
+ for (i = 0; i < 16; i++) {
+ if (use_default_digits || digits[i] == NULL) {
+ use_default_digits = TRUE;
+ equation->priv->digits[i] = strdup(default_digits[i]);
+ }
+ else
+ equation->priv->digits[i] = strdup(digits[i]);
+ }
+ g_strfreev(digits);
+
+ setlocale(LC_NUMERIC, "");
+
+ radix = nl_langinfo(RADIXCHAR);
+ equation->priv->radix = radix ? g_locale_to_utf8(radix, -1, NULL, NULL, NULL) : g_strdup(".");
+ tsep = nl_langinfo(THOUSEP);
+ equation->priv->tsep = tsep ? g_locale_to_utf8(tsep, -1, NULL, NULL, NULL) : g_strdup(",");
+
+ equation->priv->tsep_count = 3;
+
+ equation->priv->variables = math_variables_new();
+
+ equation->priv->state.status = g_strdup("");
+ equation->priv->show_zeroes = FALSE;
+ equation->priv->show_tsep = FALSE;
+ equation->priv->format = FIX;
+ equation->priv->accuracy = 9;
+ equation->priv->word_size = 32;
+ equation->priv->angle_units = MP_DEGREES;
+ // FIXME: Pick based on locale
+ equation->priv->source_currency = g_strdup(currency_names[0].short_name);
+ equation->priv->target_currency = g_strdup(currency_names[0].short_name);
+ equation->priv->base = 10;
+
+ mp_set_from_integer(0, &equation->priv->state.ans);
+}
diff --git a/src/math-equation.h b/src/math-equation.h
new file mode 100644
index 0000000..e93137d
--- /dev/null
+++ b/src/math-equation.h
@@ -0,0 +1,133 @@
+/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef MATH_EQUATION_H
+#define MATH_EQUATION_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include "mp.h"
+#include "math-variables.h"
+
+G_BEGIN_DECLS
+
+#define MATH_EQUATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), math_equation_get_type(), MathEquation))
+
+typedef struct MathEquationPrivate MathEquationPrivate;
+
+typedef struct
+{
+ GtkTextBuffer parent_instance;
+ MathEquationPrivate *priv;
+} MathEquation;
+
+typedef struct
+{
+ GtkTextBufferClass parent_class;
+} MathEquationClass;
+
+/* Number display mode. */
+typedef enum {
+ FIX,
+ SCI,
+ ENG
+} DisplayFormat;
+
+typedef enum {
+ NORMAL,
+ SUPERSCRIPT,
+ SUBSCRIPT
+} NumberMode;
+
+GType math_equation_get_type(void);
+MathEquation *math_equation_new(void);
+
+MathVariables *math_equation_get_variables(MathEquation *equation);
+
+const gchar *math_equation_get_digit_text(MathEquation *equation, guint digit);
+const gchar *math_equation_get_numeric_point_text(MathEquation *equation);
+const gchar *math_equation_get_thousands_separator_text(MathEquation *equation);
+
+void math_equation_set_status(MathEquation *equation, const gchar *status);
+const gchar *math_equation_get_status(MathEquation *equation);
+
+gboolean math_equation_is_empty(MathEquation *equation);
+gboolean math_equation_is_result(MathEquation *equation);
+gchar *math_equation_get_display(MathEquation *equation);
+gchar *math_equation_get_equation(MathEquation *equation);
+gboolean math_equation_get_number(MathEquation *equation, MPNumber *z);
+
+void math_equation_set_number_mode(MathEquation *equation, NumberMode mode);
+NumberMode math_equation_get_number_mode(MathEquation *equation);
+
+void math_equation_set_accuracy(MathEquation *equation, gint accuracy);
+gint math_equation_get_accuracy(MathEquation *equation);
+
+void math_equation_set_show_thousands_separators(MathEquation *equation, gboolean visible);
+gboolean math_equation_get_show_thousands_separators(MathEquation *equation);
+
+void math_equation_set_show_trailing_zeroes(MathEquation *equation, gboolean visible);
+gboolean math_equation_get_show_trailing_zeroes(MathEquation *equation);
+
+void math_equation_set_number_format(MathEquation *equation, DisplayFormat format);
+DisplayFormat math_equation_get_number_format(MathEquation *equation);
+
+void math_equation_set_base(MathEquation *equation, gint base);
+gint math_equation_get_base(MathEquation *equation);
+
+void math_equation_set_word_size(MathEquation *equation, gint word_size);
+gint math_equation_get_word_size(MathEquation *equation);
+
+void math_equation_set_angle_units(MathEquation *equation, MPAngleUnit angle_unit);
+MPAngleUnit math_equation_get_angle_units(MathEquation *equation);
+
+void math_equation_set_source_currency(MathEquation *equation, const gchar *currency);
+const gchar *math_equation_get_source_currency(MathEquation *equation);
+
+void math_equation_set_target_currency(MathEquation *equation, const gchar *currency);
+const gchar *math_equation_get_target_currency(MathEquation *equation);
+
+const MPNumber *math_equation_get_answer(MathEquation *equation);
+
+void math_equation_copy(MathEquation *equation);
+void math_equation_paste(MathEquation *equation);
+void math_equation_undo(MathEquation *equation);
+void math_equation_redo(MathEquation *equation);
+void math_equation_store(MathEquation *equation, const gchar *name);
+void math_equation_recall(MathEquation *equation, const gchar *name);
+void math_equation_set(MathEquation *equation, const gchar *text);
+void math_equation_set_number(MathEquation *equation, const MPNumber *x);
+void math_equation_insert(MathEquation *equation, const gchar *text);
+void math_equation_insert_digit(MathEquation *equation, guint digit);
+void math_equation_insert_numeric_point(MathEquation *equation);
+void math_equation_insert_number(MathEquation *equation, const MPNumber *x);
+void math_equation_insert_subtract(MathEquation *equation);
+void math_equation_insert_exponent(MathEquation *equation);
+void math_equation_solve(MathEquation *equation);
+void math_equation_factorize(MathEquation *equation);
+void math_equation_delete(MathEquation *equation);
+void math_equation_backspace(MathEquation *equation);
+void math_equation_clear(MathEquation *equation);
+void math_equation_shift(MathEquation *equation, gint count);
+void math_equation_toggle_bit(MathEquation *equation, guint bit);
+
+//FIXME: Obsolete
+void display_make_number(MathEquation *equation, char *target, int target_len, const MPNumber *x);
+
+#endif /* MATH_EQUATION_H */
diff --git a/src/math-preferences.c b/src/math-preferences.c
new file mode 100644
index 0000000..c846523
--- /dev/null
+++ b/src/math-preferences.c
@@ -0,0 +1,401 @@
+/* Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "math-preferences.h"
+
+G_DEFINE_TYPE (MathPreferencesDialog, math_preferences, GTK_TYPE_DIALOG);
+
+enum {
+ PROP_0,
+ PROP_EQUATION
+};
+
+struct MathPreferencesDialogPrivate
+{
+ MathEquation *equation;
+ GtkBuilder *ui;
+};
+
+#define UI_DIALOGS_FILE UI_DIR "/preferences.ui"
+#define GET_WIDGET(ui, name) \
+ GTK_WIDGET(gtk_builder_get_object(ui, name))
+
+
+MathPreferencesDialog *
+math_preferences_dialog_new(MathEquation *equation)
+{
+ return g_object_new (math_preferences_get_type(), "equation", equation, NULL);
+}
+
+
+static void
+preferences_response_cb(GtkWidget *widget, gint id)
+{
+ gtk_widget_hide(widget);
+}
+
+
+static gboolean
+preferences_dialog_delete_cb(GtkWidget *widget, GdkEvent *event)
+{
+ preferences_response_cb(widget, 0);
+ return TRUE;
+}
+
+
+void number_format_combobox_changed_cb(GtkWidget *combo, MathPreferencesDialog *dialog);
+G_MODULE_EXPORT
+void
+number_format_combobox_changed_cb(GtkWidget *combo, MathPreferencesDialog *dialog)
+{
+ DisplayFormat value;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
+ gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter);
+ gtk_tree_model_get(model, &iter, 1, &value, -1);
+ math_equation_set_number_format(dialog->priv->equation, value);
+}
+
+
+void angle_unit_combobox_changed_cb(GtkWidget *combo, MathPreferencesDialog *dialog);
+G_MODULE_EXPORT
+void
+angle_unit_combobox_changed_cb(GtkWidget *combo, MathPreferencesDialog *dialog)
+{
+ MPAngleUnit value;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
+ gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter);
+ gtk_tree_model_get(model, &iter, 1, &value, -1);
+ math_equation_set_angle_units(dialog->priv->equation, value);
+}
+
+
+void word_size_combobox_changed_cb(GtkWidget *combo, MathPreferencesDialog *dialog);
+G_MODULE_EXPORT
+void
+word_size_combobox_changed_cb(GtkWidget *combo, MathPreferencesDialog *dialog)
+{
+ gint value;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
+ gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter);
+ gtk_tree_model_get(model, &iter, 1, &value, -1);
+ math_equation_set_word_size(dialog->priv->equation, value);
+}
+
+
+void decimal_places_spin_change_value_cb(GtkWidget *spin, MathPreferencesDialog *dialog);
+G_MODULE_EXPORT
+void
+decimal_places_spin_change_value_cb(GtkWidget *spin, MathPreferencesDialog *dialog)
+{
+ gint value = 0;
+
+ value = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
+ math_equation_set_accuracy(dialog->priv->equation, value);
+}
+
+
+void thousands_separator_check_toggled_cb(GtkWidget *check, MathPreferencesDialog *dialog);
+G_MODULE_EXPORT
+void
+thousands_separator_check_toggled_cb(GtkWidget *check, MathPreferencesDialog *dialog)
+{
+ gboolean value;
+
+ value = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check));
+ math_equation_set_show_thousands_separators(dialog->priv->equation, value);
+}
+
+
+void trailing_zeroes_check_toggled_cb(GtkWidget *check, MathPreferencesDialog *dialog);
+G_MODULE_EXPORT
+void
+trailing_zeroes_check_toggled_cb(GtkWidget *check, MathPreferencesDialog *dialog)
+{
+ gboolean value;
+
+ value = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check));
+ math_equation_set_show_trailing_zeroes(dialog->priv->equation, value);
+}
+
+
+static void
+set_combo_box_from_int(GtkWidget *combo, int value)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean valid;
+
+ model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
+ valid = gtk_tree_model_get_iter_first(model, &iter);
+
+ while (valid) {
+ gint v;
+
+ gtk_tree_model_get(model, &iter, 1, &v, -1);
+ if (v == value)
+ break;
+ valid = gtk_tree_model_iter_next(model, &iter);
+ }
+ if (!valid)
+ valid = gtk_tree_model_get_iter_first(model, &iter);
+
+ gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo), &iter);
+}
+
+
+static void
+accuracy_cb(MathEquation *equation, GParamSpec *spec, MathPreferencesDialog *dialog)
+{
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(gtk_builder_get_object(dialog->priv->ui, "decimal_places_spin")),
+ math_equation_get_accuracy(equation));
+}
+
+
+static void
+show_thousands_separators_cb(MathEquation *equation, GParamSpec *spec, MathPreferencesDialog *dialog)
+{
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(dialog->priv->ui, "thousands_separator_check")),
+ math_equation_get_show_thousands_separators(equation));
+}
+
+
+static void
+show_trailing_zeroes_cb(MathEquation *equation, GParamSpec *spec, MathPreferencesDialog *dialog)
+{
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(dialog->priv->ui, "trailing_zeroes_check")),
+ math_equation_get_show_trailing_zeroes(equation));
+}
+
+
+static void
+number_format_cb(MathEquation *equation, GParamSpec *spec, MathPreferencesDialog *dialog)
+{
+ set_combo_box_from_int(GET_WIDGET(dialog->priv->ui, "number_format_combobox"), math_equation_get_number_format(equation));
+}
+
+
+static void
+word_size_cb(MathEquation *equation, GParamSpec *spec, MathPreferencesDialog *dialog)
+{
+ set_combo_box_from_int(GET_WIDGET(dialog->priv->ui, "word_size_combobox"), math_equation_get_word_size(equation));
+}
+
+
+static void
+angle_unit_cb(MathEquation *equation, GParamSpec *spec, MathPreferencesDialog *dialog)
+{
+ set_combo_box_from_int(GET_WIDGET(dialog->priv->ui, "angle_unit_combobox"), math_equation_get_angle_units(equation));
+}
+
+
+static void
+create_gui(MathPreferencesDialog *dialog)
+{
+ GtkWidget *widget;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GtkCellRenderer *renderer;
+ gchar *string, **tokens;
+ GError *error = NULL;
+ static gchar *objects[] = { "preferences_table", "angle_unit_model", "number_format_model",
+ "word_size_model", "decimal_places_adjustment", "number_base_model", NULL };
+
+ // FIXME: Handle errors
+ dialog->priv->ui = gtk_builder_new();
+ gtk_builder_add_objects_from_file(dialog->priv->ui, UI_DIALOGS_FILE, objects, &error);
+ if (error)
+ g_warning("Error loading preferences UI: %s", error->message);
+ g_clear_error(&error);
+
+ gtk_window_set_title(GTK_WINDOW(dialog),
+ /* Title of preferences dialog */
+ _("Preferences"));
+ gtk_window_set_icon_name(GTK_WINDOW(dialog), "accessories-calculator");
+ gtk_container_set_border_width(GTK_CONTAINER(dialog), 8);
+ gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
+ gtk_dialog_add_button(GTK_DIALOG(dialog),
+ /* Label on close button in preferences dialog */
+ _("_Close"), 0);
+ g_signal_connect(dialog, "response", G_CALLBACK(preferences_response_cb), NULL);
+ g_signal_connect(dialog, "delete-event", G_CALLBACK(preferences_dialog_delete_cb), NULL);
+ gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), GET_WIDGET(dialog->priv->ui, "preferences_table"), TRUE, TRUE, 0);
+
+ widget = GET_WIDGET(dialog->priv->ui, "angle_unit_combobox");
+ model = gtk_combo_box_get_model(GTK_COMBO_BOX(widget));
+ gtk_list_store_append(GTK_LIST_STORE(model), &iter);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0,
+ /* Preferences dialog: Angle unit combo box: Use degrees for trigonometric calculations */
+ _("Degrees"), 1, MP_DEGREES, -1);
+ gtk_list_store_append(GTK_LIST_STORE(model), &iter);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0,
+ /* Preferences dialog: Angle unit combo box: Use radians for trigonometric calculations */
+ _("Radians"), 1, MP_RADIANS, -1);
+ gtk_list_store_append(GTK_LIST_STORE(model), &iter);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0,
+ /* Preferences dialog: Angle unit combo box: Use gradians for trigonometric calculations */
+ _("Gradians"), 1, MP_GRADIANS, -1);
+ renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(widget), renderer, TRUE);
+ gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(widget), renderer, "text", 0);
+
+ widget = GET_WIDGET(dialog->priv->ui, "number_format_combobox");
+ model = gtk_combo_box_get_model(GTK_COMBO_BOX(widget));
+ gtk_list_store_append(GTK_LIST_STORE(model), &iter);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0,
+ /* Number display mode combo: Fixed, e.g. 1234 */
+ _("Fixed"), 1, FIX, -1);
+ gtk_list_store_append(GTK_LIST_STORE(model), &iter);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0,
+ /* Number display mode combo: Scientific, e.g. 1.234×10^3 */
+ _("Scientific"), 1, SCI, -1);
+ gtk_list_store_append(GTK_LIST_STORE(model), &iter);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0,
+ /* Number display mode combo: Engineering, e.g. 1.234k */
+ _("Engineering"), 1, ENG, -1);
+ renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(widget), renderer, TRUE);
+ gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(widget), renderer, "text", 0);
+
+ widget = GET_WIDGET(dialog->priv->ui, "word_size_combobox");
+ renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(widget), renderer, TRUE);
+ gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(widget), renderer, "text", 0);
+
+ /* Label used in preferences dialog. The %d is replaced by a spinbutton */
+ string = _("Show %d decimal _places");
+ tokens = g_strsplit(string, "%d", 2);
+ widget = GET_WIDGET(dialog->priv->ui, "decimal_places_label1");
+ if (tokens[0])
+ string = g_strstrip(tokens[0]);
+ else
+ string = "";
+ if (string[0] != '\0')
+ gtk_label_set_text_with_mnemonic(GTK_LABEL(widget), string);
+ else
+ gtk_widget_hide(widget);
+
+ widget = GET_WIDGET(dialog->priv->ui, "decimal_places_label2");
+ if (tokens[0] && tokens[1])
+ string = g_strstrip(tokens[1]);
+ else
+ string = "";
+ if (string[0] != '\0')
+ gtk_label_set_text_with_mnemonic(GTK_LABEL(widget), string);
+ else
+ gtk_widget_hide(widget);
+
+ g_strfreev(tokens);
+
+ gtk_builder_connect_signals(dialog->priv->ui, dialog);
+
+ g_signal_connect(dialog->priv->equation, "notify::accuracy", G_CALLBACK(accuracy_cb), dialog);
+ g_signal_connect(dialog->priv->equation, "notify::show-thousands-separators", G_CALLBACK(show_thousands_separators_cb), dialog);
+ g_signal_connect(dialog->priv->equation, "notify::show-trailing_zeroes", G_CALLBACK(show_trailing_zeroes_cb), dialog);
+ g_signal_connect(dialog->priv->equation, "notify::number-format", G_CALLBACK(number_format_cb), dialog);
+ g_signal_connect(dialog->priv->equation, "notify::word-size", G_CALLBACK(word_size_cb), dialog);
+ g_signal_connect(dialog->priv->equation, "notify::angle-units", G_CALLBACK(angle_unit_cb), dialog);
+
+ accuracy_cb(dialog->priv->equation, NULL, dialog);
+ show_thousands_separators_cb(dialog->priv->equation, NULL, dialog);
+ show_trailing_zeroes_cb(dialog->priv->equation, NULL, dialog);
+ number_format_cb(dialog->priv->equation, NULL, dialog);
+ word_size_cb(dialog->priv->equation, NULL, dialog);
+ angle_unit_cb(dialog->priv->equation, NULL, dialog);
+}
+
+
+static void
+math_preferences_set_property(GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MathPreferencesDialog *self;
+
+ self = MATH_PREFERENCES (object);
+
+ switch (prop_id) {
+ case PROP_EQUATION:
+ self->priv->equation = g_value_get_object (value);
+ create_gui(self);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+math_preferences_get_property(GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MathPreferencesDialog *self;
+
+ self = MATH_PREFERENCES (object);
+
+ switch (prop_id) {
+ case PROP_EQUATION:
+ g_value_set_object (value, self->priv->equation);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+math_preferences_class_init (MathPreferencesDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = math_preferences_get_property;
+ object_class->set_property = math_preferences_set_property;
+
+ g_type_class_add_private (klass, sizeof (MathPreferencesDialogPrivate));
+
+ g_object_class_install_property(object_class,
+ PROP_EQUATION,
+ g_param_spec_object("equation",
+ "equation",
+ "Equation being configured",
+ math_equation_get_type(),
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+
+static void
+math_preferences_init(MathPreferencesDialog *dialog)
+{
+ dialog->priv = G_TYPE_INSTANCE_GET_PRIVATE (dialog, math_preferences_get_type(), MathPreferencesDialogPrivate);
+}
diff --git a/src/math-preferences.h b/src/math-preferences.h
new file mode 100644
index 0000000..487a68e
--- /dev/null
+++ b/src/math-preferences.h
@@ -0,0 +1,47 @@
+/* Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef MATH_PREFERENCES_H
+#define MATH_PREFERENCES_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include "math-equation.h"
+
+G_BEGIN_DECLS
+
+#define MATH_PREFERENCES(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), math_preferences_get_type(), MathPreferencesDialog))
+
+typedef struct MathPreferencesDialogPrivate MathPreferencesDialogPrivate;
+
+typedef struct
+{
+ GtkDialog parent_instance;
+ MathPreferencesDialogPrivate *priv;
+} MathPreferencesDialog;
+
+typedef struct
+{
+ GtkDialogClass parent_class;
+} MathPreferencesDialogClass;
+
+GType math_preferences_get_type(void);
+
+MathPreferencesDialog *math_preferences_dialog_new(MathEquation *equation);
+
+#endif /* MATH_PREFERENCES_H */
diff --git a/src/math-variables.c b/src/math-variables.c
new file mode 100644
index 0000000..b4f7720
--- /dev/null
+++ b/src/math-variables.c
@@ -0,0 +1,163 @@
+/* Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "math-variables.h"
+
+
+struct MathVariablesPrivate
+{
+ gchar *file_name;
+ GHashTable *registers;
+};
+
+G_DEFINE_TYPE (MathVariables, math_variables, G_TYPE_OBJECT);
+
+
+MathVariables *
+math_variables_new()
+{
+ return g_object_new (math_variables_get_type(), NULL);
+}
+
+
+static void
+registers_load(MathVariables *variables)
+{
+ FILE *f;
+ char line[1024];
+
+ f = fopen(variables->priv->file_name, "r");
+ if (!f)
+ return;
+
+ g_hash_table_remove_all(variables->priv->registers);
+
+ while (fgets(line, 1024, f) != NULL)
+ {
+ char *name, *value;
+ MPNumber *t;
+
+ value = strchr(line, '=');
+ if (!value)
+ continue;
+ *value = '\0';
+ value = value + 1;
+
+ name = g_strstrip(line);
+ value = g_strstrip(value);
+
+ t = g_malloc(sizeof(MPNumber));
+ if (mp_set_from_string(value, 10, t) == 0)
+ g_hash_table_insert(variables->priv->registers, g_strdup(name), t);
+ else
+ g_free(t);
+ }
+ fclose(f);
+}
+
+
+static void
+registers_save(MathVariables *variables)
+{
+ gchar *dir;
+ FILE *f;
+ GHashTableIter iter;
+ gpointer key, val;
+
+ dir = g_path_get_dirname(variables->priv->file_name);
+ g_mkdir_with_parents(dir, 0700);
+ g_free(dir);
+
+ f = fopen(variables->priv->file_name, "w");
+ if (!f)
+ return;
+
+ g_hash_table_iter_init(&iter, variables->priv->registers);
+ while (g_hash_table_iter_next(&iter, &key, &val))
+ {
+ gchar *name = key;
+ MPNumber *value = val;
+ char number[1024];
+
+ mp_cast_to_string(value, 10, 10, 50, TRUE, number, 1024);
+ fprintf(f, "%s=%s\n", name, number);
+ }
+ fclose(f);
+}
+
+
+// FIXME: Sort
+gchar **
+math_variables_get_names(MathVariables *variables)
+{
+ GHashTableIter iter;
+ gpointer key;
+ gint i = 0;
+ gchar **names;
+
+ names = g_malloc0(sizeof(gchar *) * (g_hash_table_size(variables->priv->registers) + 1));
+
+ g_hash_table_iter_init(&iter, variables->priv->registers);
+ while (g_hash_table_iter_next(&iter, &key, NULL))
+ {
+ gchar *name = key;
+ names[i] = g_strdup(name);
+ i++;
+ }
+ names[i] = NULL;
+
+ return names;
+}
+
+
+void
+math_variables_set_value(MathVariables *variables, const char *name, const MPNumber *value)
+{
+ MPNumber *t;
+ t = g_malloc(sizeof(MPNumber));
+ mp_set_from_mp(value, t);
+ g_hash_table_insert(variables->priv->registers, g_strdup(name), t);
+ registers_save(variables);
+}
+
+
+MPNumber *
+math_variables_get_value(MathVariables *variables, const char *name)
+{
+ return g_hash_table_lookup(variables->priv->registers, name);
+}
+
+
+static void
+math_variables_class_init (MathVariablesClass *klass)
+{
+ g_type_class_add_private (klass, sizeof (MathVariablesPrivate));
+}
+
+
+static void
+math_variables_init(MathVariables *variables)
+{
+ variables->priv = G_TYPE_INSTANCE_GET_PRIVATE (variables, math_variables_get_type(), MathVariablesPrivate);
+ variables->priv->registers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+ variables->priv->file_name = g_build_filename(g_get_user_data_dir(), "gcalctool", "registers", NULL);
+ registers_load(variables);
+}
diff --git a/src/math-variables.h b/src/math-variables.h
new file mode 100644
index 0000000..b2317b0
--- /dev/null
+++ b/src/math-variables.h
@@ -0,0 +1,52 @@
+/* Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef MATH_VARIABLES_H
+#define MATH_VARIABLES_H
+
+#include <glib-object.h>
+#include "mp.h"
+
+G_BEGIN_DECLS
+
+#define MATH_VARIABLES(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), math_equation_get_type(), MathVariables))
+
+typedef struct MathVariablesPrivate MathVariablesPrivate;
+
+typedef struct
+{
+ GObject parent_instance;
+ MathVariablesPrivate *priv;
+} MathVariables;
+
+typedef struct
+{
+ GObjectClass parent_class;
+} MathVariablesClass;
+
+GType math_variables_get_type(void);
+
+MathVariables *math_variables_new(void);
+
+gchar **math_variables_get_names(MathVariables *variables);
+
+void math_variables_set_value(MathVariables *variables, const char *name, const MPNumber *value);
+
+MPNumber *math_variables_get_value(MathVariables *variables, const char *name);
+
+#endif /* MATH_VARIABLES_H */
diff --git a/src/math-window.c b/src/math-window.c
new file mode 100644
index 0000000..0daf030
--- /dev/null
+++ b/src/math-window.c
@@ -0,0 +1,527 @@
+/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "math-window.h"
+#include "math-preferences.h"
+
+enum {
+ PROP_0,
+ PROP_EQUATION
+};
+
+struct MathWindowPrivate
+{
+ GtkWidget *menu_bar;
+ MathEquation *equation;
+ MathDisplay *display;
+ MathButtons *buttons;
+ MathPreferencesDialog *preferences_dialog;
+ gboolean right_aligned;
+ GtkWidget *mode_basic_menu_item, *mode_advanced_menu_item, *mode_financial_menu_item, *mode_programming_menu_item;
+};
+
+G_DEFINE_TYPE (MathWindow, math_window, GTK_TYPE_WINDOW);
+
+enum {
+ QUIT,
+ LAST_SIGNAL
+};
+static guint signals[LAST_SIGNAL] = { 0, };
+
+
+MathWindow *
+math_window_new(MathEquation *equation)
+{
+ return g_object_new (math_window_get_type(), "equation", equation, NULL);
+}
+
+
+GtkWidget *
+math_window_get_menu_bar(MathWindow *window)
+{
+ return window->priv->menu_bar;
+}
+
+
+MathEquation *math_window_get_equation(MathWindow *window)
+{
+ return window->priv->equation;
+}
+
+
+MathDisplay *
+math_window_get_display(MathWindow *window)
+{
+ return window->priv->display;
+}
+
+
+MathButtons *
+math_window_get_buttons(MathWindow *window)
+{
+ return window->priv->buttons;
+}
+
+
+void
+math_window_critical_error(MathWindow *window, const gchar *title, const gchar *contents)
+{
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new(NULL, 0,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_NONE,
+ "%s", title);
+ gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
+ "%s", contents);
+ gtk_dialog_add_buttons(GTK_DIALOG(dialog), GTK_STOCK_QUIT, GTK_RESPONSE_ACCEPT, NULL);
+
+ gtk_dialog_run(GTK_DIALOG(dialog));
+
+ g_signal_emit(window, signals[QUIT], 0);
+}
+
+
+static void
+copy_cb(GtkWidget *widget, MathWindow *window)
+{
+ math_equation_copy (window->priv->equation);
+}
+
+
+static void
+paste_cb(GtkWidget *widget, MathWindow *window)
+{
+ math_equation_paste (window->priv->equation);
+}
+
+
+static void
+undo_cb(GtkWidget *widget, MathWindow *window)
+{
+ math_equation_undo(window->priv->equation);
+}
+
+
+static void
+redo_cb(GtkWidget *widget, MathWindow *window)
+{
+ math_equation_redo(window->priv->equation);
+}
+
+
+static void
+mode_changed_cb(GtkWidget *menu, MathWindow *window)
+{
+ int mode;
+
+ if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu)))
+ return;
+
+ mode = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu), "calcmode"));
+ math_buttons_set_mode(window->priv->buttons, mode);
+}
+
+
+static void
+show_preferences_cb(GtkMenuItem *menu, MathWindow *window)
+{
+ if (!window->priv->preferences_dialog) {
+ window->priv->preferences_dialog = math_preferences_dialog_new(window->priv->equation);
+ gtk_window_set_transient_for(GTK_WINDOW(window->priv->preferences_dialog), GTK_WINDOW(window));
+ }
+ gtk_window_present(GTK_WINDOW(window->priv->preferences_dialog));
+}
+
+
+static void
+help_cb(GtkWidget *widget, MathWindow *window)
+{
+ GdkScreen *screen;
+ GError *error = NULL;
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (window));
+ gtk_show_uri (screen, "ghelp:gcalctool", gtk_get_current_event_time (), &error);
+
+ if (error != NULL)
+ {
+ GtkWidget *d;
+ /* Translators: Error message displayed when unable to launch help browser */
+ const char *message = _("Unable to open help file");
+
+ d = gtk_message_dialog_new (GTK_WINDOW (window),
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+ "%s", message);
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (d),
+ "%s", error->message);
+ g_signal_connect (d, "response", G_CALLBACK (gtk_widget_destroy), NULL);
+ gtk_window_present (GTK_WINDOW (d));
+
+ g_error_free (error);
+ }
+}
+
+
+static void
+about_cb(GtkWidget *widget, MathWindow *window)
+{
+ const gchar *authors[] = {
+ "Rich Burridge <[email protected]>",
+ "Robert Ancell <[email protected]>",
+ "Klaus Niederkrüger <[email protected]>",
+ NULL
+ };
+ const gchar *documenters[] = {
+ "Sun Microsystems",
+ NULL
+ };
+
+ /* The translator credits. Please translate this with your name(s). */
+ const gchar *translator_credits = _("translator-credits");
+
+ /* The license this software is under (GPL2+) */
+ char *license = _("Gcalctool is free software; you can redistribute it and/or modify\n"
+ "it under the terms of the GNU General Public License as published by\n"
+ "the Free Software Foundation; either version 2 of the License, or\n"
+ "(at your option) any later version.\n"
+ "\n"
+ "Gcalctool is distributed in the hope that it will be useful,\n"
+ "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
+ "GNU General Public License for more details.\n"
+ "\n"
+ "You should have received a copy of the GNU General Public License\n"
+ "along with Gcalctool; if not, write to the Free Software Foundation, Inc.,\n"
+ "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA");
+
+ gtk_show_about_dialog(GTK_WINDOW(window),
+ "name",
+ /* Program name in the about dialog */
+ _("Gcalctool"),
+ "version", VERSION,
+ "copyright",
+ /* Copyright notice in the about dialog */
+ _("\xc2\xa9 1986–2010 The Gcalctool authors"),
+ "license", license,
+ "comments",
+ /* Short description in the about dialog */
+ _("Calculator with financial and scientific modes."),
+ "authors", authors,
+ "documenters", documenters,
+ "translator_credits", translator_credits,
+ "logo-icon-name", "accessories-calculator",
+ NULL);
+}
+
+
+static void
+quit_cb(GtkWidget *widget, MathWindow *window)
+{
+ g_signal_emit(window, signals[QUIT], 0);
+}
+
+
+static gboolean
+key_press_cb(MathWindow *window, GdkEventKey *event)
+{
+ gboolean result;
+ g_signal_emit_by_name(window->priv->display, "key-press-event", event, &result);
+ return result;
+}
+
+
+static void
+delete_cb(MathWindow *window, GdkEvent *event)
+{
+ g_signal_emit(window, signals[QUIT], 0);
+}
+
+
+static void
+scroll_changed_cb(GtkAdjustment *adjustment, MathWindow *window)
+{
+ if (window->priv->right_aligned)
+ gtk_adjustment_set_value(adjustment, gtk_adjustment_get_upper(adjustment) - gtk_adjustment_get_page_size(adjustment));
+}
+
+
+static void
+scroll_value_changed_cb(GtkAdjustment *adjustment, MathWindow *window)
+{
+ if (gtk_adjustment_get_value(adjustment) == gtk_adjustment_get_upper(adjustment) - gtk_adjustment_get_page_size(adjustment))
+ window->priv->right_aligned = TRUE;
+ else
+ window->priv->right_aligned = FALSE;
+}
+
+
+static void
+button_mode_changed_cb(MathButtons *buttons, GParamSpec *spec, MathWindow *window)
+{
+ GtkWidget *menu;
+
+ switch(math_buttons_get_mode(buttons))
+ {
+ default:
+ case BASIC:
+ menu = window->priv->mode_basic_menu_item;
+ //FIXME: Should it revert to decimal mode? math_equation_set_number_format(window->priv->equation, DEC);
+ break;
+
+ case ADVANCED:
+ menu = window->priv->mode_advanced_menu_item;
+ break;
+
+ case FINANCIAL:
+ menu = window->priv->mode_financial_menu_item;
+ break;
+
+ case PROGRAMMING:
+ menu = window->priv->mode_programming_menu_item;
+ break;
+ }
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu), TRUE);
+}
+
+
+static GtkWidget *
+add_menu(GtkWidget *menu_bar, const gchar *name)
+{
+ GtkWidget *menu_item, *menu;
+
+ menu_item = gtk_menu_item_new_with_mnemonic(name);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), menu_item);
+ gtk_widget_show(menu_item);
+ menu = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), menu);
+
+ return menu;
+}
+
+
+static GtkWidget *
+add_menu_item(GtkWidget *menu, GtkWidget *menu_item, GCallback callback, gpointer callback_data)
+{
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
+ gtk_widget_show(menu_item);
+ if (callback)
+ g_signal_connect(G_OBJECT(menu_item), "activate", callback, callback_data);
+ return menu_item;
+}
+
+
+static GtkWidget *
+radio_menu_item_new(GSList **group, const gchar *name)
+{
+ GtkWidget *menu_item;
+ menu_item = gtk_radio_menu_item_new_with_mnemonic(*group, name);
+ *group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menu_item));
+ return menu_item;
+}
+
+
+static void
+create_menu(MathWindow *window)
+{
+ GtkAccelGroup *accel_group;
+ GtkWidget *menu, *menu_item;
+ GSList *group = NULL;
+
+ accel_group = gtk_accel_group_new();
+ gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
+
+ /* Calculator menu */
+ #define CALCULATOR_MENU_LABEL _("_Calculator")
+ /* Mode menu */
+ #define MODE_MENU_LABEL _("_Mode")
+ /* Help menu label */
+ #define HELP_MENU_LABEL _("_Help")
+ /* Basic menu label */
+ #define MODE_BASIC_LABEL _("_Basic")
+ /* Advanced menu label */
+ #define MODE_ADVANCED_LABEL _("_Advanced")
+ /* Financial menu label */
+ #define MODE_FINANCIAL_LABEL _("_Financial")
+ /* Programming menu label */
+ #define MODE_PROGRAMMING_LABEL _("_Programming")
+ /* Help>Contents menu label */
+ #define HELP_CONTENTS_LABEL _("_Contents")
+
+ menu = add_menu(window->priv->menu_bar, CALCULATOR_MENU_LABEL);
+ add_menu_item(menu, gtk_image_menu_item_new_from_stock(GTK_STOCK_COPY, accel_group), G_CALLBACK(copy_cb), window);
+ add_menu_item(menu, gtk_image_menu_item_new_from_stock(GTK_STOCK_PASTE, accel_group), G_CALLBACK(paste_cb), window);
+ menu_item = add_menu_item(menu, gtk_image_menu_item_new_from_stock(GTK_STOCK_UNDO, accel_group), G_CALLBACK(undo_cb), window);
+ gtk_widget_add_accelerator(menu_item, "activate", accel_group, GDK_z, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
+ menu_item = add_menu_item(menu, gtk_image_menu_item_new_from_stock(GTK_STOCK_REDO, accel_group), G_CALLBACK(redo_cb), window);
+ gtk_widget_add_accelerator(menu_item, "activate", accel_group, GDK_z, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE);
+ add_menu_item(menu, gtk_separator_menu_item_new(), NULL, NULL);
+ add_menu_item(menu, gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES, accel_group), G_CALLBACK(show_preferences_cb), window);
+ add_menu_item(menu, gtk_separator_menu_item_new(), NULL, NULL);
+ menu_item = add_menu_item(menu, gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, accel_group), G_CALLBACK(quit_cb), window);
+ gtk_widget_add_accelerator(menu_item, "activate", accel_group, GDK_w, GDK_CONTROL_MASK, 0);
+
+ menu = add_menu(window->priv->menu_bar, MODE_MENU_LABEL);
+ window->priv->mode_basic_menu_item = add_menu_item(menu, radio_menu_item_new(&group, MODE_BASIC_LABEL), G_CALLBACK(mode_changed_cb), window);
+ g_object_set_data(G_OBJECT(window->priv->mode_basic_menu_item), "calcmode", GINT_TO_POINTER(BASIC));
+ window->priv->mode_advanced_menu_item = add_menu_item(menu, radio_menu_item_new(&group, MODE_ADVANCED_LABEL), G_CALLBACK(mode_changed_cb), window);
+ g_object_set_data(G_OBJECT(window->priv->mode_advanced_menu_item), "calcmode", GINT_TO_POINTER(ADVANCED));
+ window->priv->mode_financial_menu_item = add_menu_item(menu, radio_menu_item_new(&group, MODE_FINANCIAL_LABEL), G_CALLBACK(mode_changed_cb), window);
+ g_object_set_data(G_OBJECT(window->priv->mode_financial_menu_item), "calcmode", GINT_TO_POINTER(FINANCIAL));
+ window->priv->mode_programming_menu_item = add_menu_item(menu, radio_menu_item_new(&group, MODE_PROGRAMMING_LABEL), G_CALLBACK(mode_changed_cb), window);
+ g_object_set_data(G_OBJECT(window->priv->mode_programming_menu_item), "calcmode", GINT_TO_POINTER(PROGRAMMING));
+
+ menu = add_menu(window->priv->menu_bar, HELP_MENU_LABEL);
+ menu_item = add_menu_item(menu, gtk_menu_item_new_with_mnemonic(HELP_CONTENTS_LABEL), G_CALLBACK(help_cb), window);
+ gtk_widget_add_accelerator(menu_item, "activate", accel_group, GDK_F1, 0, GTK_ACCEL_VISIBLE);
+ add_menu_item(menu, gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT, accel_group), G_CALLBACK(about_cb), window);
+}
+
+
+static void
+create_gui(MathWindow *window)
+{
+ GtkWidget *main_vbox, *vbox;
+ GtkWidget *scrolled_window;
+
+ main_vbox = gtk_vbox_new(FALSE, 0);
+ gtk_container_add(GTK_CONTAINER(window), main_vbox);
+ gtk_widget_show(main_vbox);
+
+ window->priv->menu_bar = gtk_menu_bar_new();
+ gtk_box_pack_start(GTK_BOX(main_vbox), window->priv->menu_bar, TRUE, TRUE, 0);
+ gtk_widget_show(window->priv->menu_bar);
+
+ create_menu(window);
+
+ vbox = gtk_vbox_new(FALSE, 6);
+ gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
+ gtk_box_pack_start(GTK_BOX(main_vbox), vbox, TRUE, TRUE, 0);
+ gtk_widget_show(vbox);
+
+ scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), GTK_SHADOW_IN);
+ gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(scrolled_window), TRUE, TRUE, 0);
+ g_signal_connect(gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scrolled_window)), "changed", G_CALLBACK(scroll_changed_cb), window);
+ g_signal_connect(gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scrolled_window)), "value-changed", G_CALLBACK(scroll_value_changed_cb), window);
+ window->priv->right_aligned = TRUE;
+ gtk_widget_show(scrolled_window);
+
+ window->priv->display = math_display_new_with_equation(window->priv->equation);
+ gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window), GTK_WIDGET(window->priv->display));
+ gtk_widget_show(GTK_WIDGET(window->priv->display));
+
+ window->priv->buttons = math_buttons_new(window->priv->equation);
+ g_signal_connect(window->priv->buttons, "notify::mode", G_CALLBACK(button_mode_changed_cb), window);
+ button_mode_changed_cb(window->priv->buttons, NULL, window);
+ gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(window->priv->buttons), TRUE, TRUE, 0);
+ gtk_widget_show(GTK_WIDGET(window->priv->buttons));
+}
+
+
+static void
+math_window_set_property(GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MathWindow *self;
+
+ self = MATH_WINDOW (object);
+
+ switch (prop_id) {
+ case PROP_EQUATION:
+ self->priv->equation = g_value_get_object (value);
+ create_gui(self);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+math_window_get_property(GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MathWindow *self;
+
+ self = MATH_WINDOW (object);
+
+ switch (prop_id) {
+ case PROP_EQUATION:
+ g_value_set_object (value, self->priv->equation);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+math_window_class_init (MathWindowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = math_window_get_property;
+ object_class->set_property = math_window_set_property;
+
+ g_type_class_add_private (klass, sizeof (MathWindowPrivate));
+
+ g_object_class_install_property(object_class,
+ PROP_EQUATION,
+ g_param_spec_object("equation",
+ "equation",
+ "Equation being calculated",
+ math_equation_get_type(),
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ signals[QUIT] =
+ g_signal_new ("quit",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (MathWindowClass, quit),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+
+static void
+math_window_init(MathWindow *window)
+{
+ window->priv = G_TYPE_INSTANCE_GET_PRIVATE (window, math_window_get_type(), MathWindowPrivate);
+ gtk_window_set_title(GTK_WINDOW(window),
+ /* Title of main window */
+ _("Calculator"));
+ gtk_window_set_icon_name(GTK_WINDOW(window), "accessories-calculator");
+ gtk_window_set_role(GTK_WINDOW(window), "gcalctool");
+ gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
+ g_signal_connect_after(G_OBJECT(window), "key-press-event", G_CALLBACK(key_press_cb), NULL);
+ g_signal_connect(G_OBJECT(window), "delete-event", G_CALLBACK(delete_cb), NULL);
+}
diff --git a/src/math-window.h b/src/math-window.h
new file mode 100644
index 0000000..70c81bd
--- /dev/null
+++ b/src/math-window.h
@@ -0,0 +1,61 @@
+/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef MATH_WINDOW_H
+#define MATH_WINDOW_H
+
+#include <glib-object.h>
+#include "math-equation.h"
+#include "math-display.h"
+#include "math-buttons.h"
+
+G_BEGIN_DECLS
+
+#define MATH_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), math_window_get_type(), MathWindow))
+
+typedef struct MathWindowPrivate MathWindowPrivate;
+
+typedef struct
+{
+ GtkWindow parent_instance;
+ MathWindowPrivate *priv;
+} MathWindow;
+
+typedef struct
+{
+ GtkWindowClass parent_class;
+
+ void (*quit)(MathWindow *window);
+} MathWindowClass;
+
+GType math_window_get_type(void);
+
+MathWindow *math_window_new(MathEquation *equation);
+
+GtkWidget *math_window_get_menu_bar(MathWindow *window);
+
+MathEquation *math_window_get_equation(MathWindow *window);
+
+MathDisplay *math_window_get_display(MathWindow *window);
+
+MathButtons *math_window_get_buttons(MathWindow *window);
+
+void math_window_critical_error(MathWindow *window, const gchar *title, const gchar *contents);
+
+#endif /* MATH_WINDOW_H */
diff --git a/src/mp-binary.c b/src/mp-binary.c
new file mode 100644
index 0000000..e4cedba
--- /dev/null
+++ b/src/mp-binary.c
@@ -0,0 +1,208 @@
+/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <stdio.h>
+
+#include "mp.h"
+#include "mp-private.h"
+
+// FIXME: Make dynamic
+#define MAX_DIGITS 1000
+
+static char digits[] = "0123456789ABCDEF";
+
+static int hex_to_int(char digit)
+{
+ if (digit >= '0' && digit <= '9')
+ return digit - '0';
+ if (digit >= 'A' && digit <= 'F')
+ return digit - 'A' + 10;
+ if (digit >= 'a' && digit <= 'f')
+ return digit - 'a' + 10;
+ return 0;
+}
+
+
+static void
+mp_bitwise(const MPNumber *x, const MPNumber *y, int (*bitwise_operator)(int, int), MPNumber *z, int wordlen)
+{
+ char text1[MAX_DIGITS], text2[MAX_DIGITS], text_out[MAX_DIGITS], text_out2[MAX_DIGITS];
+ int offset1, offset2, offset_out;
+
+ mp_cast_to_string(x, 16, 16, 0, 0, text1, MAX_DIGITS);
+ mp_cast_to_string(y, 16, 16, 0, 0, text2, MAX_DIGITS);
+ offset1 = strlen(text1) - 1;
+ offset2 = strlen(text2) - 1;
+ offset_out = wordlen / 4 - 1;
+ if (offset_out <= 0) {
+ offset_out = offset1 > offset2 ? offset1 : offset2;
+ }
+ if (offset_out > 0 && (offset_out < offset1 || offset_out < offset2)) {
+ mperr("Overflow. Try a bigger word size");
+ return;
+ }
+
+ /* Perform bitwise operator on each character from right to left */
+ for (text_out[offset_out+1] = '\0'; offset_out >= 0; offset_out--) {
+ int v1 = 0, v2 = 0;
+
+ if (offset1 >= 0) {
+ v1 = hex_to_int(text1[offset1]);
+ offset1--;
+ }
+ if (offset2 >= 0) {
+ v2 = hex_to_int(text2[offset2]);
+ offset2--;
+ }
+ text_out[offset_out] = digits[bitwise_operator(v1, v2)];
+ }
+
+ snprintf(text_out2, MAX_DIGITS, "%s", text_out);
+ mp_set_from_string(text_out2, 16, z);
+}
+
+
+static int mp_bitwise_and(int v1, int v2) { return v1 & v2; }
+static int mp_bitwise_or(int v1, int v2) { return v1 | v2; }
+static int mp_bitwise_xor(int v1, int v2) { return v1 ^ v2; }
+static int mp_bitwise_not(int v1, int dummy) { return v1 ^ 0xF; }
+
+
+bool
+mp_is_overflow (const MPNumber *x, int wordlen)
+{
+ MPNumber tmp1, tmp2;
+ mp_set_from_integer(2, &tmp1);
+ mp_xpowy_integer(&tmp1, wordlen, &tmp2);
+ return mp_is_greater_than (&tmp2, x);
+}
+
+
+void
+mp_and(const MPNumber *x, const MPNumber *y, MPNumber *z)
+{
+ if (!mp_is_positive_integer(x) || !mp_is_positive_integer(y))
+ {
+ /* Translators: Error displayed when boolean AND attempted on non-integer values */
+ mperr(_("Boolean AND is only defined for positive integers"));
+ }
+
+ mp_bitwise(x, y, mp_bitwise_and, z, 0);
+}
+
+
+void
+mp_or(const MPNumber *x, const MPNumber *y, MPNumber *z)
+{
+ if (!mp_is_positive_integer(x) || !mp_is_positive_integer(y))
+ {
+ /* Translators: Error displayed when boolean OR attempted on non-integer values */
+ mperr(_("Boolean OR is only defined for positive integers"));
+ }
+
+ mp_bitwise(x, y, mp_bitwise_or, z, 0);
+}
+
+
+void
+mp_xor(const MPNumber *x, const MPNumber *y, MPNumber *z)
+{
+ if (!mp_is_positive_integer(x) || !mp_is_positive_integer(y))
+ {
+ /* Translators: Error displayed when boolean XOR attempted on non-integer values */
+ mperr(_("Boolean XOR is only defined for positive integers"));
+ }
+
+ mp_bitwise(x, y, mp_bitwise_xor, z, 0);
+}
+
+
+void
+mp_not(const MPNumber *x, int wordlen, MPNumber *z)
+{
+ MPNumber temp;
+
+ if (!mp_is_positive_integer(x))
+ {
+ /* Translators: Error displayed when boolean XOR attempted on non-integer values */
+ mperr(_("Boolean NOT is only defined for positive integers"));
+ }
+
+ mp_set_from_integer(0, &temp);
+ mp_bitwise(x, &temp, mp_bitwise_not, z, wordlen);
+}
+
+
+void
+mp_mask(const MPNumber *x, int wordlen, MPNumber *z)
+{
+ char text[MAX_DIGITS];
+ size_t len, offset;
+
+ /* Convert to a hexadecimal string and use last characters */
+ mp_cast_to_string(x, 16, 16, 0, 0, text, MAX_DIGITS);
+ len = strlen(text);
+ offset = wordlen / 4;
+ offset = len > offset ? len - offset: 0;
+ mp_set_from_string(text + offset, 16, z);
+}
+
+
+void
+mp_shift(const MPNumber *x, int count, MPNumber *z)
+{
+ int i, multiplier = 1;
+
+ if (!mp_is_integer(x)) {
+ /* Translators: Error displayed when bit shift attempted on non-integer values */
+ mperr(_("Shift is only possible on integer values"));
+ return;
+ }
+
+ if (count >= 0) {
+ for (i = 0; i < count; i++)
+ multiplier *= 2;
+ mp_multiply_integer(x, multiplier, z);
+ }
+ else {
+ MPNumber temp;
+ for (i = 0; i < -count; i++)
+ multiplier *= 2;
+ mp_divide_integer(x, multiplier, &temp);
+ mp_floor(&temp, z);
+ }
+}
+
+
+void
+mp_ones_complement(const MPNumber *x, int wordlen, MPNumber *z)
+{
+ MPNumber t;
+ mp_set_from_integer(0, &t);
+ mp_bitwise(x, &t, mp_bitwise_xor, z, wordlen);
+ mp_not(z, wordlen, z);
+}
+
+
+void
+mp_twos_complement(const MPNumber *x, int wordlen, MPNumber *z)
+{
+ mp_ones_complement (x, wordlen, z);
+ mp_add_integer (z, 1, z);
+}
diff --git a/src/mp-convert.c b/src/mp-convert.c
new file mode 100644
index 0000000..68be574
--- /dev/null
+++ b/src/mp-convert.c
@@ -0,0 +1,933 @@
+/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "mp.h"
+#include "mp-private.h"
+
+void
+mp_set_from_mp(const MPNumber *x, MPNumber *z)
+{
+ if (z != x)
+ memcpy(z, x, sizeof(MPNumber));
+}
+
+
+void
+mp_set_from_float(float rx, MPNumber *z)
+{
+ int i, k, ib, ie, tp;
+ float rj;
+
+ mp_set_from_integer(0, z);
+
+ /* CHECK SIGN */
+ if (rx < 0.0f) {
+ z->sign = -1;
+ rj = -(double)(rx);
+ } else if (rx > 0.0f) {
+ z->sign = 1;
+ rj = rx;
+ } else {
+ /* IF RX = 0E0 RETURN 0 */
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* INCREASE IE AND DIVIDE RJ BY 16. */
+ ie = 0;
+ while (rj >= 1.0f) {
+ ++ie;
+ rj *= 0.0625f;
+ }
+ while (rj < 0.0625f) {
+ --ie;
+ rj *= 16.0f;
+ }
+
+ /* NOW RJ IS DY DIVIDED BY SUITABLE POWER OF 16.
+ * SET EXPONENT TO 0
+ */
+ z->exponent = 0;
+
+ /* CONVERSION LOOP (ASSUME SINGLE-PRECISION OPS. EXACT) */
+ for (i = 0; i < MP_T + 4; i++) {
+ rj *= (float) MP_BASE;
+ z->fraction[i] = (int) rj;
+ rj -= (float) z->fraction[i];
+ }
+
+ /* NORMALIZE RESULT */
+ mp_normalize(z);
+
+ /* Computing MAX */
+ ib = max(MP_BASE * 7 * MP_BASE, 32767) / 16;
+ tp = 1;
+
+ /* NOW MULTIPLY BY 16**IE */
+ if (ie < 0) {
+ k = -ie;
+ for (i = 1; i <= k; ++i) {
+ tp <<= 4;
+ if (tp <= ib && tp != MP_BASE && i < k)
+ continue;
+ mp_divide_integer(z, tp, z);
+ tp = 1;
+ }
+ } else if (ie > 0) {
+ for (i = 1; i <= ie; ++i) {
+ tp <<= 4;
+ if (tp <= ib && tp != MP_BASE && i < ie)
+ continue;
+ mp_multiply_integer(z, tp, z);
+ tp = 1;
+ }
+ }
+}
+
+
+void
+mp_set_from_double(double dx, MPNumber *z)
+{
+ int i, k, ib, ie, tp;
+ double dj;
+
+ mp_set_from_integer(0, z);
+
+ /* CHECK SIGN */
+ if (dx < 0.0) {
+ z->sign = -1;
+ dj = -dx;
+ } else if (dx > 0.0) {
+ z->sign = 1;
+ dj = dx;
+ } else {
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* INCREASE IE AND DIVIDE DJ BY 16. */
+ for (ie = 0; dj >= 1.0; ie++)
+ dj *= 1.0/16.0;
+
+ for ( ; dj < 1.0/16.0; ie--)
+ dj *= 16.0;
+
+ /* NOW DJ IS DY DIVIDED BY SUITABLE POWER OF 16
+ * SET EXPONENT TO 0
+ */
+ z->exponent = 0;
+
+ /* CONVERSION LOOP (ASSUME DOUBLE-PRECISION OPS. EXACT) */
+ for (i = 0; i < MP_T + 4; i++) {
+ dj *= (double) MP_BASE;
+ z->fraction[i] = (int) dj;
+ dj -= (double) z->fraction[i];
+ }
+
+ /* NORMALIZE RESULT */
+ mp_normalize(z);
+
+ /* Computing MAX */
+ ib = max(MP_BASE * 7 * MP_BASE, 32767) / 16;
+ tp = 1;
+
+ /* NOW MULTIPLY BY 16**IE */
+ if (ie < 0) {
+ k = -ie;
+ for (i = 1; i <= k; ++i) {
+ tp <<= 4;
+ if (tp <= ib && tp != MP_BASE && i < k)
+ continue;
+ mp_divide_integer(z, tp, z);
+ tp = 1;
+ }
+ } else if (ie > 0) {
+ for (i = 1; i <= ie; ++i) {
+ tp <<= 4;
+ if (tp <= ib && tp != MP_BASE && i < ie)
+ continue;
+ mp_multiply_integer(z, tp, z);
+ tp = 1;
+ }
+ }
+}
+
+
+void
+mp_set_from_integer(int64_t x, MPNumber *z)
+{
+ int i;
+
+ memset(z, 0, sizeof(MPNumber));
+
+ if (x == 0) {
+ z->sign = 0;
+ return;
+ }
+
+ if (x < 0) {
+ x = -x;
+ z->sign = -1;
+ }
+ else if (x > 0)
+ z->sign = 1;
+
+ while (x != 0) {
+ z->fraction[z->exponent] = x % MP_BASE;
+ z->exponent++;
+ x /= MP_BASE;
+ }
+ for (i = 0; i < z->exponent / 2; i++) {
+ int t = z->fraction[i];
+ z->fraction[i] = z->fraction[z->exponent - i - 1];
+ z->fraction[z->exponent - i - 1] = t;
+ }
+}
+
+
+void
+mp_set_from_unsigned_integer(uint64_t x, MPNumber *z)
+{
+ int i;
+
+ mp_set_from_integer(0, z);
+
+ if (x == 0) {
+ z->sign = 0;
+ return;
+ }
+ z->sign = 1;
+
+ while (x != 0) {
+ z->fraction[z->exponent] = x % MP_BASE;
+ x = x / MP_BASE;
+ z->exponent++;
+ }
+ for (i = 0; i < z->exponent / 2; i++) {
+ int t = z->fraction[i];
+ z->fraction[i] = z->fraction[z->exponent - i - 1];
+ z->fraction[z->exponent - i - 1] = t;
+ }
+}
+
+
+void
+mp_set_from_fraction(int64_t numerator, int64_t denominator, MPNumber *z)
+{
+ mp_gcd(&numerator, &denominator);
+
+ if (denominator == 0) {
+ mperr("*** J == 0 IN CALL TO MP_SET_FROM_FRACTION ***\n");
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ if (denominator < 0) {
+ numerator = -numerator;
+ denominator = -denominator;
+ }
+
+ mp_set_from_integer(numerator, z);
+ if (denominator != 1)
+ mp_divide_integer(z, denominator, z);
+}
+
+
+void
+mp_set_from_polar(const MPNumber *r, MPAngleUnit unit, const MPNumber *theta, MPNumber *z)
+{
+ MPNumber x, y;
+
+ mp_cos(theta, unit, &x);
+ mp_multiply(&x, r, &x);
+ mp_sin(theta, unit, &y);
+ mp_multiply(&y, r, &y);
+ mp_set_from_complex(&x, &y, z);
+}
+
+
+void
+mp_set_from_complex(const MPNumber *x, const MPNumber *y, MPNumber *z)
+{
+ /* NOTE: Do imaginary component first as z may be x or y */
+ z->im_sign = y->sign;
+ z->im_exponent = y->exponent;
+ memcpy(z->im_fraction, y->fraction, sizeof(int) * MP_SIZE);
+
+ z->sign = x->sign;
+ z->exponent = x->exponent;
+ if (z != x)
+ memcpy(z->fraction, x->fraction, sizeof(int) * MP_SIZE);
+}
+
+
+void
+mp_set_from_random(MPNumber *z)
+{
+ mp_set_from_double(drand48(), z);
+}
+
+
+int64_t
+mp_cast_to_int(const MPNumber *x)
+{
+ int i;
+ int64_t z = 0, v;
+
+ /* |x| <= 1 */
+ if (x->sign == 0 || x->exponent <= 0)
+ return 0;
+
+ /* Multiply digits together */
+ for (i = 0; i < x->exponent; i++) {
+ int64_t t;
+
+ t = z;
+ z = z * MP_BASE + x->fraction[i];
+
+ /* Check for overflow */
+ if (z <= t)
+ return 0;
+ }
+
+ /* Validate result */
+ v = z;
+ for (i = x->exponent - 1; i >= 0; i--) {
+ int64_t digit;
+
+ /* Get last digit */
+ digit = v - (v / MP_BASE) * MP_BASE;
+ if (x->fraction[i] != digit)
+ return 0;
+
+ v /= MP_BASE;
+ }
+ if (v != 0)
+ return 0;
+
+ return x->sign * z;
+}
+
+
+uint64_t
+mp_cast_to_unsigned_int(const MPNumber *x)
+{
+ int i;
+ uint64_t z = 0, v;
+
+ /* x <= 1 */
+ if (x->sign <= 0 || x->exponent <= 0)
+ return 0;
+
+ /* Multiply digits together */
+ for (i = 0; i < x->exponent; i++) {
+ uint64_t t;
+
+ t = z;
+ z = z * MP_BASE + x->fraction[i];
+
+ /* Check for overflow */
+ if (z <= t)
+ return 0;
+ }
+
+ /* Validate result */
+ v = z;
+ for (i = x->exponent - 1; i >= 0; i--) {
+ uint64_t digit;
+
+ /* Get last digit */
+ digit = v - (v / MP_BASE) * MP_BASE;
+ if (x->fraction[i] != digit)
+ return 0;
+
+ v /= MP_BASE;
+ }
+ if (v != 0)
+ return 0;
+
+ return z;
+}
+
+
+static double
+mppow_ri(float ap, int bp)
+{
+ double pow;
+
+ if (bp == 0)
+ return 1.0;
+
+ if (bp < 0) {
+ if (ap == 0)
+ return 1.0;
+ bp = -bp;
+ ap = 1 / ap;
+ }
+
+ pow = 1.0;
+ for (;;) {
+ if (bp & 01)
+ pow *= ap;
+ if (bp >>= 1)
+ ap *= ap;
+ else
+ break;
+ }
+
+ return pow;
+}
+
+
+float
+mp_cast_to_float(const MPNumber *x)
+{
+ int i;
+ float rz = 0.0;
+
+ if (mp_is_zero(x))
+ return 0.0;
+
+ for (i = 0; i < MP_T; i++) {
+ rz = (float) MP_BASE * rz + (float)x->fraction[i];
+
+ /* CHECK IF FULL SINGLE-PRECISION ACCURACY ATTAINED */
+ if (rz + 1.0f <= rz)
+ break;
+ }
+
+ /* NOW ALLOW FOR EXPONENT */
+ rz *= mppow_ri((float) MP_BASE, x->exponent - i - 1);
+
+ /* CHECK REASONABLENESS OF RESULT */
+ /* LHS SHOULD BE <= 0.5, BUT ALLOW FOR SOME ERROR IN ALOG */
+ if (rz <= (float)0. ||
+ fabs((float) x->exponent - (log(rz) / log((float) MP_BASE) + (float).5)) > (float).6) {
+ /* FOLLOWING MESSAGE INDICATES THAT X IS TOO LARGE OR SMALL -
+ * TRY USING MPCMRE INSTEAD.
+ */
+ mperr("*** FLOATING-POINT OVER/UNDER-FLOW IN MP_CAST_TO_FLOAT ***\n");
+ return 0.0;
+ }
+
+ if (x->sign < 0)
+ rz = -(double)(rz);
+
+ return rz;
+}
+
+
+static double
+mppow_di(double ap, int bp)
+{
+ double pow = 1.0;
+
+ if (bp != 0) {
+ if (bp < 0) {
+ if (ap == 0) return(pow);
+ bp = -bp;
+ ap = 1/ap;
+ }
+ for (;;) {
+ if (bp & 01) pow *= ap;
+ if (bp >>= 1) ap *= ap;
+ else break;
+ }
+ }
+
+ return(pow);
+}
+
+
+double
+mp_cast_to_double(const MPNumber *x)
+{
+ int i, tm = 0;
+ double d__1, dz2, ret_val = 0.0;
+
+ if (mp_is_zero(x))
+ return 0.0;
+
+ for (i = 0; i < MP_T; i++) {
+ ret_val = (double) MP_BASE * ret_val + (double) x->fraction[i];
+ tm = i;
+
+ /* CHECK IF FULL DOUBLE-PRECISION ACCURACY ATTAINED */
+ dz2 = ret_val + 1.0;
+
+ /* TEST BELOW NOT ALWAYS EQUIVALENT TO - IF (DZ2.LE.DZ) GO TO 20,
+ * FOR EXAMPLE ON CYBER 76.
+ */
+ if (dz2 - ret_val <= 0.0)
+ break;
+ }
+
+ /* NOW ALLOW FOR EXPONENT */
+ ret_val *= mppow_di((double) MP_BASE, x->exponent - tm - 1);
+
+ /* CHECK REASONABLENESS OF RESULT. */
+ /* LHS SHOULD BE .LE. 0.5 BUT ALLOW FOR SOME ERROR IN DLOG */
+ if (ret_val <= 0. ||
+ ((d__1 = (double) ((float) x->exponent) - (log(ret_val) / log((double)
+ ((float) MP_BASE)) + .5), abs(d__1)) > .6)) {
+ /* FOLLOWING MESSAGE INDICATES THAT X IS TOO LARGE OR SMALL -
+ * TRY USING MPCMDE INSTEAD.
+ */
+ mperr("*** FLOATING-POINT OVER/UNDER-FLOW IN MP_CAST_TO_DOUBLE ***\n");
+ return 0.0;
+ }
+ else
+ {
+ if (x->sign < 0)
+ ret_val = -ret_val;
+ return ret_val;
+ }
+}
+
+
+static void
+mp_cast_to_string_real(const MPNumber *x, int default_base, int base, int accuracy, bool trim_zeroes, bool force_sign, GString *string)
+{
+ static char 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, -(accuracy+1), &temp);
+ mp_multiply_integer(&temp, base, &temp);
+ mp_divide_integer(&temp, 2, &temp);
+ mp_add(&number, &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);
+ do {
+ MPNumber t, t2, t3;
+ int64_t d;
+
+ 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] : '?');
+
+ mp_set_from_mp(&t, &temp);
+ } while (!mp_is_zero(&temp));
+
+ last_non_zero = string->len;
+ g_string_append_c(string, '.');
+
+ /* Write out the fractional component */
+ mp_set_from_mp(&fractional_component, &temp);
+ for (i = accuracy; 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 (trim_zeroes || accuracy == 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 != default_base) {
+ const char *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;
+ }
+ }
+}
+
+
+void
+mp_cast_to_string(const MPNumber *x, int default_base, int base, int accuracy, bool trim_zeroes, char *buffer, int buffer_length)
+{
+ GString *string;
+ MPNumber x_real;
+
+ string = g_string_sized_new(buffer_length);
+
+ mp_real_component(x, &x_real);
+ mp_cast_to_string_real(&x_real, default_base, base, accuracy, trim_zeroes, FALSE, string);
+ if (mp_is_complex(x)) {
+ GString *s;
+ gboolean force_sign = TRUE;
+ MPNumber x_im;
+
+ mp_imaginary_component(x, &x_im);
+
+ if (strcmp(string->str, "0") == 0) {
+ g_string_assign(string, "");
+ force_sign = false;
+ }
+
+ s = g_string_sized_new(buffer_length);
+ mp_cast_to_string_real(&x_im, default_base, 10, accuracy, trim_zeroes, force_sign, s);
+ 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);
+ }
+
+ // FIXME: Check for truncation
+ strncpy(buffer, string->str, buffer_length);
+ g_string_free(string, TRUE);
+}
+
+
+void
+mp_cast_to_exponential_string(const MPNumber *x, int default_base, int base_, int max_digits, bool trim_zeroes, bool eng_format, char *buffer, int buffer_length)
+{
+ char fixed[1024], *c;
+ MPNumber t, z, base, base3, base10, base10inv, mantissa;
+ int exponent = 0;
+ GString *string;
+ const char *super_digits[] = {"⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹"};
+
+ string = g_string_sized_new(buffer_length);
+
+ mp_abs(x, &z);
+ if (mp_is_negative(x))
+ g_string_append(string, "−");
+ mp_set_from_mp(&z, &mantissa);
+
+ mp_set_from_integer(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);
+ }
+ }
+
+ mp_cast_to_string(&mantissa, default_base, base_, max_digits, trim_zeroes, fixed, 1024);
+ g_string_append(string, fixed);
+ if (exponent != 0) {
+ g_string_append_printf(string, "×10"); // FIXME: Use the current base
+ if (exponent < 0) {
+ exponent = -exponent;
+ g_string_append(string, "⁻");
+ }
+ snprintf(fixed, 1024, "%d", exponent);
+ for (c = fixed; *c; c++)
+ g_string_append(string, super_digits[*c - '0']);
+ }
+
+ strncpy(buffer, string->str, buffer_length);
+ g_string_free(string, TRUE);
+}
+
+
+static int
+char_val(char **c, int base)
+{
+ int i, j, value, offset;
+ const char *digits[][10] = {{"٠", "١", "٢", "٣", "٤", "٥", "٦", "٧", "٨", "٩"},
+ {"۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹"},
+ {"߀", "߁", "߂", "߃", "߄", "߅", "߆", "߇", "߈", "߉"},
+ {"०", "१", "२", "३", "४", "५", "६", "७", "८", "९"},
+ {"০", "১", "২", "৩", "৪", "৫", "৬", "৭", "৮", "৯"},
+ {"੦", "੧", "੨", "੩", "੪", "੫", "੬", "੭", "੮", "੯"},
+ {"૦", "૧", "૨", "૩", "૪", "૫", "૬", "૭", "૮", "૯"},
+ {"୦", "୧", "୨", "୩", "୪", "୫", "୬", "୭", "୮", "୯"},
+ {"௦", "௧", "௨", "௩", "௪", "௫", "௬", "௭", "௮", "௯"},
+ {"౦", "౧", "౨", "౩", "౪", "౫", "౬", "౭", "౮", "౯"},
+ {"೦", "೧", "೨", "೩", "೪", "೫", "೬", "೭", "೮", "೯"},
+ {"൦", "൧", "൨", "൩", "൪", "൫", "൬", "൭", "൮", "൯"},
+ {"๐", "๑", "๒", "๓", "๔", "๕", "๖", "๗", "๘", "๙"},
+ {"໐", "໑", "໒", "໓", "໔", "໕", "໖", "໗", "໘", "໙"},
+ {"༠", "༡", "༢", "༣", "༤", "༥", "༦", "༧", "༨", "༩"},
+ {"၀", "၁", "၂", "၃", "၄", "၅", "၆", "၇", "၈", "၉"},
+ {"႐", "႑", "႒", "႓", "႔", "႕", "႖", "႗", "႘", "႙"},
+ {"០", "១", "២", "៣", "៤", "៥", "៦", "៧", "៨", "៩"},
+ {"᠐", "᠑", "᠒", "᠓", "᠔", "᠕", "᠖", "᠗", "᠘", "᠙"},
+ {"᥆", "᥇", "᥈", "᥉", "᥊", "᥋", "᥌", "᥍", "᥎", "᥏"},
+ {"᧐", "᧑", "᧒", "᧓", "᧔", "᧕", "᧖", "᧗", "᧘", "᧙"},
+ {"᭐", "᭑", "᭒", "᭓", "᭔", "᭕", "᭖", "᭗", "᭘", "᭙"},
+ {"᮰", "᮱", "᮲", "᮳", "᮴", "᮵", "᮶", "᮷", "᮸", "᮹"},
+ {"᱀", "᱁", "᱂", "᱃", "᱄", "᱅", "᱆", "᱇", "᱈", "᱉"},
+ {"᱐", "᱑", "᱒", "᱓", "᱔", "᱕", "᱖", "᱗", "᱘", "᱙"},
+ {"꘠", "꘡", "꘢", "꘣", "꘤", "꘥", "꘦", "꘧", "꘨", "꘩"},
+ {"꣐", "꣑", "꣒", "꣓", "꣔", "꣕", "꣖", "꣗", "꣘", "꣙"},
+ {"꤀", "꤁", "꤂", "꤃", "꤄", "꤅", "꤆", "꤇", "꤈", "꤉"},
+ {"꩐", "꩑", "꩒", "꩓", "꩔", "꩕", "꩖", "꩗", "꩘", "꩙"},
+ {"𐒠", "𐒡", "𐒢", "𐒣", "𐒤", "𐒥", "𐒦", "𐒧", "𐒨", "𐒩"},
+ {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};
+
+ if (**c >= '0' && **c <= '9') {
+ value = **c - '0';
+ offset = 1;
+ } else if (**c >= 'a' && **c <= 'f') {
+ value = **c - 'a' + 10;
+ offset = 1;
+ } else if (**c >= 'A' && **c <= 'F') {
+ value = **c - 'A' + 10;
+ offset = 1;
+ } else {
+ for (i = 0; digits[i][0]; i++) {
+ for (j = 0; j < 10; j++) {
+ if (strncmp(*c, digits[i][j], strlen(digits[i][j])) == 0)
+ break;
+ }
+ if (j != 10)
+ break;
+ }
+ if (digits[i][0] == NULL)
+ return -1;
+ value = j;
+ offset = strlen(digits[i][j]);
+ }
+ if (value >= base)
+ return -1;
+
+ *c += offset;
+
+ return value;
+}
+
+
+static int
+ends_with(const char *start, const char *end, const char *word)
+{
+ size_t word_len = strlen(word);
+
+ if (word_len > end - start)
+ return 0;
+
+ return strncmp(end - word_len, word, word_len) == 0;
+}
+
+
+// FIXME: Doesn't handle errors well (e.g. trailing space)
+static bool
+set_from_sexagesimal(const char *str, int length, MPNumber *z)
+{
+ int degrees = 0, minutes = 0;
+ char seconds[length+1];
+ MPNumber t;
+ int n_matched;
+
+ seconds[0] = '\0';
+ n_matched = sscanf(str, "%d°%d'%s\"", &degrees, &minutes, seconds);
+
+ if (n_matched < 1)
+ return true;
+ mp_set_from_integer(degrees, z);
+ if (n_matched > 1) {
+ mp_set_from_integer(minutes, &t);
+ mp_divide_integer(&t, 60, &t);
+ mp_add(z, &t, z);
+ }
+ if (n_matched > 2) {
+ mp_set_from_string(seconds, 10, &t);
+ mp_divide_integer(&t, 3600, &t);
+ mp_add(z, &t, z);
+ }
+
+ return false;
+}
+
+
+bool
+mp_set_from_string(const char *str, int default_base, MPNumber *z)
+{
+ int i, base, negate = 0, multiplier = 0, base_multiplier = 1;
+ const char *c, *end;
+ gboolean has_fraction = FALSE;
+
+ const char *base_digits[] = {"₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉", NULL};
+ const char *fractions[] = {"½", "⅓", "⅔", "¼", "¾", "⅕", "⅖", "⅗", "⅘", "⅙", "⅚", "⅛", "⅜", "⅝", "⅞", NULL};
+ int numerators[] = { 1, 1, 2, 1, 3, 1, 2, 3, 4, 1, 5, 1, 3, 5, 7};
+ int denominators[] = { 2, 3, 3, 4, 4, 5, 5, 5, 5, 6, 6, 8, 8, 8, 8};
+
+ if (strstr(str, "°"))
+ return set_from_sexagesimal(str, strlen(str), z);
+
+ /* Find the base */
+ end = str;
+ while (*end != '\0')
+ end++;
+ base = 0;
+ while (1) {
+ for (i = 0; base_digits[i] != NULL; i++) {
+ if (ends_with(str, end, base_digits[i])) {
+ base += i * base_multiplier;
+ end -= strlen(base_digits[i]);
+ base_multiplier *= 10;
+ break;
+ }
+ }
+ if (base_digits[i] == NULL)
+ break;
+ }
+ if (base_multiplier == 1)
+ base = default_base;
+
+ /* Check if this has a sign */
+ c = str;
+ if (*c == '+') {
+ c++;
+ } else if (*c == '-') {
+ negate = 1;
+ c++;
+ } else if (strncmp(c, "−", strlen("−")) == 0) {
+ negate = 1;
+ c += strlen("−");
+ }
+
+ /* Convert integer part */
+ mp_set_from_integer(0, z);
+ while ((i = char_val((char **)&c, base)) >= 0) {
+ if (i > base)
+ return true;
+ mp_multiply_integer(z, base, z);
+ mp_add_integer(z, i, z);
+ }
+
+ /* Look for fraction characters, e.g. ⅚ */
+ for (i = 0; fractions[i] != NULL; i++) {
+ if (ends_with(str, end, fractions[i])) {
+ end -= strlen(fractions[i]);
+ break;
+ }
+ }
+ if (fractions[i] != NULL) {
+ MPNumber fraction;
+ mp_set_from_fraction(numerators[i], denominators[i], &fraction);
+ mp_add(z, &fraction, z);
+ }
+
+ if (*c == '.' || *c == ',') {
+ has_fraction = TRUE;
+ c++;
+ }
+
+ /* Convert fractional part */
+ if (has_fraction) {
+ MPNumber numerator, denominator;
+
+ mp_set_from_integer(0, &numerator);
+ mp_set_from_integer(1, &denominator);
+ while ((i = char_val((char **)&c, base)) >= 0) {
+ mp_multiply_integer(&denominator, base, &denominator);
+ mp_multiply_integer(&numerator, base, &numerator);
+ mp_add_integer(&numerator, i, &numerator);
+ }
+ mp_divide(&numerator, &denominator, &numerator);
+ mp_add(z, &numerator, z);
+ }
+
+ if (c != end) {
+ return true;
+ }
+
+ if (multiplier != 0) {
+ MPNumber t;
+ mp_set_from_integer(10, &t);
+ mp_xpowy_integer(&t, multiplier, &t);
+ mp_multiply(z, &t, z);
+ }
+
+ if (negate == 1)
+ mp_invert_sign(z, z);
+
+ return false;
+}
diff --git a/src/mp-equation-lexer.l b/src/mp-equation-lexer.l
new file mode 100644
index 0000000..bbc9765
--- /dev/null
+++ b/src/mp-equation-lexer.l
@@ -0,0 +1,120 @@
+%option 8bit reentrant bison-locations
+%option never-interactive
+%option noyywrap noinput nounput
+%option prefix="_mp_equation_"
+%option extra-type="MPEquationParserState *"
+%option outfile="mp-equation-lexer.c" header-file="mp-equation-lexer.h"
+
+%{
+/* Copyright (c) 2004-2008 Sami Pietila
+ * Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <stdlib.h>
+#include <locale.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "mp-equation-private.h"
+#include "mp-equation-parser.h"
+#include "mp-equation.h"
+%}
+
+
+ZERO "0"|"٠"|"۰"|"߀"|"०"|"০"|"੦"|"૦"|"୦"|"௦"|"౦"|"೦"|"൦"|"๐"|"໐"|"༠"|"၀"|"႐"|"០"|"᠐"|"᥆"|"᧐"|"᭐"|"᮰"|"᱀"|"᱐"|"꘠"|"꣐"|"꤀"|"꩐"|"𐒠"
+ONE "1"|"١"|"۱"|"߁"|"१"|"১"|"੧"|"૧"|"୧"|"௧"|"౧"|"೧"|"൧"|"๑"|"໑"|"༡"|"၁"|"႑"|"១"|"᠑"|"᥇"|"᧑"|"᭑"|"᮱"|"᱁"|"᱑"|"꘡"|"꣑"|"꤁"|"꩑"|"𐒡"
+TWO "2"|"٢"|"۲"|"߂"|"२"|"২"|"੨"|"૨"|"୨"|"௨"|"౨"|"೨"|"൨"|"๒"|"໒"|"༢"|"၂"|"႒"|"២"|"᠒"|"᥈"|"᧒"|"᭒"|"᮲"|"᱂"|"᱒"|"꘢"|"꣒"|"꤂"|"꩒"|"𐒢"
+THREE "3"|"٣"|"۳"|"߃"|"३"|"৩"|"੩"|"૩"|"୩"|"௩"|"౩"|"೩"|"൩"|"๓"|"໓"|"༣"|"၃"|"႓"|"៣"|"᠓"|"᥉"|"᧓"|"᭓"|"᮳"|"᱃"|"᱓"|"꘣"|"꣓"|"꤃"|"꩓"|"𐒣"
+FOUR "4"|"٤"|"۴"|"߄"|"४"|"৪"|"੪"|"૪"|"୪"|"௪"|"౪"|"೪"|"൪"|"๔"|"໔"|"༤"|"၄"|"႔"|"៤"|"᠔"|"᥊"|"᧔"|"᭔"|"᮴"|"᱄"|"᱔"|"꘤"|"꣔"|"꤄"|"꩔"|"𐒤"
+FIVE "5"|"٥"|"۵"|"߅"|"५"|"৫"|"੫"|"૫"|"୫"|"௫"|"౫"|"೫"|"൫"|"๕"|"໕"|"༥"|"၅"|"႕"|"៥"|"᠕"|"᥋"|"᧕"|"᭕"|"᮵"|"᱅"|"᱕"|"꘥"|"꣕"|"꤅"|"꩕"|"𐒥"
+SIX "6"|"٦"|"۶"|"߆"|"६"|"৬"|"੬"|"૬"|"୬"|"௬"|"౬"|"೬"|"൬"|"๖"|"໖"|"༦"|"၆"|"႖"|"៦"|"᠖"|"᥌"|"᧖"|"᭖"|"᮶"|"᱆"|"᱖"|"꘦"|"꣖"|"꤆"|"꩖"|"𐒦"
+SEVEN "7"|"٧"|"۷"|"߇"|"७"|"৭"|"੭"|"૭"|"୭"|"௭"|"౭"|"೭"|"൭"|"๗"|"໗"|"༧"|"၇"|"႗"|"៧"|"᠗"|"᥍"|"᧗"|"᭗"|"᮷"|"᱇"|"᱗"|"꘧"|"꣗"|"꤇"|"꩗"|"𐒧"
+EIGHT "8"|"٨"|"۸"|"߈"|"८"|"৮"|"੮"|"૮"|"୮"|"௮"|"౮"|"೮"|"൮"|"๘"|"໘"|"༨"|"၈"|"႘"|"៨"|"᠘"|"᥎"|"᧘"|"᭘"|"᮸"|"᱈"|"᱘"|"꘨"|"꣘"|"꤈"|"꩘"|"𐒨"
+NINE "9"|"٩"|"۹"|"߉"|"९"|"৯"|"੯"|"૯"|"୯"|"௯"|"౯"|"೯"|"൯"|"๙"|"໙"|"༩"|"၉"|"႙"|"៩"|"᠙"|"᥏"|"᧙"|"᭙"|"᮹"|"᱉"|"᱙"|"꘩"|"꣙"|"꤉"|"꩙"|"𐒩"
+DECIMAL "."|","
+DEC {ZERO}|{ONE}|{TWO}|{THREE}|{FOUR}|{FIVE}|{SIX}|{SEVEN}|{EIGHT}|{NINE}
+HEX {DEC}|[A-F]|[a-f]
+SUPER_DIGITS "⁰"|"¹"|"²"|"³"|"⁴"|"⁵"|"⁶"|"⁷"|"⁸"|"⁹"
+SUPER_MINUS "⁻"
+SUB_DIGITS "₀"|"₁"|"₂"|"₃"|"₄"|"₅"|"₆"|"₇"|"₈"|"₉"
+FRACTION "½"|"⅓"|"⅔"|"¼"|"¾"|"⅕"|"⅖"|"⅗"|"⅘"|"⅙"|"⅚"|"⅛"|"⅜"|"⅝"|"⅞"
+GREEKS "α"|"β"|"γ"|"δ"|"ε"|"ζ"|"η"|"θ"|"ι"|"κ"|"λ"|"μ"|"ν"|"ξ"|"ο"|"π"|"ρ"|"ς"|"σ"|"τ"|"υ"|"φ"|"χ"|"ψ"|"ω"
+DEGREES "°"
+MINUTES "'"
+SECONDS "\""
+LETTERS [a-zA-Z]|{GREEKS}
+
+SUP_NUM {SUPER_DIGITS}+
+NSUP_NUM {SUPER_MINUS}{SUPER_DIGITS}+
+SUB_NUM {SUB_DIGITS}+
+WORD {LETTERS}+
+DEC_NUM {DEC}+|{DEC}*{DECIMAL}{DEC}+
+DEF_NUM {HEX}+|{HEX}*{DECIMAL}{HEX}+
+BASE_NUM {HEX}+{SUB_NUM}|{HEX}*{DECIMAL}{HEX}+{SUB_NUM}
+ANGLE_NUM {DEC_NUM}{DEGREES}|{DEC}+{DEGREES}{DEC_NUM}{MINUTES}|{DEC}+{DEGREES}{DEC}+{MINUTES}{DEC_NUM}{SECONDS}
+
+NUMBER {DEF_NUM}|{BASE_NUM}|{FRACTION}|{DEC}+{FRACTION}|{ANGLE_NUM}
+VARIABLE {WORD}|{WORD}{SUB_NUM}|{GREEKS}
+
+MOD [mM][oO][dD]
+AND "∧"|[aA][nN][dD]
+OR "∨"|[oO][rR]
+XOR "⊻"|"⊕"|[xX][oO][rR]
+NOT "¬"|"~"|[nN][oO][tT]
+RE "⃰ℜ"
+IM "ℑ"
+IN [iI][nN]
+
+%%
+
+"+" {return tADD;}
+"-"|"−" {return tSUBTRACT;}
+"*"|"×" {return tMULTIPLY;}
+"/"|"∕"|"÷" {return tDIVIDE;}
+{MOD} {return tMOD;}
+"⌊" {return tLFLOOR;}
+"⌋" {return tRFLOOR;}
+"⌈" {return tLCEILING;}
+"⌉" {return tRCEILING;}
+"√" {return tROOT;}
+"∛" {return tROOT3;}
+"∜" {return tROOT4;}
+{NOT} {return tNOT;}
+{AND} {return tAND;}
+{OR} {return tOR;}
+{XOR} {return tXOR;}
+{IN} {return tIN;}
+{NUMBER} {if (mp_set_from_string(yytext, _mp_equation_get_extra(yyscanner)->options->base, &yylval->int_t) != 0) REJECT; return tNUMBER;}
+{SUP_NUM} {yylval->integer = super_atoi(yytext); return tSUPNUM;}
+{NSUP_NUM} {yylval->integer = super_atoi(yytext); return tNSUPNUM;}
+{SUB_NUM} {yylval->integer = sub_atoi(yytext); return tSUBNUM;}
+{VARIABLE} {\
+ MPEquationParserState *state = _mp_equation_get_extra(yyscanner);\
+ if (state->function_is_defined(state, yytext)) {\
+ yylval->name = strdup(yytext);\
+ return tFUNCTION;\
+ }\
+ else {\
+ yylval->name = strdup(yytext);\
+ return tVARIABLE;\
+ }\
+}
+[ \r\t\n]
+. {return *yytext;}
+
+%%
diff --git a/src/mp-equation-parser.y b/src/mp-equation-parser.y
new file mode 100644
index 0000000..064f90f
--- /dev/null
+++ b/src/mp-equation-parser.y
@@ -0,0 +1,266 @@
+%{
+/* Copyright (c) 2004-2008 Sami Pietila
+ * Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "mp-equation-private.h"
+#include "mp-equation-parser.h"
+#include "mp-equation-lexer.h"
+
+// fixme support x log x
+// treat exp NAME exp as a function always and pass both arguments, i.e.
+// can do mod using both and all others use $1 * NAME($3)
+
+static void set_error(yyscan_t yyscanner, int error, const char *token)
+{
+ _mp_equation_get_extra(yyscanner)->error = error;
+ if (token)
+ _mp_equation_get_extra(yyscanner)->error_token = strdup(token);
+}
+
+static void set_result(yyscan_t yyscanner, const MPNumber *x)
+{
+ mp_set_from_mp(x, &(_mp_equation_get_extra(yyscanner))->ret);
+}
+
+static char *
+utf8_next_char (const char *c)
+{
+ c++;
+ while ((*c & 0xC0) == 0x80)
+ c++;
+ return (char *)c;
+}
+
+static int get_variable(yyscan_t yyscanner, const char *name, int power, MPNumber *z)
+{
+ int result = 0;
+
+ /* If defined, then get the variable */
+ if (_mp_equation_get_extra(yyscanner)->get_variable(_mp_equation_get_extra(yyscanner), name, z)) {
+ mp_xpowy_integer(z, power, z);
+ return 1;
+ }
+
+ /* If has more than one character then assume a multiplication of variables */
+ if (utf8_next_char(name)[0] != '\0') {
+ const char *c, *next;
+ char *buffer = malloc(sizeof(char) * strlen(name));
+ MPNumber value;
+
+ result = 1;
+ mp_set_from_integer(1, &value);
+ for (c = name; *c != '\0'; c = next) {
+ MPNumber t;
+
+ next = utf8_next_char(c);
+ snprintf(buffer, next - c + 1, "%s", c);
+
+ if (!_mp_equation_get_extra(yyscanner)->get_variable(_mp_equation_get_extra(yyscanner), buffer, &t)) {
+ result = 0;
+ break;
+ }
+
+ /* If last term do power */
+ if (*next == '\0')
+ mp_xpowy_integer(&t, power, &t);
+
+ mp_multiply(&value, &t, &value);
+ }
+
+ free(buffer);
+ if (result)
+ mp_set_from_mp(&value, z);
+ }
+
+ if (!result)
+ set_error(yyscanner, PARSER_ERR_UNKNOWN_VARIABLE, name);
+
+ return result;
+}
+
+static void set_variable(yyscan_t yyscanner, const char *name, MPNumber *x)
+{
+ _mp_equation_get_extra(yyscanner)->set_variable(_mp_equation_get_extra(yyscanner), name, x);
+}
+
+static int get_function(yyscan_t yyscanner, const char *name, const MPNumber *x, MPNumber *z)
+{
+ if (!_mp_equation_get_extra(yyscanner)->get_function(_mp_equation_get_extra(yyscanner), name, x, z)) {
+ set_error(yyscanner, PARSER_ERR_UNKNOWN_FUNCTION, name);
+ return 0;
+ }
+ return 1;
+}
+
+static int get_inverse_function(yyscan_t yyscanner, const char *name, const MPNumber *x, MPNumber *z)
+{
+ char *inv_name;
+ int result;
+
+ inv_name = malloc(sizeof(char) * (strlen(name) + strlen("⁻¹") + 1));
+ strcpy(inv_name, name);
+ strcat(inv_name, "⁻¹");
+ result = get_function(yyscanner, inv_name, x, z);
+ free(inv_name);
+
+ return result;
+}
+
+static void do_not(yyscan_t yyscanner, const MPNumber *x, MPNumber *z)
+{
+ if (!mp_is_overflow(x, _mp_equation_get_extra(yyscanner)->options->wordlen)) {
+ set_error(yyscanner, PARSER_ERR_OVERFLOW, NULL);
+ }
+ mp_not(x, _mp_equation_get_extra(yyscanner)->options->wordlen, z);
+}
+
+static char *make_unit(const char *name, int power)
+{
+ char *name2;
+
+ // FIXME: Hacky
+ if (power == 2) {
+ name2 = malloc(sizeof(char) * (strlen(name) + strlen("²") + 1));
+ sprintf(name2, "%s²", name);
+ }
+ else if (power == 3) {
+ name2 = malloc(sizeof(char) * (strlen(name) + strlen("³") + 1));
+ sprintf(name2, "%s³", name);
+ }
+ else {
+ name2 = malloc(sizeof(char) * (strlen(name) + strlen("?") + 1));
+ sprintf(name2, "%s?", name);
+ }
+
+ return name2;
+}
+
+static void do_conversion(yyscan_t yyscanner, const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z)
+{
+ if (!_mp_equation_get_extra(yyscanner)->convert(_mp_equation_get_extra(yyscanner), x, x_units, z_units, z))
+ set_error(yyscanner, PARSER_ERR_UNKNOWN_CONVERSION, NULL);
+}
+
+%}
+
+%pure-parser
+%name-prefix="_mp_equation_"
+%locations
+%parse-param {yyscan_t yyscanner}
+%lex-param {yyscan_t yyscanner}
+
+%union {
+ MPNumber int_t;
+ int integer;
+ char *name;
+}
+
+%left <int_t> tNUMBER
+%left tLFLOOR tRFLOOR tLCEILING tRCEILING
+%left UNARY_PLUS
+%left tADD tSUBTRACT
+%left tAND tOR tXOR tXNOR
+%left tMULTIPLY tDIVIDE tMOD MULTIPLICATION
+%left tNOT
+%left tROOT tROOT3 tROOT4
+%left <name> tVARIABLE tFUNCTION
+%right <integer> tSUBNUM tSUPNUM tNSUPNUM
+%left BOOLEAN_OPERATOR
+%left PERCENTAGE
+%left UNARY_MINUS
+%right '^' '!' '|'
+%left tIN
+
+%type <int_t> exp variable term
+%type <name> unit
+%start statement
+
+%%
+
+statement:
+ exp { set_result(yyscanner, &$1); }
+| exp '=' { set_result(yyscanner, &$1); }
+| tVARIABLE '=' exp {set_variable(yyscanner, $1, &$3); set_result(yyscanner, &$3); }
+| tNUMBER unit tIN unit { MPNumber t; do_conversion(yyscanner, &$1, $2, $4, &t); set_result(yyscanner, &t); free($2); free($4); }
+| unit tIN unit { MPNumber x, t; mp_set_from_integer(1, &x); do_conversion(yyscanner, &x, $1, $3, &t); set_result(yyscanner, &t); free($1); free($3); }
+;
+
+unit:
+ tVARIABLE {$$ = $1;}
+| tVARIABLE tSUPNUM {$$ = make_unit($1, $2); free($1);}
+
+/* |x| gets confused and thinks = |x|(...||) */
+
+exp:
+ '(' exp ')' {mp_set_from_mp(&$2, &$$);}
+| exp '(' exp ')' {mp_multiply(&$1, &$3, &$$);}
+| tLFLOOR exp tRFLOOR {mp_floor(&$2, &$$);}
+| tLCEILING exp tRCEILING {mp_ceiling(&$2, &$$);}
+| '[' exp ']' {mp_round(&$2, &$$);}
+| '{' exp '}' {mp_fractional_part(&$2, &$$);}
+| '|' exp '|' {mp_abs(&$2, &$$);}
+| exp '^' exp {mp_xpowy(&$1, &$3, &$$);}
+| exp tSUPNUM {mp_xpowy_integer(&$1, $2, &$$);}
+| exp tNSUPNUM {mp_xpowy_integer(&$1, $2, &$$);}
+| exp '!' {mp_factorial(&$1, &$$);}
+| variable {mp_set_from_mp(&$1, &$$);}
+| tNUMBER variable %prec MULTIPLICATION {mp_multiply(&$1, &$2, &$$);}
+| tSUBTRACT exp %prec UNARY_MINUS {mp_invert_sign(&$2, &$$);}
+| tADD tNUMBER %prec UNARY_PLUS {mp_set_from_mp(&$2, &$$);}
+| exp tDIVIDE exp {mp_divide(&$1, &$3, &$$);}
+| exp tMOD exp {mp_modulus_divide(&$1, &$3, &$$);}
+| exp tMULTIPLY exp {mp_multiply(&$1, &$3, &$$);}
+| exp tADD exp '%' %prec PERCENTAGE {mp_add_integer(&$3, 100, &$3); mp_divide_integer(&$3, 100, &$3); mp_multiply(&$1, &$3, &$$);}
+| exp tSUBTRACT exp '%' %prec PERCENTAGE {mp_add_integer(&$3, -100, &$3); mp_divide_integer(&$3, -100, &$3); mp_multiply(&$1, &$3, &$$);}
+| exp tADD exp {mp_add(&$1, &$3, &$$);}
+| exp tSUBTRACT exp {mp_subtract(&$1, &$3, &$$);}
+| exp '%' {mp_divide_integer(&$1, 100, &$$);}
+| tNOT exp {do_not(yyscanner, &$2, &$$);}
+| exp tAND exp %prec BOOLEAN_OPERATOR {mp_and(&$1, &$3, &$$);}
+| exp tOR exp %prec BOOLEAN_OPERATOR {mp_or(&$1, &$3, &$$);}
+| exp tXOR exp %prec BOOLEAN_OPERATOR {mp_xor(&$1, &$3, &$$);}
+| tNUMBER {mp_set_from_mp(&$1, &$$);}
+;
+
+
+variable:
+ term {mp_set_from_mp(&$1, &$$);}
+| tFUNCTION exp {if (!get_function(yyscanner, $1, &$2, &$$)) YYABORT; free($1);}
+| tFUNCTION tSUPNUM exp {if (!get_function(yyscanner, $1, &$3, &$$)) YYABORT; mp_xpowy_integer(&$$, $2, &$$); free($1);}
+| tFUNCTION tNSUPNUM exp {if (!get_inverse_function(yyscanner, $1, &$3, &$$)) YYABORT; mp_xpowy_integer(&$$, -$2, &$$); free($1);}
+| tVARIABLE tSUPNUM exp {set_error(yyscanner, PARSER_ERR_UNKNOWN_FUNCTION, $1); free($1); YYABORT;}
+| tSUBNUM tROOT exp {mp_root(&$3, $1, &$$);}
+| tROOT exp {mp_sqrt(&$2, &$$);}
+| tROOT3 exp {mp_root(&$2, 3, &$$);}
+| tROOT4 exp {mp_root(&$2, 4, &$$);}
+;
+
+term:
+ tVARIABLE {if (!get_variable(yyscanner, $1, 1, &$$)) YYABORT; free($1);}
+| tVARIABLE tSUPNUM {if (!get_variable(yyscanner, $1, $2, &$$)) YYABORT; free($1);}
+| term term {mp_multiply(&$1, &$2, &$$);}
+;
+
+%%
diff --git a/src/mp-equation-private.h b/src/mp-equation-private.h
new file mode 100644
index 0000000..7ef96c2
--- /dev/null
+++ b/src/mp-equation-private.h
@@ -0,0 +1,64 @@
+/* Copyright (c) 2004-2008 Sami Pietila
+ * Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef MP_EQUATION_PRIVATE_H
+#define MP_EQUATION_PRIVATE_H
+
+#include "mp-equation.h"
+
+typedef struct MPEquationParserState MPEquationParserState;
+
+/* State for parser */
+struct MPEquationParserState {
+ /* User provided options */
+ MPEquationOptions *options;
+
+ /* Function to check if a variable is defined */
+ int (*variable_is_defined)(MPEquationParserState *state, const char *name);
+
+ /* Function to get variable values */
+ int (*get_variable)(MPEquationParserState *state, const char *name, MPNumber *z);
+
+ /* Function to set variable values */
+ void (*set_variable)(MPEquationParserState *state, const char *name, const MPNumber *x);
+
+ /* Function to check if a function is defined */
+ int (*function_is_defined)(MPEquationParserState *state, const char *name);
+
+ /* Function to solve functions */
+ int (*get_function)(MPEquationParserState *state, const char *name, const MPNumber *x, MPNumber *z);
+
+ /* Function to convert units */
+ int (*convert)(MPEquationParserState *state, const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z);
+
+ // FIXME: get_operator??
+
+ /* Error returned from parser */
+ int error;
+
+ /* Name of token where error occured */
+ char *error_token;
+
+ /* Value returned from parser */
+ MPNumber ret;
+};
+
+int _mp_equation_error(void *yylloc, MPEquationParserState *state, char *text);
+
+#endif
diff --git a/src/mp-equation.c b/src/mp-equation.c
new file mode 100644
index 0000000..34f698b
--- /dev/null
+++ b/src/mp-equation.c
@@ -0,0 +1,493 @@
+/* Copyright (c) 2004-2008 Sami Pietila
+ * Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <ctype.h>
+
+#include "mp-equation-private.h"
+#include "mp-equation-parser.h"
+#include "mp-equation-lexer.h"
+
+extern int _mp_equation_parse(yyscan_t yyscanner);
+
+
+static int
+variable_is_defined(MPEquationParserState *state, const char *name)
+{
+ /* FIXME: Make more generic */
+ if (strcmp(name, "e") == 0 || strcmp(name, "i") == 0 || strcmp(name, "π") == 0)
+ return 1;
+ if (state->options->variable_is_defined)
+ return state->options->variable_is_defined(name, state->options->callback_data);
+ return 0;
+}
+
+
+static int
+get_variable(MPEquationParserState *state, const char *name, MPNumber *z)
+{
+ int result = 1;
+
+ if (strcmp(name, "e") == 0)
+ mp_get_eulers(z);
+ else if (strcmp(name, "i") == 0)
+ mp_get_i(z);
+ else if (strcmp(name, "π") == 0)
+ mp_get_pi(z);
+ else if (state->options->get_variable)
+ result = state->options->get_variable(name, z, state->options->callback_data);
+ else
+ result = 0;
+
+ return result;
+}
+
+static void
+set_variable(MPEquationParserState *state, const char *name, const MPNumber *x)
+{
+ // Reserved words, e, π, mod, and, or, xor, not, abs, log, ln, sqrt, int, frac, sin, cos, ...
+ if (strcmp(name, "e") == 0 || strcmp(name, "i") == 0 || strcmp(name, "π") == 0)
+ return; // FALSE
+
+ if (state->options->set_variable)
+ state->options->set_variable(name, x, state->options->callback_data);
+}
+
+// FIXME: Accept "2sin" not "2 sin", i.e. let the tokenizer collect the multiple
+// Parser then distinguishes between "sin"="s*i*n" or "sin5" = "sin 5" = "sin(5)"
+// i.e. numbers+letters = variable or function depending on following arg
+// letters+numbers = numbers+letters+numbers = function
+
+
+int
+sub_atoi(const char *data)
+{
+ int i, value = 0;
+ const char *digits[] = {"₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉", NULL};
+
+ do {
+ for(i = 0; digits[i] != NULL && strncmp(data, digits[i], strlen(digits[i])) != 0; i++);
+ if(digits[i] == NULL)
+ return -1;
+ data += strlen(digits[i]);
+ value = value * 10 + i;
+ } while(*data != '\0');
+
+ return value;
+}
+
+int
+super_atoi(const char *data)
+{
+ int i, sign = 1, value = 0;
+ const char *digits[11] = {"⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹", NULL};
+
+ if(strncmp(data, "⁻", strlen("⁻")) == 0) {
+ sign = -1;
+ data += strlen("⁻");
+ }
+
+ do {
+ for(i = 0; digits[i] != NULL && strncmp(data, digits[i], strlen(digits[i])) != 0; i++);
+ if(digits[i] == NULL)
+ return 0;
+ value = value * 10 + i;
+ data += strlen(digits[i]);
+ } while(*data != '\0');
+
+ return sign * value;
+}
+
+
+static int
+function_is_defined(MPEquationParserState *state, const char *name)
+{
+ char *c, *lower_name;
+
+ lower_name = strdup(name);
+ for (c = lower_name; *c; c++)
+ *c = tolower(*c);
+
+ /* FIXME: Make more generic */
+ if (strcmp(lower_name, "log") == 0 ||
+ (strncmp(lower_name, "log", 3) == 0 && sub_atoi(lower_name + 3) >= 0) ||
+ strcmp(lower_name, "ln") == 0 ||
+ strcmp(lower_name, "sqrt") == 0 ||
+ strcmp(lower_name, "abs") == 0 ||
+ strcmp(lower_name, "sgn") == 0 ||
+ strcmp(lower_name, "arg") == 0 ||
+ strcmp(lower_name, "conj") == 0 ||
+ strcmp(lower_name, "int") == 0 ||
+ strcmp(lower_name, "frac") == 0 ||
+ strcmp(lower_name, "floor") == 0 ||
+ strcmp(lower_name, "ceil") == 0 ||
+ strcmp(lower_name, "round") == 0 ||
+ strcmp(lower_name, "re") == 0 ||
+ strcmp(lower_name, "im") == 0 ||
+ strcmp(lower_name, "sin") == 0 || strcmp(lower_name, "cos") == 0 || strcmp(lower_name, "tan") == 0 ||
+ strcmp(lower_name, "sin⁻¹") == 0 || strcmp(lower_name, "cos⁻¹") == 0 || strcmp(lower_name, "tan⁻¹") == 0 ||
+ strcmp(lower_name, "sinh") == 0 || strcmp(lower_name, "cosh") == 0 || strcmp(lower_name, "tanh") == 0 ||
+ strcmp(lower_name, "sinh⁻¹") == 0 || strcmp(lower_name, "cosh⁻¹") == 0 || strcmp(lower_name, "tanh⁻¹") == 0 ||
+ strcmp(lower_name, "asinh") == 0 || strcmp(lower_name, "acosh") == 0 || strcmp(lower_name, "atanh") == 0 ||
+ strcmp(lower_name, "ones") == 0 ||
+ strcmp(lower_name, "twos") == 0) {
+ g_free (lower_name);
+ return 1;
+ }
+ g_free (lower_name);
+
+ if (state->options->function_is_defined)
+ return state->options->function_is_defined(name, state->options->callback_data);
+ return 0;
+}
+
+
+static int
+get_function(MPEquationParserState *state, const char *name, const MPNumber *x, MPNumber *z)
+{
+ char *c, *lower_name;
+ int result = 1;
+
+ lower_name = strdup(name);
+ for (c = lower_name; *c; c++)
+ *c = tolower(*c);
+
+ // FIXME: Re Im ?
+
+ if (strcmp(lower_name, "log") == 0)
+ mp_logarithm(10, x, z); // FIXME: Default to ln
+ else if (strncmp(lower_name, "log", 3) == 0) {
+ int base;
+
+ base = sub_atoi(lower_name + 3);
+ if (base < 0)
+ result = 0;
+ else
+ mp_logarithm(base, x, z);
+ }
+ else if (strcmp(lower_name, "ln") == 0)
+ mp_ln(x, z);
+ else if (strcmp(lower_name, "sqrt") == 0) // √x
+ mp_sqrt(x, z);
+ else if (strcmp(lower_name, "abs") == 0) // |x|
+ mp_abs(x, z);
+ else if (strcmp(lower_name, "sgn") == 0)
+ mp_sgn(x, z);
+ else if (strcmp(lower_name, "arg") == 0)
+ mp_arg(x, state->options->angle_units, z);
+ else if (strcmp(lower_name, "conj") == 0)
+ mp_conjugate(x, z);
+ else if (strcmp(lower_name, "int") == 0)
+ mp_integer_component(x, z);
+ else if (strcmp(lower_name, "frac") == 0)
+ mp_fractional_component(x, z);
+ else if (strcmp(lower_name, "floor") == 0)
+ mp_floor(x, z);
+ else if (strcmp(lower_name, "ceil") == 0)
+ mp_ceiling(x, z);
+ else if (strcmp(lower_name, "round") == 0)
+ mp_round(x, z);
+ else if (strcmp(lower_name, "re") == 0)
+ mp_real_component(x, z);
+ else if (strcmp(lower_name, "im") == 0)
+ mp_imaginary_component(x, z);
+ else if (strcmp(lower_name, "sin") == 0)
+ mp_sin(x, state->options->angle_units, z);
+ else if (strcmp(lower_name, "cos") == 0)
+ mp_cos(x, state->options->angle_units, z);
+ else if (strcmp(lower_name, "tan") == 0)
+ mp_tan(x, state->options->angle_units, z);
+ else if (strcmp(lower_name, "sin⁻¹") == 0 || strcmp(lower_name, "asin") == 0)
+ mp_asin(x, state->options->angle_units, z);
+ else if (strcmp(lower_name, "cos⁻¹") == 0 || strcmp(lower_name, "acos") == 0)
+ mp_acos(x, state->options->angle_units, z);
+ else if (strcmp(lower_name, "tan⁻¹") == 0 || strcmp(lower_name, "atan") == 0)
+ mp_atan(x, state->options->angle_units, z);
+ else if (strcmp(lower_name, "sinh") == 0)
+ mp_sinh(x, z);
+ else if (strcmp(lower_name, "cosh") == 0)
+ mp_cosh(x, z);
+ else if (strcmp(lower_name, "tanh") == 0)
+ mp_tanh(x, z);
+ else if (strcmp(lower_name, "sinh⁻¹") == 0 || strcmp(lower_name, "asinh") == 0)
+ mp_asinh(x, z);
+ else if (strcmp(lower_name, "cosh⁻¹") == 0 || strcmp(lower_name, "acosh") == 0)
+ mp_acosh(x, z);
+ else if (strcmp(lower_name, "tanh⁻¹") == 0 || strcmp(lower_name, "atanh") == 0)
+ mp_atanh(x, z);
+ else if (strcmp(lower_name, "ones") == 0)
+ mp_ones_complement(x, state->options->wordlen, z);
+ else if (strcmp(lower_name, "twos") == 0)
+ mp_twos_complement(x, state->options->wordlen, z);
+ else if (state->options->get_function)
+ result = state->options->get_function(name, x, z, state->options->callback_data);
+ else
+ result = 0;
+
+ free(lower_name);
+
+ return result;
+}
+
+
+static int
+do_convert(const char *units[][2], const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z)
+{
+ int x_index, z_index;
+ MPNumber x_factor, z_factor;
+
+ for (x_index = 0; units[x_index][0] != NULL && strcmp(units[x_index][0], x_units) != 0; x_index++);
+ if (units[x_index][0] == NULL)
+ return 0;
+ for (z_index = 0; units[z_index][0] != NULL && strcmp(units[z_index][0], z_units) != 0; z_index++);
+ if (units[z_index][0] == NULL)
+ return 0;
+
+ mp_set_from_string(units[x_index][1], 10, &x_factor);
+ mp_set_from_string(units[z_index][1], 10, &z_factor);
+ mp_multiply(x, &x_factor, z);
+ mp_divide(z, &z_factor, z);
+
+ return 1;
+}
+
+
+static int
+convert(MPEquationParserState *state, const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z)
+{
+ const char *length_units[][2] = {
+ {"parsec", "30857000000000000"},
+ {"parsecs", "30857000000000000"},
+ {"pc", "30857000000000000"},
+ {"lightyear", "9460730472580800"},
+ {"lightyears", "9460730472580800"},
+ {"ly", "9460730472580800"},
+ {"au", "149597870691"},
+ {"nm", "1852000"},
+ {"mile", "1609.344"},
+ {"miles", "1609.344"},
+ {"mi", "1609.344"},
+ {"kilometer", "1000"},
+ {"kilometers", "1000"},
+ {"km", "1000"},
+ {"kms", "1000"},
+ {"cable", "219.456"},
+ {"cables", "219.456"},
+ {"cb", "219.456"},
+ {"fathom", "1.8288"},
+ {"fathoms", "1.8288"},
+ {"ftm", "1.8288"},
+ {"meter", "1"},
+ {"meters", "1"},
+ {"m", "1"},
+ {"yard", "0.9144"},
+ {"yd", "0.9144"},
+ {"foot", "0.3048"},
+ {"feet", "0.3048"},
+ {"ft", "0.3048"},
+ {"inch", "0.0254"},
+ {"inches", "0.0254"},
+ {"centimeter", "0.01"},
+ {"centimeters", "0.01"},
+ {"cm", "0.01"},
+ {"cms", "0.01"},
+ {"millimeter", "0.001"},
+ {"millimeters", "0.001"},
+ {"mm", "0.001"},
+ {"micrometer", "0.000001"},
+ {"micrometers", "0.000001"},
+ {"um", "0.000001"},
+ {"nanometer", "0.000000001"},
+ {"nanometers", "0.000000001"},
+ {NULL, NULL}
+ };
+
+ const char *area_units[][2] = {
+ {"hectare", "10000"},
+ {"hectares", "10000"},
+ {"acre", "4046.8564224"},
+ {"acres", "4046.8564224"},
+ {"m²", "1"},
+ {"cm²", "0.001"},
+ {"mm²", "0.000001"},
+ {NULL, NULL}
+ };
+
+ const char *volume_units[][2] = {
+ {"m³", "1000"},
+ {"gallon", "3.785412"},
+ {"gallons", "3.785412"},
+ {"gal", "3.785412"},
+ {"litre", "1"},
+ {"litres", "1"},
+ {"liter", "1"},
+ {"liters", "1"},
+ {"L", "1"},
+ {"quart", "0.9463529"},
+ {"quarts", "0.9463529"},
+ {"qt", "0.9463529"},
+ {"pint", "0.4731765"},
+ {"pints", "0.4731765"},
+ {"pt", "0.4731765"},
+ {"millilitre", "0.001"},
+ {"millilitres", "0.001"},
+ {"milliliter", "0.001"},
+ {"milliliters", "0.001"},
+ {"mL", "0.001"},
+ {"cm³", "0.001"},
+ {"mm³", "0.000001"},
+ {NULL, NULL}
+ };
+
+ const char *weight_units[][2] = {
+ {"tonne", "1000"},
+ {"tonnes", "1000"},
+ {"kilograms", "1"},
+ {"kilogramme", "1"},
+ {"kilogrammes", "1"},
+ {"kg", "1"},
+ {"kgs", "1"},
+ {"pound", "0.45359237"},
+ {"pounds", "0.45359237"},
+ {"lb", "0.45359237"},
+ {"ounce", "0.002834952"},
+ {"ounces", "0.002834952"},
+ {"oz", "0.002834952"},
+ {"gram", "0.001"},
+ {"grams", "0.001"},
+ {"gramme", "0.001"},
+ {"grammes", "0.001"},
+ {"g", "0.001"},
+ {NULL, NULL}
+ };
+
+ const char *time_units[][2] = {
+ {"year", "31557600"},
+ {"years", "31557600"},
+ {"day", "86400"},
+ {"days", "86400"},
+ {"hour", "3600"},
+ {"hours", "3600"},
+ {"minute", "60"},
+ {"minutes", "60"},
+ {"second", "1"},
+ {"seconds", "1"},
+ {"s", "1"},
+ {"millisecond", "0.001"},
+ {"milliseconds", "0.001"},
+ {"ms", "0.001"},
+ {"microsecond", "0.000001"},
+ {"microseconds", "0.000001"},
+ {"us", "0.000001"},
+ {NULL, NULL}
+ };
+
+ if (do_convert(length_units, x, x_units, z_units, z) ||
+ do_convert(area_units, x, x_units, z_units, z) ||
+ do_convert(volume_units, x, x_units, z_units, z) ||
+ do_convert(weight_units, x, x_units, z_units, z) ||
+ do_convert(time_units, x, x_units, z_units, z))
+ return 1;
+
+ if (state->options->convert)
+ return state->options->convert(x, x_units, z_units, z, state->options->callback_data);
+
+ return 0;
+}
+
+
+MPErrorCode
+mp_equation_parse(const char *expression, MPEquationOptions *options, MPNumber *result, char **error_token)
+{
+ int ret;
+ MPEquationParserState state;
+ yyscan_t yyscanner;
+ YY_BUFFER_STATE buffer;
+
+ if (!(expression && result) || strlen(expression) == 0)
+ return PARSER_ERR_INVALID;
+
+ memset(&state, 0, sizeof(MPEquationParserState));
+ state.options = options;
+ state.variable_is_defined = variable_is_defined;
+ state.get_variable = get_variable;
+ state.set_variable = set_variable;
+ state.function_is_defined = function_is_defined;
+ state.get_function = get_function;
+ state.convert = convert;
+ state.error = 0;
+
+ mp_clear_error();
+
+ _mp_equation_lex_init_extra(&state, &yyscanner);
+ buffer = _mp_equation__scan_string(expression, yyscanner);
+
+ ret = _mp_equation_parse(yyscanner);
+ if (state.error_token != NULL && error_token != NULL) {
+ *error_token = state.error_token;
+ }
+
+ _mp_equation__delete_buffer(buffer, yyscanner);
+ _mp_equation_lex_destroy(yyscanner);
+
+ /* Error during parsing */
+ if (state.error)
+ return state.error;
+
+ if (mp_get_error())
+ return PARSER_ERR_MP;
+
+ /* Failed to parse */
+ if (ret)
+ return PARSER_ERR_INVALID;
+
+ mp_set_from_mp(&state.ret, result);
+
+ return PARSER_ERR_NONE;
+}
+
+
+const char *
+mp_error_code_to_string(MPErrorCode error_code)
+{
+ switch(error_code)
+ {
+ case PARSER_ERR_NONE:
+ return "PARSER_ERR_NONE";
+ case PARSER_ERR_INVALID:
+ return "PARSER_ERR_INVALID";
+ case PARSER_ERR_OVERFLOW:
+ return "PARSER_ERR_OVERFLOW";
+ case PARSER_ERR_UNKNOWN_VARIABLE:
+ return "PARSER_ERR_UNKNOWN_VARIABLE";
+ case PARSER_ERR_UNKNOWN_FUNCTION:
+ return "PARSER_ERR_UNKNOWN_FUNCTION";
+ case PARSER_ERR_UNKNOWN_CONVERSION:
+ return "PARSER_ERR_UNKNOWN_CONVERSION";
+ case PARSER_ERR_MP:
+ return "PARSER_ERR_MP";
+ default:
+ return "Unknown parser error";
+ }
+}
+
+
+int _mp_equation_error(void *yylloc, MPEquationParserState *state, char *text)
+{
+ return 0;
+}
diff --git a/src/mp-equation.h b/src/mp-equation.h
new file mode 100644
index 0000000..60e6690
--- /dev/null
+++ b/src/mp-equation.h
@@ -0,0 +1,77 @@
+/* Copyright (c) 2004-2008 Sami Pietila
+ * Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef MP_EQUATION_H
+#define MP_EQUATION_H
+
+#include "mp.h"
+
+typedef enum
+{
+ PARSER_ERR_NONE = 0,
+ PARSER_ERR_INVALID,
+ PARSER_ERR_OVERFLOW,
+ PARSER_ERR_UNKNOWN_VARIABLE,
+ PARSER_ERR_UNKNOWN_FUNCTION,
+ PARSER_ERR_UNKNOWN_CONVERSION,
+ PARSER_ERR_MP
+} MPErrorCode;
+
+/* Options for parser */
+typedef struct {
+ /* Default number base */
+ int base;
+
+ /* The wordlength for binary operations in bits (e.g. 8, 16, 32) */
+ int wordlen;
+
+ /* Units for angles (e.g. radians, degrees) */
+ MPAngleUnit angle_units;
+
+ // FIXME:
+ // int enable_builtins;
+
+ /* Data to pass to callbacks */
+ void *callback_data;
+
+ /* Function to check if a variable is defined */
+ int (*variable_is_defined)(const char *name, void *data);
+
+ /* Function to get variable values */
+ int (*get_variable)(const char *name, MPNumber *z, void *data);
+
+ /* Function to set variable values */
+ void (*set_variable)(const char *name, const MPNumber *x, void *data);
+
+ /* Function to check if a function is defined */
+ int (*function_is_defined)(const char *name, void *data);
+
+ /* Function to solve functions */
+ int (*get_function)(const char *name, const MPNumber *x, MPNumber *z, void *data);
+
+ /* Function to convert units */
+ int (*convert)(const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z, void *data);
+} MPEquationOptions;
+
+MPErrorCode mp_equation_parse(const char *expression, MPEquationOptions *options, MPNumber *result, char **error_token);
+const char *mp_error_code_to_string(MPErrorCode error_code);
+
+int sub_atoi(const char *data);
+int super_atoi(const char *data);
+#endif
diff --git a/src/mp-private.h b/src/mp-private.h
new file mode 100644
index 0000000..94b1530
--- /dev/null
+++ b/src/mp-private.h
@@ -0,0 +1,47 @@
+/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef MP_INTERNAL_H
+#define MP_INTERNAL_H
+
+#include <glib/gi18n.h>
+
+/* If we're not using GNU C, elide __attribute__ */
+#ifndef __GNUC__
+# define __attribute__(x) /*NOTHING*/
+#endif
+
+#define min(a, b) ((a) <= (b) ? (a) : (b))
+#define max(a, b) ((a) >= (b) ? (a) : (b))
+
+//2E0 BELOW ENSURES AT LEAST ONE GUARD DIGIT
+//MP.t = (int) ((float) (accuracy) * log((float)10.) / log((float) MP_BASE) + (float) 2.0);
+//if (MP.t > MP_SIZE) {
+// mperr("MP_SIZE TOO SMALL IN CALL TO MPSET, INCREASE MP_SIZE AND DIMENSIONS OF MP ARRAYS TO AT LEAST %d ***", MP.t);
+// MP.t = MP_SIZE;
+//}
+#define MP_T 100
+
+void mperr(const char *format, ...) __attribute__((format(printf, 1, 2)));
+void mp_gcd(int64_t *, int64_t *);
+void mp_normalize(MPNumber *);
+void convert_to_radians(const MPNumber *x, MPAngleUnit unit, MPNumber *z);
+void convert_from_radians(const MPNumber *x, MPAngleUnit unit, MPNumber *z);
+
+#endif /* MP_INTERNAL_H */
diff --git a/src/mp-trigonometric.c b/src/mp-trigonometric.c
new file mode 100644
index 0000000..2ebc676
--- /dev/null
+++ b/src/mp-trigonometric.c
@@ -0,0 +1,628 @@
+/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <libintl.h>
+
+#include "mp.h"
+#include "mp-private.h"
+
+static int
+mp_compare_mp_to_int(const MPNumber *x, int i)
+{
+ MPNumber t;
+ mp_set_from_integer(i, &t);
+ return mp_compare_mp_to_mp(x, &t);
+}
+
+
+/* Convert x to radians */
+void
+convert_to_radians(const MPNumber *x, MPAngleUnit unit, MPNumber *z)
+{
+ MPNumber t1, t2;
+
+ switch(unit) {
+ default:
+ case MP_RADIANS:
+ mp_set_from_mp(x, z);
+ break;
+
+ case MP_DEGREES:
+ mp_get_pi(&t1);
+ mp_multiply(x, &t1, &t2);
+ mp_divide_integer(&t2, 180, z);
+ break;
+
+ case MP_GRADIANS:
+ mp_get_pi(&t1);
+ mp_multiply(x, &t1, &t2);
+ mp_divide_integer(&t2, 200, z);
+ break;
+ }
+}
+
+
+void
+mp_get_pi(MPNumber *z)
+{
+ mp_set_from_string("3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679", 10, z);
+}
+
+
+void
+convert_from_radians(const MPNumber *x, MPAngleUnit unit, MPNumber *z)
+{
+ MPNumber t1, t2;
+
+ switch (unit) {
+ default:
+ case MP_RADIANS:
+ mp_set_from_mp(x, z);
+ break;
+
+ case MP_DEGREES:
+ mp_multiply_integer(x, 180, &t2);
+ mp_get_pi(&t1);
+ mp_divide(&t2, &t1, z);
+ break;
+
+ case MP_GRADIANS:
+ mp_multiply_integer(x, 200, &t2);
+ mp_get_pi(&t1);
+ mp_divide(&t2, &t1, z);
+ break;
+ }
+}
+
+
+/* z = sin(x) -1 >= x >= 1, do_sin = 1
+ * z = cos(x) -1 >= x >= 1, do_sin = 0
+ */
+static void
+mpsin1(const MPNumber *x, MPNumber *z, int do_sin)
+{
+ int i, b2;
+ MPNumber t1, t2;
+
+ /* sin(0) = 0, cos(0) = 1 */
+ if (mp_is_zero(x)) {
+ if (do_sin == 0)
+ mp_set_from_integer(1, z);
+ else
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ mp_multiply(x, x, &t2);
+ if (mp_compare_mp_to_int(&t2, 1) > 0) {
+ mperr("*** ABS(X) > 1 IN CALL TO MPSIN1 ***");
+ }
+
+ if (do_sin == 0) {
+ mp_set_from_integer(1, &t1);
+ mp_set_from_integer(0, z);
+ i = 1;
+ } else {
+ mp_set_from_mp(x, &t1);
+ mp_set_from_mp(&t1, z);
+ i = 2;
+ }
+
+ /* Taylor series */
+ /* POWER SERIES LOOP. REDUCE T IF POSSIBLE */
+ b2 = 2 * max(MP_BASE, 64);
+ do {
+ if (MP_T + t1.exponent <= 0)
+ break;
+
+ /* IF I*(I+1) IS NOT REPRESENTABLE AS AN INTEGER, THE FOLLOWING
+ * DIVISION BY I*(I+1) HAS TO BE SPLIT UP.
+ */
+ mp_multiply(&t2, &t1, &t1);
+ if (i > b2) {
+ mp_divide_integer(&t1, -i, &t1);
+ mp_divide_integer(&t1, i + 1, &t1);
+ } else {
+ mp_divide_integer(&t1, -i * (i + 1), &t1);
+ }
+ mp_add(&t1, z, z);
+
+ i += 2;
+ } while (t1.sign != 0);
+
+ if (do_sin == 0)
+ mp_add_integer(z, 1, z);
+}
+
+
+static void
+mp_sin_real(const MPNumber *x, MPAngleUnit unit, MPNumber *z)
+{
+ int xs;
+ MPNumber x_radians;
+
+ /* sin(0) = 0 */
+ if (mp_is_zero(x)) {
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ convert_to_radians(x, unit, &x_radians);
+
+ xs = x_radians.sign;
+ mp_abs(&x_radians, &x_radians);
+
+ /* USE MPSIN1 IF ABS(X) <= 1 */
+ if (mp_compare_mp_to_int(&x_radians, 1) <= 0) {
+ mpsin1(&x_radians, z, 1);
+ }
+ /* FIND ABS(X) MODULO 2PI */
+ else {
+ mp_get_pi(z);
+ mp_divide_integer(z, 4, z);
+ mp_divide(&x_radians, z, &x_radians);
+ mp_divide_integer(&x_radians, 8, &x_radians);
+ mp_fractional_component(&x_radians, &x_radians);
+
+ /* SUBTRACT 1/2, SAVE SIGN AND TAKE ABS */
+ mp_add_fraction(&x_radians, -1, 2, &x_radians);
+ xs = -xs * x_radians.sign;
+ if (xs == 0) {
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ x_radians.sign = 1;
+ mp_multiply_integer(&x_radians, 4, &x_radians);
+
+ /* IF NOT LESS THAN 1, SUBTRACT FROM 2 */
+ if (x_radians.exponent > 0)
+ mp_add_integer(&x_radians, -2, &x_radians);
+
+ if (mp_is_zero(&x_radians)) {
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ x_radians.sign = 1;
+ mp_multiply_integer(&x_radians, 2, &x_radians);
+
+ /* NOW REDUCED TO FIRST QUADRANT, IF LESS THAN PI/4 USE
+ * POWER SERIES, ELSE COMPUTE COS OF COMPLEMENT
+ */
+ if (x_radians.exponent > 0) {
+ mp_add_integer(&x_radians, -2, &x_radians);
+ mp_multiply(&x_radians, z, &x_radians);
+ mpsin1(&x_radians, z, 0);
+ } else {
+ mp_multiply(&x_radians, z, &x_radians);
+ mpsin1(&x_radians, z, 1);
+ }
+ }
+
+ z->sign = xs;
+}
+
+
+static void
+mp_cos_real(const MPNumber *x, MPAngleUnit unit, MPNumber *z)
+{
+ /* cos(0) = 1 */
+ if (mp_is_zero(x)) {
+ mp_set_from_integer(1, z);
+ return;
+ }
+
+ convert_to_radians(x, unit, z);
+
+ /* Use power series if |x| <= 1 */
+ mp_abs(z, z);
+ if (mp_compare_mp_to_int(z, 1) <= 0) {
+ mpsin1(z, z, 0);
+ } else {
+ MPNumber t;
+
+ /* cos(x) = sin(π/2 - |x|) */
+ mp_get_pi(&t);
+ mp_divide_integer(&t, 2, &t);
+ mp_subtract(&t, z, z);
+ mp_sin(z, MP_RADIANS, z);
+ }
+}
+
+
+void
+mp_sin(const MPNumber *x, MPAngleUnit unit, MPNumber *z)
+{
+ if (mp_is_complex(x)) {
+ MPNumber x_real, x_im, z_real, z_im, t;
+
+ mp_real_component(x, &x_real);
+ mp_imaginary_component(x, &x_im);
+
+ mp_sin_real(&x_real, unit, &z_real);
+ mp_cosh(&x_im, &t);
+ mp_multiply(&z_real, &t, &z_real);
+
+ mp_cos_real(&x_real, unit, &z_im);
+ mp_sinh(&x_im, &t);
+ mp_multiply(&z_im, &t, &z_im);
+
+ mp_set_from_complex(&z_real, &z_im, z);
+ }
+ else
+ mp_sin_real(x, unit, z);
+}
+
+
+void
+mp_cos(const MPNumber *x, MPAngleUnit unit, MPNumber *z)
+{
+ if (mp_is_complex(x)) {
+ MPNumber x_real, x_im, z_real, z_im, t;
+
+ mp_real_component(x, &x_real);
+ mp_imaginary_component(x, &x_im);
+
+ mp_cos_real(&x_real, unit, &z_real);
+ mp_cosh(&x_im, &t);
+ mp_multiply(&z_real, &t, &z_real);
+
+ mp_sin_real(&x_real, unit, &z_im);
+ mp_sinh(&x_im, &t);
+ mp_multiply(&z_im, &t, &z_im);
+ mp_invert_sign(&z_im, &z_im);
+
+ mp_set_from_complex(&z_real, &z_im, z);
+ }
+ else
+ mp_cos_real(x, unit, z);
+}
+
+
+void
+mp_tan(const MPNumber *x, MPAngleUnit unit, MPNumber *z)
+{
+ MPNumber cos_x, sin_x;
+
+ /* Check for undefined values */
+ mp_cos(x, unit, &cos_x);
+ if (mp_is_zero(&cos_x)) {
+ /* Translators: Error displayed when tangent value is undefined */
+ mperr(_("Tangent is undefined for angles that are multiples of π (180°) from π∕2 (90°)"));
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* tan(x) = sin(x) / cos(x) */
+ mp_sin(x, unit, &sin_x);
+ mp_divide(&sin_x, &cos_x, z);
+}
+
+
+void
+mp_asin(const MPNumber *x, MPAngleUnit unit, MPNumber *z)
+{
+ MPNumber t1, t2;
+
+ /* asin⁻¹(0) = 0 */
+ if (mp_is_zero(x)) {
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* sin⁻¹(x) = tan⁻¹(x / √(1 - x²)), |x| < 1 */
+ if (x->exponent <= 0) {
+ mp_set_from_integer(1, &t1);
+ mp_set_from_mp(&t1, &t2);
+ mp_subtract(&t1, x, &t1);
+ mp_add(&t2, x, &t2);
+ mp_multiply(&t1, &t2, &t2);
+ mp_root(&t2, -2, &t2);
+ mp_multiply(x, &t2, z);
+ mp_atan(z, unit, z);
+ return;
+ }
+
+ /* sin⁻¹(1) = π/2, sin⁻¹(-1) = -π/2 */
+ mp_set_from_integer(x->sign, &t2);
+ if (mp_is_equal(x, &t2)) {
+ mp_get_pi(z);
+ mp_divide_integer(z, 2 * t2.sign, z);
+ convert_from_radians(z, unit, z);
+ return;
+ }
+
+ /* Translators: Error displayed when inverse sine value is undefined */
+ mperr(_("Inverse sine is undefined for values outside [-1, 1]"));
+ mp_set_from_integer(0, z);
+}
+
+
+void
+mp_acos(const MPNumber *x, MPAngleUnit unit, MPNumber *z)
+{
+ MPNumber t1, t2;
+ MPNumber MPn1, pi, MPy;
+
+ mp_get_pi(&pi);
+ mp_set_from_integer(1, &t1);
+ mp_set_from_integer(-1, &MPn1);
+
+ if (mp_is_greater_than(x, &t1) || mp_is_less_than(x, &MPn1)) {
+ /* Translators: Error displayed when inverse cosine value is undefined */
+ mperr(_("Inverse cosine is undefined for values outside [-1, 1]"));
+ mp_set_from_integer(0, z);
+ } else if (mp_is_zero(x)) {
+ mp_divide_integer(&pi, 2, z);
+ } else if (mp_is_equal(x, &t1)) {
+ mp_set_from_integer(0, z);
+ } else if (mp_is_equal(x, &MPn1)) {
+ mp_set_from_mp(&pi, z);
+ } else {
+ /* cos⁻¹(x) = tan⁻¹(√(1 - x²) / x) */
+ mp_multiply(x, x, &t2);
+ mp_subtract(&t1, &t2, &t2);
+ mp_sqrt(&t2, &t2);
+ mp_divide(&t2, x, &t2);
+ mp_atan(&t2, MP_RADIANS, &MPy);
+ if (x->sign > 0) {
+ mp_set_from_mp(&MPy, z);
+ } else {
+ mp_add(&MPy, &pi, z);
+ }
+ }
+
+ convert_from_radians(z, unit, z);
+}
+
+
+void
+mp_atan(const MPNumber *x, MPAngleUnit unit, MPNumber *z)
+{
+ int i, q;
+ float rx = 0.0;
+ MPNumber t1, t2;
+
+ if (mp_is_zero(x)) {
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ mp_set_from_mp(x, &t2);
+ if (abs(x->exponent) <= 2)
+ rx = mp_cast_to_float(x);
+
+ /* REDUCE ARGUMENT IF NECESSARY BEFORE USING SERIES */
+ q = 1;
+ while (t2.exponent >= 0)
+ {
+ if (t2.exponent == 0 && 2 * (t2.fraction[0] + 1) <= MP_BASE)
+ break;
+
+ q *= 2;
+
+ /* t = t / (√(t² + 1) + 1) */
+ mp_multiply(&t2, &t2, z);
+ mp_add_integer(z, 1, z);
+ mp_sqrt(z, z);
+ mp_add_integer(z, 1, z);
+ mp_divide(&t2, z, &t2);
+ }
+
+ /* USE POWER SERIES NOW ARGUMENT IN (-0.5, 0.5) */
+ mp_set_from_mp(&t2, z);
+ mp_multiply(&t2, &t2, &t1);
+
+ /* SERIES LOOP. REDUCE T IF POSSIBLE. */
+ for (i = 1; ; i += 2) {
+ if (MP_T + 2 + t2.exponent <= 1)
+ break;
+
+ mp_multiply(&t2, &t1, &t2);
+ mp_multiply_fraction(&t2, -i, i + 2, &t2);
+
+ mp_add(z, &t2, z);
+ if (mp_is_zero(&t2))
+ break;
+ }
+
+ /* CORRECT FOR ARGUMENT REDUCTION */
+ mp_multiply_integer(z, q, z);
+
+ /* CHECK THAT RELATIVE ERROR LESS THAN 0.01 UNLESS EXPONENT
+ * OF X IS LARGE (WHEN ATAN MIGHT NOT WORK)
+ */
+ if (abs(x->exponent) <= 2) {
+ float ry = mp_cast_to_float(z);
+ /* THE FOLLOWING MESSAGE MAY INDICATE THAT B**(T-1) IS TOO SMALL. */
+ if (fabs(ry - atan(rx)) >= fabs(ry) * 0.01)
+ mperr("*** ERROR OCCURRED IN MP_ATAN, RESULT INCORRECT ***");
+ }
+
+ convert_from_radians(z, unit, z);
+}
+
+
+void
+mp_sinh(const MPNumber *x, MPNumber *z)
+{
+ MPNumber abs_x;
+
+ /* sinh(0) = 0 */
+ if (mp_is_zero(x)) {
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* WORK WITH ABS(X) */
+ mp_abs(x, &abs_x);
+
+ /* If |x| < 1 USE MPEXP TO AVOID CANCELLATION, otherwise IF TOO LARGE MP_EPOWY GIVES ERROR MESSAGE */
+ if (abs_x.exponent <= 0) {
+ MPNumber exp_x, a, b;
+
+ /* ((e^|x| + 1) * (e^|x| - 1)) / e^|x| */
+ // FIXME: Solves to e^|x| - e^-|x|, why not lower branch always? */
+ mp_epowy(&abs_x, &exp_x);
+ mp_add_integer(&exp_x, 1, &a);
+ mp_add_integer(&exp_x, -1, &b);
+ mp_multiply(&a, &b, z);
+ mp_divide(z, &exp_x, z);
+ }
+ else {
+ MPNumber exp_x;
+
+ /* e^|x| - e^-|x| */
+ mp_epowy(&abs_x, &exp_x);
+ mp_reciprocal(&exp_x, z);
+ mp_subtract(&exp_x, z, z);
+ }
+
+ /* DIVIDE BY TWO AND RESTORE SIGN */
+ mp_divide_integer(z, 2, z);
+ mp_multiply_integer(z, x->sign, z);
+}
+
+
+void
+mp_cosh(const MPNumber *x, MPNumber *z)
+{
+ MPNumber t;
+
+ /* cosh(0) = 1 */
+ if (mp_is_zero(x)) {
+ mp_set_from_integer(1, z);
+ return;
+ }
+
+ /* cosh(x) = (e^x + e^-x) / 2 */
+ mp_abs(x, &t);
+ mp_epowy(&t, &t);
+ mp_reciprocal(&t, z);
+ mp_add(&t, z, z);
+ mp_divide_integer(z, 2, z);
+}
+
+
+void
+mp_tanh(const MPNumber *x, MPNumber *z)
+{
+ float r__1;
+ MPNumber t;
+
+ /* tanh(0) = 0 */
+ if (mp_is_zero(x)) {
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ mp_abs(x, &t);
+
+ /* SEE IF ABS(X) SO LARGE THAT RESULT IS +-1 */
+ r__1 = (float) MP_T * 0.5 * log((float) MP_BASE);
+ mp_set_from_float(r__1, z);
+ if (mp_compare_mp_to_mp(&t, z) > 0) {
+ mp_set_from_integer(x->sign, z);
+ return;
+ }
+
+ /* If |x| >= 1/2 use ?, otherwise use ? to avoid cancellation */
+ /* |tanh(x)| = (e^|2x| - 1) / (e^|2x| + 1) */
+ mp_multiply_integer(&t, 2, &t);
+ if (t.exponent > 0) {
+ mp_epowy(&t, &t);
+ mp_add_integer(&t, -1, z);
+ mp_add_integer(&t, 1, &t);
+ mp_divide(z, &t, z);
+ } else {
+ mp_epowy(&t, &t);
+ mp_add_integer(&t, 1, z);
+ mp_divide(&t, z, z);
+ }
+
+ /* Restore sign */
+ z->sign = x->sign * z->sign;
+}
+
+
+void
+mp_asinh(const MPNumber *x, MPNumber *z)
+{
+ MPNumber t;
+
+ /* sinh⁻¹(x) = ln(x + √(x² + 1)) */
+ mp_multiply(x, x, &t);
+ mp_add_integer(&t, 1, &t);
+ mp_sqrt(&t, &t);
+ mp_add(x, &t, &t);
+ mp_ln(&t, z);
+}
+
+
+void
+mp_acosh(const MPNumber *x, MPNumber *z)
+{
+ MPNumber t;
+
+ /* Check x >= 1 */
+ mp_set_from_integer(1, &t);
+ if (mp_is_less_than(x, &t)) {
+ /* Translators: Error displayed when inverse hyperbolic cosine value is undefined */
+ mperr(_("Inverse hyperbolic cosine is undefined for values less than or equal to one"));
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* cosh⁻¹(x) = ln(x + √(x² - 1)) */
+ mp_multiply(x, x, &t);
+ mp_add_integer(&t, -1, &t);
+ mp_sqrt(&t, &t);
+ mp_add(x, &t, &t);
+ mp_ln(&t, z);
+}
+
+
+void
+mp_atanh(const MPNumber *x, MPNumber *z)
+{
+ MPNumber one, minus_one, n, d;
+
+ /* Check -1 <= x <= 1 */
+ mp_set_from_integer(1, &one);
+ mp_set_from_integer(-1, &minus_one);
+ if (mp_is_greater_equal(x, &one) || mp_is_less_equal(x, &minus_one)) {
+ /* Translators: Error displayed when inverse hyperbolic tangent value is undefined */
+ mperr(_("Inverse hyperbolic tangent is undefined for values outside [-1, 1]"));
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* atanh(x) = 0.5 * ln((1 + x) / (1 - x)) */
+ mp_add_integer(x, 1, &n);
+ mp_set_from_mp(x, &d);
+ mp_invert_sign(&d, &d);
+ mp_add_integer(&d, 1, &d);
+ mp_divide(&n, &d, z);
+ mp_ln(z, z);
+ mp_divide_integer(z, 2, z);
+}
diff --git a/src/mp.c b/src/mp.c
new file mode 100644
index 0000000..0e46b35
--- /dev/null
+++ b/src/mp.c
@@ -0,0 +1,2092 @@
+/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <errno.h>
+
+#include "mp.h"
+#include "mp-private.h"
+
+// FIXME: Re-add overflow and underflow detection
+
+char *mp_error = NULL;
+
+/* THIS ROUTINE IS CALLED WHEN AN ERROR CONDITION IS ENCOUNTERED, AND
+ * AFTER A MESSAGE HAS BEEN WRITTEN TO STDERR.
+ */
+void
+mperr(const char *format, ...)
+{
+ char text[1024];
+ va_list args;
+
+ va_start(args, format);
+ vsnprintf(text, 1024, format, args);
+ va_end(args);
+
+ if (mp_error)
+ free(mp_error);
+ mp_error = strdup(text);
+}
+
+
+const char *
+mp_get_error()
+{
+ return mp_error;
+}
+
+
+void mp_clear_error()
+{
+ if (mp_error)
+ free(mp_error);
+ mp_error = NULL;
+}
+
+
+/* ROUTINE CALLED BY MP_DIVIDE AND MP_SQRT TO ENSURE THAT
+ * RESULTS ARE REPRESENTED EXACTLY IN T-2 DIGITS IF THEY
+ * CAN BE. X IS AN MP NUMBER, I AND J ARE INTEGERS.
+ */
+static void
+mp_ext(int i, int j, MPNumber *x)
+{
+ int q, s;
+
+ if (mp_is_zero(x) || MP_T <= 2 || i == 0)
+ return;
+
+ /* COMPUTE MAXIMUM POSSIBLE ERROR IN THE LAST PLACE */
+ q = (j + 1) / i + 1;
+ s = MP_BASE * x->fraction[MP_T - 2] + x->fraction[MP_T - 1];
+
+ /* SET LAST TWO DIGITS TO ZERO */
+ if (s <= q) {
+ x->fraction[MP_T - 2] = 0;
+ x->fraction[MP_T - 1] = 0;
+ return;
+ }
+
+ if (s + q < MP_BASE * MP_BASE)
+ return;
+
+ /* ROUND UP HERE */
+ x->fraction[MP_T - 2] = MP_BASE - 1;
+ x->fraction[MP_T - 1] = MP_BASE;
+
+ /* NORMALIZE X (LAST DIGIT B IS OK IN MP_MULTIPLY_INTEGER) */
+ mp_multiply_integer(x, 1, x);
+}
+
+
+void
+mp_get_eulers(MPNumber *z)
+{
+ MPNumber t;
+ mp_set_from_integer(1, &t);
+ mp_epowy(&t, z);
+}
+
+
+void
+mp_get_i(MPNumber *z)
+{
+ mp_set_from_integer(0, z);
+ z->im_sign = 1;
+ z->im_exponent = 1;
+ z->im_fraction[0] = 1;
+}
+
+
+void
+mp_abs(const MPNumber *x, MPNumber *z)
+{
+ if (mp_is_complex(x)){
+ MPNumber x_real, x_im;
+
+ mp_real_component(x, &x_real);
+ mp_imaginary_component(x, &x_im);
+
+ mp_multiply(&x_real, &x_real, &x_real);
+ mp_multiply(&x_im, &x_im, &x_im);
+ mp_add(&x_real, &x_im, z);
+ mp_root(z, 2, z);
+ }
+ else {
+ mp_set_from_mp(x, z);
+ if (z->sign < 0)
+ z->sign = -z->sign;
+ }
+}
+
+
+void
+mp_arg(const MPNumber *x, MPAngleUnit unit, MPNumber *z)
+{
+ MPNumber x_real, x_im, pi;
+
+ if (mp_is_zero(x)) {
+ /* Translators: Error display when attempting to take argument of zero */
+ mperr(_("Argument not defined for zero"));
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ mp_real_component(x, &x_real);
+ mp_imaginary_component(x, &x_im);
+ mp_get_pi(&pi);
+
+ if (mp_is_zero(&x_im)) {
+ if (mp_is_negative(&x_real))
+ convert_from_radians(&pi, MP_RADIANS, z);
+ else
+ mp_set_from_integer(0, z);
+ }
+ else if (mp_is_zero(&x_real)) {
+ mp_set_from_mp(&pi, z);
+ if (mp_is_negative(&x_im))
+ mp_divide_integer(z, -2, z);
+ else
+ mp_divide_integer(z, 2, z);
+ }
+ else if (mp_is_negative(&x_real)) {
+ mp_divide(&x_im, &x_real, z);
+ mp_atan(z, MP_RADIANS, z);
+ if (mp_is_negative(&x_im))
+ mp_subtract(z, &pi, z);
+ else
+ mp_add(z, &pi, z);
+ }
+ else {
+ mp_divide(&x_im, &x_real, z);
+ mp_atan(z, MP_RADIANS, z);
+ }
+
+ convert_from_radians(z, unit, z);
+}
+
+
+void
+mp_conjugate(const MPNumber *x, MPNumber *z)
+{
+ mp_set_from_mp(x, z);
+ z->im_sign = -z->im_sign;
+}
+
+
+void
+mp_real_component(const MPNumber *x, MPNumber *z)
+{
+ mp_set_from_mp(x, z);
+
+ /* Clear imaginary component */
+ z->im_sign = 0;
+ z->im_exponent = 0;
+ memset(z->im_fraction, 0, sizeof(int) * MP_SIZE);
+}
+
+
+void
+mp_imaginary_component(const MPNumber *x, MPNumber *z)
+{
+ /* Copy imaginary component to real component */
+ z->sign = x->im_sign;
+ z->exponent = x->im_exponent;
+ memcpy(z->fraction, x->im_fraction, sizeof(int) * MP_SIZE);
+
+ /* Clear (old) imaginary component */
+ z->im_sign = 0;
+ z->im_exponent = 0;
+ memset(z->im_fraction, 0, sizeof(int) * MP_SIZE);
+}
+
+
+static void
+mp_add_real(const MPNumber *x, int y_sign, const MPNumber *y, MPNumber *z)
+{
+ int sign_prod, i, c;
+ int exp_diff, med;
+ bool x_largest = false;
+ const int *big_fraction, *small_fraction;
+ MPNumber x_copy, y_copy;
+
+ /* 0 + y = y */
+ if (mp_is_zero(x)) {
+ mp_set_from_mp(y, z);
+ z->sign = y_sign;
+ return;
+ }
+ /* x + 0 = x */
+ else if (mp_is_zero(y)) {
+ mp_set_from_mp(x, z);
+ return;
+ }
+
+ sign_prod = y_sign * x->sign;
+ exp_diff = x->exponent - y->exponent;
+ med = abs(exp_diff);
+ if (exp_diff < 0) {
+ x_largest = false;
+ } else if (exp_diff > 0) {
+ x_largest = true;
+ } else {
+ /* EXPONENTS EQUAL SO COMPARE SIGNS, THEN FRACTIONS IF NEC. */
+ if (sign_prod < 0) {
+ /* Signs are not equal. find out which mantissa is larger. */
+ int j;
+ for (j = 0; j < MP_T; j++) {
+ int i = x->fraction[j] - y->fraction[j];
+ if (i == 0)
+ continue;
+ if (i < 0)
+ x_largest = false;
+ else if (i > 0)
+ x_largest = true;
+ break;
+ }
+
+ /* Both mantissas equal, so result is zero. */
+ if (j >= MP_T) {
+ mp_set_from_integer(0, z);
+ return;
+ }
+ }
+ }
+
+ mp_set_from_mp(x, &x_copy);
+ mp_set_from_mp(y, &y_copy);
+ mp_set_from_integer(0, z);
+
+ if (x_largest) {
+ z->sign = x_copy.sign;
+ z->exponent = x_copy.exponent;
+ big_fraction = x_copy.fraction;
+ small_fraction = y_copy.fraction;
+ } else {
+ z->sign = y_sign;
+ z->exponent = y_copy.exponent;
+ big_fraction = y_copy.fraction;
+ small_fraction = x_copy.fraction;
+ }
+
+ /* CLEAR GUARD DIGITS TO RIGHT OF X DIGITS */
+ for(i = 3; i >= med; i--)
+ z->fraction[MP_T + i] = 0;
+
+ if (sign_prod >= 0) {
+ /* HERE DO ADDITION, EXPONENT(Y) >= EXPONENT(X) */
+ for (i = MP_T + 3; i >= MP_T; i--)
+ z->fraction[i] = small_fraction[i - med];
+
+ c = 0;
+ for (; i >= med; i--) {
+ c = big_fraction[i] + small_fraction[i - med] + c;
+
+ if (c < MP_BASE) {
+ /* NO CARRY GENERATED HERE */
+ z->fraction[i] = c;
+ c = 0;
+ } else {
+ /* CARRY GENERATED HERE */
+ z->fraction[i] = c - MP_BASE;
+ c = 1;
+ }
+ }
+
+ for (; i >= 0; i--)
+ {
+ c = big_fraction[i] + c;
+ if (c < MP_BASE) {
+ z->fraction[i] = c;
+ i--;
+
+ /* NO CARRY POSSIBLE HERE */
+ for (; i >= 0; i--)
+ z->fraction[i] = big_fraction[i];
+
+ c = 0;
+ break;
+ }
+
+ z->fraction[i] = 0;
+ c = 1;
+ }
+
+ /* MUST SHIFT RIGHT HERE AS CARRY OFF END */
+ if (c != 0) {
+ for (i = MP_T + 3; i > 0; i--)
+ z->fraction[i] = z->fraction[i - 1];
+ z->fraction[0] = 1;
+ z->exponent++;
+ }
+ }
+ else {
+ c = 0;
+ for (i = MP_T + med - 1; i >= MP_T; i--) {
+ /* HERE DO SUBTRACTION, ABS(Y) > ABS(X) */
+ z->fraction[i] = c - small_fraction[i - med];
+ c = 0;
+
+ /* BORROW GENERATED HERE */
+ if (z->fraction[i] < 0) {
+ c = -1;
+ z->fraction[i] += MP_BASE;
+ }
+ }
+
+ for(; i >= med; i--) {
+ c = big_fraction[i] + c - small_fraction[i - med];
+ if (c >= 0) {
+ /* NO BORROW GENERATED HERE */
+ z->fraction[i] = c;
+ c = 0;
+ } else {
+ /* BORROW GENERATED HERE */
+ z->fraction[i] = c + MP_BASE;
+ c = -1;
+ }
+ }
+
+ for (; i >= 0; i--) {
+ c = big_fraction[i] + c;
+
+ if (c >= 0) {
+ z->fraction[i] = c;
+ i--;
+
+ /* NO CARRY POSSIBLE HERE */
+ for (; i >= 0; i--)
+ z->fraction[i] = big_fraction[i];
+
+ break;
+ }
+
+ z->fraction[i] = c + MP_BASE;
+ c = -1;
+ }
+ }
+
+ mp_normalize(z);
+}
+
+
+static void
+mp_add_with_sign(const MPNumber *x, int y_sign, const MPNumber *y, MPNumber *z)
+{
+ if (mp_is_complex(x) || mp_is_complex(y)) {
+ MPNumber real_x, real_y, im_x, im_y, real_z, im_z;
+
+ mp_real_component(x, &real_x);
+ mp_imaginary_component(x, &im_x);
+ mp_real_component(y, &real_y);
+ mp_imaginary_component(y, &im_y);
+
+ mp_add_real(&real_x, y_sign * y->sign, &real_y, &real_z);
+ mp_add_real(&im_x, y_sign * y->im_sign, &im_y, &im_z);
+
+ mp_set_from_complex(&real_z, &im_z, z);
+ }
+ else
+ mp_add_real(x, y_sign * y->sign, y, z);
+}
+
+
+void
+mp_add(const MPNumber *x, const MPNumber *y, MPNumber *z)
+{
+ mp_add_with_sign(x, 1, y, z);
+}
+
+
+void
+mp_add_integer(const MPNumber *x, int64_t y, MPNumber *z)
+{
+ MPNumber t;
+ mp_set_from_integer(y, &t);
+ mp_add(x, &t, z);
+}
+
+
+void
+mp_add_fraction(const MPNumber *x, int64_t i, int64_t j, MPNumber *y)
+{
+ MPNumber t;
+ mp_set_from_fraction(i, j, &t);
+ mp_add(x, &t, y);
+}
+
+
+void
+mp_subtract(const MPNumber *x, const MPNumber *y, MPNumber *z)
+{
+ mp_add_with_sign(x, -1, y, z);
+}
+
+
+void
+mp_sgn(const MPNumber *x, MPNumber *z)
+{
+ if (mp_is_zero(x))
+ mp_set_from_integer(0, z);
+ else if (mp_is_negative(x))
+ mp_set_from_integer(-1, z);
+ else
+ mp_set_from_integer(1, z);
+}
+
+void
+mp_integer_component(const MPNumber *x, MPNumber *z)
+{
+ int i;
+
+ /* Clear fraction */
+ mp_set_from_mp(x, z);
+ for (i = z->exponent; i < MP_SIZE; i++)
+ z->fraction[i] = 0;
+ z->im_sign = 0;
+ z->im_exponent = 0;
+ memset(z->im_fraction, 0, sizeof(int) * MP_SIZE);
+}
+
+void
+mp_fractional_component(const MPNumber *x, MPNumber *z)
+{
+ int i, shift;
+
+ /* Fractional component of zero is 0 */
+ if (mp_is_zero(x)) {
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* All fractional */
+ if (x->exponent <= 0) {
+ mp_set_from_mp(x, z);
+ return;
+ }
+
+ /* Shift fractional component */
+ shift = x->exponent;
+ for (i = shift; i < MP_SIZE && x->fraction[i] == 0; i++)
+ shift++;
+ z->sign = x->sign;
+ z->exponent = x->exponent - shift;
+ for (i = 0; i < MP_SIZE; i++) {
+ if (i + shift >= MP_SIZE)
+ z->fraction[i] = 0;
+ else
+ z->fraction[i] = x->fraction[i + shift];
+ }
+ if (z->fraction[0] == 0)
+ z->sign = 0;
+
+ z->im_sign = 0;
+ z->im_exponent = 0;
+ memset(z->im_fraction, 0, sizeof(int) * MP_SIZE);
+}
+
+
+void
+mp_fractional_part(const MPNumber *x, MPNumber *z)
+{
+ MPNumber f;
+ mp_floor(x, &f);
+ mp_subtract(x, &f, z);
+}
+
+
+void
+mp_floor(const MPNumber *x, MPNumber *z)
+{
+ int i;
+ bool have_fraction = false, is_negative;
+
+ /* Integer component of zero = 0 */
+ if (mp_is_zero(x)) {
+ mp_set_from_mp(x, z);
+ return;
+ }
+
+ /* If all fractional then no integer component */
+ if (x->exponent <= 0) {
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ is_negative = mp_is_negative(x);
+
+ /* Clear fraction */
+ mp_set_from_mp(x, z);
+ for (i = z->exponent; i < MP_SIZE; i++) {
+ if (z->fraction[i])
+ have_fraction = true;
+ z->fraction[i] = 0;
+ }
+ z->im_sign = 0;
+ z->im_exponent = 0;
+ memset(z->im_fraction, 0, sizeof(int) * MP_SIZE);
+
+ if (have_fraction && is_negative)
+ mp_add_integer(z, -1, z);
+}
+
+
+void
+mp_ceiling(const MPNumber *x, MPNumber *z)
+{
+ MPNumber f;
+
+ mp_floor(x, z);
+ mp_fractional_component(x, &f);
+ if (mp_is_zero(&f))
+ return;
+ mp_add_integer(z, 1, z);
+}
+
+
+void
+mp_round(const MPNumber *x, MPNumber *z)
+{
+ MPNumber f, one;
+ bool do_floor;
+
+ do_floor = !mp_is_negative(x);
+
+ mp_fractional_component(x, &f);
+ mp_multiply_integer(&f, 2, &f);
+ mp_abs(&f, &f);
+ mp_set_from_integer(1, &one);
+ if (mp_is_greater_equal(&f, &one))
+ do_floor = !do_floor;
+
+ if (do_floor)
+ mp_floor(x, z);
+ else
+ mp_ceiling(x, z);
+}
+
+int
+mp_compare_mp_to_mp(const MPNumber *x, const MPNumber *y)
+{
+ int i;
+
+ if (x->sign != y->sign) {
+ if (x->sign > y->sign)
+ return 1;
+ else
+ return -1;
+ }
+
+ /* x = y = 0 */
+ if (mp_is_zero(x))
+ return 0;
+
+ /* See if numbers are of different magnitude */
+ if (x->exponent != y->exponent) {
+ if (x->exponent > y->exponent)
+ return x->sign;
+ else
+ return -x->sign;
+ }
+
+ /* Compare fractions */
+ for (i = 0; i < MP_SIZE; i++) {
+ if (x->fraction[i] == y->fraction[i])
+ continue;
+
+ if (x->fraction[i] > y->fraction[i])
+ return x->sign;
+ else
+ return -x->sign;
+ }
+
+ /* x = y */
+ return 0;
+}
+
+
+void
+mp_divide(const MPNumber *x, const MPNumber *y, MPNumber *z)
+{
+ int i, ie;
+ MPNumber t;
+
+ /* x/0 */
+ if (mp_is_zero(y)) {
+ /* Translators: Error displayed attempted to divide by zero */
+ mperr(_("Division by zero is undefined"));
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* 0/y = 0 */
+ if (mp_is_zero(x)) {
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* z = x × y⁻¹ */
+ /* FIXME: Set exponent to zero to avoid overflow in mp_multiply??? */
+ mp_reciprocal(y, &t);
+ ie = t.exponent;
+ t.exponent = 0;
+ i = t.fraction[0];
+ mp_multiply(x, &t, z);
+ mp_ext(i, z->fraction[0], z);
+ z->exponent += ie;
+}
+
+
+static void
+mp_divide_integer_real(const MPNumber *x, int64_t y, MPNumber *z)
+{
+ int c, i, k, b2, c2, j1, j2;
+ MPNumber x_copy;
+
+ /* x/0 */
+ if (y == 0) {
+ /* Translators: Error displayed attempted to divide by zero */
+ mperr(_("Division by zero is undefined"));
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* 0/y = 0 */
+ if (mp_is_zero(x)) {
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* Division by -1 or 1 just changes sign */
+ if (y == 1 || y == -1) {
+ if (y < 0)
+ mp_invert_sign(x, z);
+ else
+ mp_set_from_mp(x, z);
+ return;
+ }
+
+ /* Copy x as z may also refer to x */
+ mp_set_from_mp(x, &x_copy);
+ mp_set_from_integer(0, z);
+
+ if (y < 0) {
+ y = -y;
+ z->sign = -x_copy.sign;
+ }
+ else
+ z->sign = x_copy.sign;
+ z->exponent = x_copy.exponent;
+
+ c = 0;
+ i = 0;
+
+ /* IF y*B NOT REPRESENTABLE AS AN INTEGER HAVE TO SIMULATE
+ * LONG DIVISION. ASSUME AT LEAST 16-BIT WORD.
+ */
+
+ /* Computing MAX */
+ b2 = max(MP_BASE << 3, 32767 / MP_BASE);
+ if (y < b2) {
+ int kh, r1;
+
+ /* LOOK FOR FIRST NONZERO DIGIT IN QUOTIENT */
+ do {
+ c = MP_BASE * c;
+ if (i < MP_T)
+ c += x_copy.fraction[i];
+ i++;
+ r1 = c / y;
+ if (r1 < 0)
+ goto L210;
+ } while (r1 == 0);
+
+ /* ADJUST EXPONENT AND GET T+4 DIGITS IN QUOTIENT */
+ z->exponent += 1 - i;
+ z->fraction[0] = r1;
+ c = MP_BASE * (c - y * r1);
+ kh = 1;
+ if (i < MP_T) {
+ kh = MP_T + 1 - i;
+ for (k = 1; k < kh; k++) {
+ c += x_copy.fraction[i];
+ z->fraction[k] = c / y;
+ c = MP_BASE * (c - y * z->fraction[k]);
+ i++;
+ }
+ if (c < 0)
+ goto L210;
+ }
+
+ for (k = kh; k < MP_T + 4; k++) {
+ z->fraction[k] = c / y;
+ c = MP_BASE * (c - y * z->fraction[k]);
+ }
+ if (c < 0)
+ goto L210;
+
+ mp_normalize(z);
+ return;
+ }
+
+ /* HERE NEED SIMULATED DOUBLE-PRECISION DIVISION */
+ j1 = y / MP_BASE;
+ j2 = y - j1 * MP_BASE;
+
+ /* LOOK FOR FIRST NONZERO DIGIT */
+ c2 = 0;
+ do {
+ c = MP_BASE * c + c2;
+ c2 = i < MP_T ? x_copy.fraction[i] : 0;
+ i++;
+ } while (c < j1 || (c == j1 && c2 < j2));
+
+ /* COMPUTE T+4 QUOTIENT DIGITS */
+ z->exponent += 1 - i;
+ i--;
+
+ /* MAIN LOOP FOR LARGE ABS(y) CASE */
+ for (k = 1; k <= MP_T + 4; k++) {
+ int ir, iq, iqj;
+
+ /* GET APPROXIMATE QUOTIENT FIRST */
+ ir = c / (j1 + 1);
+
+ /* NOW REDUCE SO OVERFLOW DOES NOT OCCUR */
+ iq = c - ir * j1;
+ if (iq >= b2) {
+ /* HERE IQ*B WOULD POSSIBLY OVERFLOW SO INCREASE IR */
+ ++ir;
+ iq -= j1;
+ }
+
+ iq = iq * MP_BASE - ir * j2;
+ if (iq < 0) {
+ /* HERE IQ NEGATIVE SO IR WAS TOO LARGE */
+ ir--;
+ iq += y;
+ }
+
+ if (i < MP_T)
+ iq += x_copy.fraction[i];
+ i++;
+ iqj = iq / y;
+
+ /* R(K) = QUOTIENT, C = REMAINDER */
+ z->fraction[k - 1] = iqj + ir;
+ c = iq - y * iqj;
+
+ if (c < 0)
+ goto L210;
+ }
+
+ mp_normalize(z);
+
+L210:
+ /* CARRY NEGATIVE SO OVERFLOW MUST HAVE OCCURRED */
+ mperr("*** INTEGER OVERFLOW IN MP_DIVIDE_INTEGER, B TOO LARGE ***");
+ mp_set_from_integer(0, z);
+}
+
+
+void
+mp_divide_integer(const MPNumber *x, int64_t y, MPNumber *z)
+{
+ if (mp_is_complex(x)) {
+ MPNumber re_z, im_z;
+
+ mp_real_component(x, &re_z);
+ mp_imaginary_component(x, &im_z);
+ mp_divide_integer_real(&re_z, y, &re_z);
+ mp_divide_integer_real(&im_z, y, &im_z);
+ mp_set_from_complex(&re_z, &im_z, z);
+ }
+ else
+ mp_divide_integer_real(x, y, z);
+}
+
+
+bool
+mp_is_integer(const MPNumber *x)
+{
+ MPNumber t1, t2, t3;
+
+ if (mp_is_complex(x))
+ return false;
+
+ /* This fix is required for 1/3 repiprocal not being detected as an integer */
+ /* Multiplication and division by 10000 is used to get around a
+ * limitation to the "fix" for Sun bugtraq bug #4006391 in the
+ * mp_floor() routine in mp.c, when the exponent is less than 1.
+ */
+ mp_set_from_integer(10000, &t3);
+ mp_multiply(x, &t3, &t1);
+ mp_divide(&t1, &t3, &t1);
+ mp_floor(&t1, &t2);
+ return mp_is_equal(&t1, &t2);
+
+ /* Correct way to check for integer */
+ /*int i;
+
+ // Zero is an integer
+ if (mp_is_zero(x))
+ return true;
+
+ // Fractional
+ if (x->exponent <= 0)
+ return false;
+
+ // Look for fractional components
+ for (i = x->exponent; i < MP_SIZE; i++) {
+ if (x->fraction[i] != 0)
+ return false;
+ }
+
+ return true;*/
+}
+
+
+bool
+mp_is_positive_integer(const MPNumber *x)
+{
+ if (mp_is_complex(x))
+ return false;
+ else
+ return x->sign >= 0 && mp_is_integer(x);
+}
+
+
+bool
+mp_is_natural(const MPNumber *x)
+{
+ if (mp_is_complex(x))
+ return false;
+ else
+ return x->sign > 0 && mp_is_integer(x);
+}
+
+
+bool
+mp_is_complex(const MPNumber *x)
+{
+ return x->im_sign != 0;
+}
+
+
+bool
+mp_is_equal(const MPNumber *x, const MPNumber *y)
+{
+ return mp_compare_mp_to_mp(x, y) == 0;
+}
+
+
+/* Return e^x for |x| < 1 USING AN O(SQRT(T).M(T)) ALGORITHM
+ * DESCRIBED IN - R. P. BRENT, THE COMPLEXITY OF MULTIPLE-
+ * PRECISION ARITHMETIC (IN COMPLEXITY OF COMPUTATIONAL PROBLEM
+ * SOLVING, UNIV. OF QUEENSLAND PRESS, BRISBANE, 1976, 126-165).
+ * ASYMPTOTICALLY FASTER METHODS EXIST, BUT ARE NOT USEFUL
+ * UNLESS T IS VERY LARGE. SEE COMMENTS TO MP_ATAN AND MPPIGL.
+ */
+static void
+mp_exp(const MPNumber *x, MPNumber *z)
+{
+ int i, q;
+ float rlb;
+ MPNumber t1, t2;
+
+ /* e^0 = 1 */
+ if (mp_is_zero(x)) {
+ mp_set_from_integer(1, z);
+ return;
+ }
+
+ /* Only defined for |x| < 1 */
+ if (x->exponent > 0) {
+ mperr("*** ABS(X) NOT LESS THAN 1 IN CALL TO MP_EXP ***");
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ mp_set_from_mp(x, &t1);
+ rlb = log((float)MP_BASE);
+
+ /* Compute approximately optimal q (and divide x by 2^q) */
+ q = (int)(sqrt((float)MP_T * 0.48f * rlb) + (float) x->exponent * 1.44f * rlb);
+
+ /* HALVE Q TIMES */
+ if (q > 0) {
+ int ib, ic;
+
+ ib = MP_BASE << 2;
+ ic = 1;
+ for (i = 1; i <= q; ++i) {
+ ic *= 2;
+ if (ic < ib && ic != MP_BASE && i < q)
+ continue;
+ mp_divide_integer(&t1, ic, &t1);
+ ic = 1;
+ }
+ }
+
+ if (mp_is_zero(&t1)) {
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* Sum series, reducing t where possible */
+ mp_set_from_mp(&t1, z);
+ mp_set_from_mp(&t1, &t2);
+ for (i = 2; MP_T + t2.exponent - z->exponent > 0; i++) {
+ mp_multiply(&t1, &t2, &t2);
+ mp_divide_integer(&t2, i, &t2);
+ mp_add(&t2, z, z);
+ if (mp_is_zero(&t2))
+ break;
+ }
+
+ /* Apply (x+1)^2 - 1 = x(2 + x) for q iterations */
+ for (i = 1; i <= q; ++i) {
+ mp_add_integer(z, 2, &t1);
+ mp_multiply(&t1, z, z);
+ }
+
+ mp_add_integer(z, 1, z);
+}
+
+
+static void
+mp_epowy_real(const MPNumber *x, MPNumber *z)
+{
+ float r__1;
+ int i, ix, xs, tss;
+ float rx, rz, rlb;
+ MPNumber t1, t2;
+
+ /* e^0 = 1 */
+ if (mp_is_zero(x)) {
+ mp_set_from_integer(1, z);
+ return;
+ }
+
+ /* If |x| < 1 use mp_exp */
+ if (x->exponent <= 0) {
+ mp_exp(x, z);
+ return;
+ }
+
+ /* SEE IF ABS(X) SO LARGE THAT EXP(X) WILL CERTAINLY OVERFLOW
+ * OR UNDERFLOW. 1.01 IS TO ALLOW FOR ERRORS IN ALOG.
+ */
+ rlb = log((float)MP_BASE) * 1.01f;
+
+ /* NOW SAFE TO CONVERT X TO REAL */
+ rx = mp_cast_to_float(x);
+
+ /* SAVE SIGN AND WORK WITH ABS(X) */
+ xs = x->sign;
+ mp_abs(x, &t2);
+
+ /* GET FRACTIONAL AND INTEGER PARTS OF ABS(X) */
+ ix = mp_cast_to_int(&t2);
+ mp_fractional_component(&t2, &t2);
+
+ /* ATTACH SIGN TO FRACTIONAL PART AND COMPUTE EXP OF IT */
+ t2.sign *= xs;
+ mp_exp(&t2, z);
+
+ /* COMPUTE E-2 OR 1/E USING TWO EXTRA DIGITS IN CASE ABS(X) LARGE
+ * (BUT ONLY ONE EXTRA DIGIT IF T < 4)
+ */
+ if (MP_T < 4)
+ tss = MP_T + 1;
+ else
+ tss = MP_T + 2;
+
+ /* LOOP FOR E COMPUTATION. DECREASE T IF POSSIBLE. */
+ /* Computing MIN */
+ mp_set_from_integer(xs, &t1);
+
+ t2.sign = 0;
+ for (i = 2 ; ; i++) {
+ if (min(tss, tss + 2 + t1.exponent) <= 2)
+ break;
+
+ mp_divide_integer(&t1, i * xs, &t1);
+ mp_add(&t2, &t1, &t2);
+ if (mp_is_zero(&t1))
+ break;
+ }
+
+ /* RAISE E OR 1/E TO POWER IX */
+ if (xs > 0)
+ mp_add_integer(&t2, 2, &t2);
+ mp_xpowy_integer(&t2, ix, &t2);
+
+ /* MULTIPLY EXPS OF INTEGER AND FRACTIONAL PARTS */
+ mp_multiply(z, &t2, z);
+
+ /* CHECK THAT RELATIVE ERROR LESS THAN 0.01 UNLESS ABS(X) LARGE
+ * (WHEN EXP MIGHT OVERFLOW OR UNDERFLOW)
+ */
+ if (fabs(rx) > 10.0f)
+ return;
+
+ rz = mp_cast_to_float(z);
+ r__1 = rz - exp(rx);
+ if (fabs(r__1) < rz * 0.01f)
+ return;
+
+ /* THE FOLLOWING MESSAGE MAY INDICATE THAT
+ * B**(T-1) IS TOO SMALL, OR THAT M IS TOO SMALL SO THE
+ * RESULT UNDERFLOWED.
+ */
+ mperr("*** ERROR OCCURRED IN MP_EPOWY, RESULT INCORRECT ***");
+}
+
+
+void
+mp_epowy(const MPNumber *x, MPNumber *z)
+{
+ /* e^0 = 1 */
+ if (mp_is_zero(x)) {
+ mp_set_from_integer(1, z);
+ return;
+ }
+
+ if (mp_is_complex(x)) {
+ MPNumber x_real, r, theta;
+
+ mp_real_component(x, &x_real);
+ mp_imaginary_component(x, &theta);
+
+ mp_epowy_real(&x_real, &r);
+ mp_set_from_polar(&r, MP_RADIANS, &theta, z);
+ }
+ else
+ mp_epowy_real(x, z);
+}
+
+
+/* RETURNS K = K/GCD AND L = L/GCD, WHERE GCD IS THE
+ * GREATEST COMMON DIVISOR OF K AND L.
+ * SAVE INPUT PARAMETERS IN LOCAL VARIABLES
+ */
+void
+mp_gcd(int64_t *k, int64_t *l)
+{
+ int64_t i, j;
+
+ i = abs(*k);
+ j = abs(*l);
+ if (j == 0) {
+ /* IF J = 0 RETURN (1, 0) UNLESS I = 0, THEN (0, 0) */
+ *k = 1;
+ *l = 0;
+ if (i == 0)
+ *k = 0;
+ return;
+ }
+
+ /* EUCLIDEAN ALGORITHM LOOP */
+ do {
+ i %= j;
+ if (i == 0) {
+ *k = *k / j;
+ *l = *l / j;
+ return;
+ }
+ j %= i;
+ } while (j != 0);
+
+ /* HERE J IS THE GCD OF K AND L */
+ *k = *k / i;
+ *l = *l / i;
+}
+
+
+bool
+mp_is_zero(const MPNumber *x)
+{
+ return x->sign == 0 && x->im_sign == 0;
+}
+
+
+bool
+mp_is_negative(const MPNumber *x)
+{
+ return x->sign < 0;
+}
+
+
+bool
+mp_is_greater_equal(const MPNumber *x, const MPNumber *y)
+{
+ return mp_compare_mp_to_mp(x, y) >= 0;
+}
+
+
+bool
+mp_is_greater_than(const MPNumber *x, const MPNumber *y)
+{
+ return mp_compare_mp_to_mp(x, y) > 0;
+}
+
+
+bool
+mp_is_less_equal(const MPNumber *x, const MPNumber *y)
+{
+ return mp_compare_mp_to_mp(x, y) <= 0;
+}
+
+
+/* RETURNS MP Y = LN(1+X) IF X IS AN MP NUMBER SATISFYING THE
+ * CONDITION ABS(X) < 1/B, ERROR OTHERWISE.
+ * USES NEWTONS METHOD TO SOLVE THE EQUATION
+ * EXP1(-Y) = X, THEN REVERSES SIGN OF Y.
+ */
+static void
+mp_lns(const MPNumber *x, MPNumber *z)
+{
+ int t, it0;
+ MPNumber t1, t2, t3;
+
+ /* ln(1+0) = 0 */
+ if (mp_is_zero(x)) {
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* Get starting approximation -ln(1+x) ~= -x + x^2/2 - x^3/3 + x^4/4 */
+ mp_set_from_mp(x, &t2);
+ mp_divide_integer(x, 4, &t1);
+ mp_add_fraction(&t1, -1, 3, &t1);
+ mp_multiply(x, &t1, &t1);
+ mp_add_fraction(&t1, 1, 2, &t1);
+ mp_multiply(x, &t1, &t1);
+ mp_add_integer(&t1, -1, &t1);
+ mp_multiply(x, &t1, z);
+
+ /* Solve using Newtons method */
+ it0 = t = 5;
+ while(1)
+ {
+ int ts2, ts3;
+
+ /* t3 = (e^t3 - 1) */
+ /* z = z - (t2 + t3 + (t2 * t3)) */
+ mp_epowy(z, &t3);
+ mp_add_integer(&t3, -1, &t3);
+ mp_multiply(&t2, &t3, &t1);
+ mp_add(&t3, &t1, &t3);
+ mp_add(&t2, &t3, &t3);
+ mp_subtract(z, &t3, z);
+ if (t >= MP_T)
+ break;
+
+ /* FOLLOWING LOOP COMPUTES NEXT VALUE OF T TO USE.
+ * BECAUSE NEWTONS METHOD HAS 2ND ORDER CONVERGENCE,
+ * WE CAN ALMOST DOUBLE T EACH TIME.
+ */
+ ts3 = t;
+ t = MP_T;
+ do {
+ ts2 = t;
+ t = (t + it0) / 2;
+ } while (t > ts3);
+ t = ts2;
+ }
+
+ /* CHECK THAT NEWTON ITERATION WAS CONVERGING AS EXPECTED */
+ if (t3.sign != 0 && t3.exponent << 1 > it0 - MP_T) {
+ mperr("*** ERROR OCCURRED IN MP_LNS, NEWTON ITERATION NOT CONVERGING PROPERLY ***");
+ }
+
+ z->sign = -z->sign;
+}
+
+
+static void
+mp_ln_real(const MPNumber *x, MPNumber *z)
+{
+ int e, k;
+ float rx, rlx;
+ MPNumber t1, t2;
+
+ /* LOOP TO GET APPROXIMATE LN(X) USING SINGLE-PRECISION */
+ mp_set_from_mp(x, &t1);
+ mp_set_from_integer(0, z);
+ for(k = 0; k < 10; k++)
+ {
+ /* COMPUTE FINAL CORRECTION ACCURATELY USING MP_LNS */
+ mp_add_integer(&t1, -1, &t2);
+ if (mp_is_zero(&t2) || t2.exponent + 1 <= 0) {
+ mp_lns(&t2, &t2);
+ mp_add(z, &t2, z);
+ return;
+ }
+
+ /* REMOVE EXPONENT TO AVOID FLOATING-POINT OVERFLOW */
+ e = t1.exponent;
+ t1.exponent = 0;
+ rx = mp_cast_to_float(&t1);
+ t1.exponent = e;
+ rlx = log(rx) + (float)e * log((float)MP_BASE);
+ mp_set_from_float(-(double)rlx, &t2);
+
+ /* UPDATE Z AND COMPUTE ACCURATE EXP OF APPROXIMATE LOG */
+ mp_subtract(z, &t2, z);
+ mp_epowy(&t2, &t2);
+
+ /* COMPUTE RESIDUAL WHOSE LOG IS STILL TO BE FOUND */
+ mp_multiply(&t1, &t2, &t1);
+ }
+
+ mperr("*** ERROR IN MP_LN, ITERATION NOT CONVERGING ***");
+}
+
+
+void
+mp_ln(const MPNumber *x, MPNumber *z)
+{
+ /* ln(0) undefined */
+ if (mp_is_zero(x)) {
+ /* Translators: Error displayed when attempting to take logarithm of zero */
+ mperr(_("Logarithm of zero is undefined"));
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* ln(-x) complex */
+ /* FIXME: Make complex numbers optional */
+ /*if (mp_is_negative(x)) {
+ // Translators: Error displayed attempted to take logarithm of negative value
+ mperr(_("Logarithm of negative values is undefined"));
+ mp_set_from_integer(0, z);
+ return;
+ }*/
+
+ if (mp_is_complex(x) || mp_is_negative(x)) {
+ MPNumber r, theta, z_real;
+
+ /* ln(re^iθ) = e^(ln(r)+iθ) */
+ mp_abs(x, &r);
+ mp_arg(x, MP_RADIANS, &theta);
+
+ mp_ln_real(&r, &z_real);
+ mp_set_from_complex(&z_real, &theta, z);
+ }
+ else
+ mp_ln_real(x, z);
+}
+
+
+void
+mp_logarithm(int64_t n, const MPNumber *x, MPNumber *z)
+{
+ MPNumber t1, t2;
+
+ /* log(0) undefined */
+ if (mp_is_zero(x)) {
+ /* Translators: Error displayed when attempting to take logarithm of zero */
+ mperr(_("Logarithm of zero is undefined"));
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* logn(x) = ln(x) / ln(n) */
+ mp_set_from_integer(n, &t1);
+ mp_ln(&t1, &t1);
+ mp_ln(x, &t2);
+ mp_divide(&t2, &t1, z);
+}
+
+
+bool
+mp_is_less_than(const MPNumber *x, const MPNumber *y)
+{
+ return mp_compare_mp_to_mp(x, y) < 0;
+}
+
+
+static void
+mp_multiply_real(const MPNumber *x, const MPNumber *y, MPNumber *z)
+{
+ int c, i, xi;
+ MPNumber r;
+
+ /* x*0 = 0*y = 0 */
+ if (x->sign == 0 || y->sign == 0) {
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ z->sign = x->sign * y->sign;
+ z->exponent = x->exponent + y->exponent;
+ memset(&r, 0, sizeof(MPNumber));
+
+ /* PERFORM MULTIPLICATION */
+ c = 8;
+ for (i = 0; i < MP_T; i++) {
+ int j;
+
+ xi = x->fraction[i];
+
+ /* FOR SPEED, PUT THE NUMBER WITH MANY ZEROS FIRST */
+ if (xi == 0)
+ continue;
+
+ /* Computing MIN */
+ for (j = 0; j < min(MP_T, MP_T + 3 - i); j++)
+ r.fraction[i+j+1] += xi * y->fraction[j];
+ c--;
+ if (c > 0)
+ continue;
+
+ /* CHECK FOR LEGAL BASE B DIGIT */
+ if (xi < 0 || xi >= MP_BASE) {
+ mperr("*** ILLEGAL BASE B DIGIT IN CALL TO MP_MULTIPLY, POSSIBLE OVERWRITING PROBLEM ***");
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* PROPAGATE CARRIES AT END AND EVERY EIGHTH TIME,
+ * FASTER THAN DOING IT EVERY TIME.
+ */
+ for (j = MP_T + 3; j >= 0; j--) {
+ int ri = r.fraction[j] + c;
+ if (ri < 0) {
+ mperr("*** INTEGER OVERFLOW IN MP_MULTIPLY, B TOO LARGE ***");
+ mp_set_from_integer(0, z);
+ return;
+ }
+ c = ri / MP_BASE;
+ r.fraction[j] = ri - MP_BASE * c;
+ }
+ if (c != 0) {
+ mperr("*** ILLEGAL BASE B DIGIT IN CALL TO MP_MULTIPLY, POSSIBLE OVERWRITING PROBLEM ***");
+ mp_set_from_integer(0, z);
+ return;
+ }
+ c = 8;
+ }
+
+ if (c != 8) {
+ if (xi < 0 || xi >= MP_BASE) {
+ mperr("*** ILLEGAL BASE B DIGIT IN CALL TO MP_MULTIPLY, POSSIBLE OVERWRITING PROBLEM ***");
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ c = 0;
+ for (i = MP_T + 3; i >= 0; i--) {
+ int ri = r.fraction[i] + c;
+ if (ri < 0) {
+ mperr("*** INTEGER OVERFLOW IN MP_MULTIPLY, B TOO LARGE ***");
+ mp_set_from_integer(0, z);
+ return;
+ }
+ c = ri / MP_BASE;
+ r.fraction[i] = ri - MP_BASE * c;
+ }
+
+ if (c != 0) {
+ mperr("*** ILLEGAL BASE B DIGIT IN CALL TO MP_MULTIPLY, POSSIBLE OVERWRITING PROBLEM ***");
+ mp_set_from_integer(0, z);
+ return;
+ }
+ }
+
+ /* Clear complex part */
+ z->im_sign = 0;
+ z->im_exponent = 0;
+ memset(z->im_fraction, 0, sizeof(int) * MP_SIZE);
+
+ /* NORMALIZE AND ROUND RESULT */
+ // FIXME: Use stack variable because of mp_normalize brokeness
+ for (i = 0; i < MP_SIZE; i++)
+ z->fraction[i] = r.fraction[i];
+ mp_normalize(z);
+}
+
+
+void
+mp_multiply(const MPNumber *x, const MPNumber *y, MPNumber *z)
+{
+ /* x*0 = 0*y = 0 */
+ if (mp_is_zero(x) || mp_is_zero(y)) {
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* (a+bi)(c+di) = (ac-bd)+(ad+bc)i */
+ if (mp_is_complex(x) || mp_is_complex(y)) {
+ MPNumber real_x, real_y, im_x, im_y, t1, t2, real_z, im_z;
+
+ mp_real_component(x, &real_x);
+ mp_imaginary_component(x, &im_x);
+ mp_real_component(y, &real_y);
+ mp_imaginary_component(y, &im_y);
+
+ mp_multiply_real(&real_x, &real_y, &t1);
+ mp_multiply_real(&im_x, &im_y, &t2);
+ mp_subtract(&t1, &t2, &real_z);
+
+ mp_multiply_real(&real_x, &im_y, &t1);
+ mp_multiply_real(&im_x, &real_y, &t2);
+ mp_add(&t1, &t2, &im_z);
+
+ mp_set_from_complex(&real_z, &im_z, z);
+ }
+ else {
+ mp_multiply_real(x, y, z);
+ }
+}
+
+
+static void
+mp_multiply_integer_real(const MPNumber *x, int64_t y, MPNumber *z)
+{
+ int c, i;
+ MPNumber x_copy;
+
+ /* x*0 = 0*y = 0 */
+ if (mp_is_zero(x) || y == 0) {
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* x*1 = x, x*-1 = -x */
+ // FIXME: Why is this not working? mp_ext is using this function to do a normalization
+ /*if (y == 1 || y == -1) {
+ if (y < 0)
+ mp_invert_sign(x, z);
+ else
+ mp_set_from_mp(x, z);
+ return;
+ }*/
+
+ /* Copy x as z may also refer to x */
+ mp_set_from_mp(x, &x_copy);
+ mp_set_from_integer(0, z);
+
+ if (y < 0) {
+ y = -y;
+ z->sign = -x_copy.sign;
+ }
+ else
+ z->sign = x_copy.sign;
+ z->exponent = x_copy.exponent + 4;
+
+ /* FORM PRODUCT IN ACCUMULATOR */
+ c = 0;
+
+ /* IF y*B NOT REPRESENTABLE AS AN INTEGER WE HAVE TO SIMULATE
+ * DOUBLE-PRECISION MULTIPLICATION.
+ */
+
+ /* Computing MAX */
+ if (y >= max(MP_BASE << 3, 32767 / MP_BASE)) {
+ int64_t j1, j2;
+
+ /* HERE J IS TOO LARGE FOR SINGLE-PRECISION MULTIPLICATION */
+ j1 = y / MP_BASE;
+ j2 = y - j1 * MP_BASE;
+
+ /* FORM PRODUCT */
+ for (i = MP_T + 3; i >= 0; i--) {
+ int64_t c1, c2, is, ix, t;
+
+ c1 = c / MP_BASE;
+ c2 = c - MP_BASE * c1;
+ ix = 0;
+ if (i > 3)
+ ix = x_copy.fraction[i - 4];
+
+ t = j2 * ix + c2;
+ is = t / MP_BASE;
+ c = j1 * ix + c1 + is;
+ z->fraction[i] = t - MP_BASE * is;
+ }
+ }
+ else
+ {
+ int64_t ri = 0;
+
+ for (i = MP_T + 3; i >= 4; i--) {
+ ri = y * x_copy.fraction[i - 4] + c;
+ c = ri / MP_BASE;
+ z->fraction[i] = ri - MP_BASE * c;
+ }
+
+ /* CHECK FOR INTEGER OVERFLOW */
+ if (ri < 0) {
+ mperr("*** INTEGER OVERFLOW IN mp_multiply_integer, B TOO LARGE ***");
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* HAVE TO TREAT FIRST FOUR WORDS OF R SEPARATELY */
+ for (i = 3; i >= 0; i--) {
+ int t;
+
+ t = c;
+ c = t / MP_BASE;
+ z->fraction[i] = t - MP_BASE * c;
+ }
+ }
+
+ /* HAVE TO SHIFT RIGHT HERE AS CARRY OFF END */
+ while (c != 0) {
+ int64_t t;
+
+ for (i = MP_T + 3; i >= 1; i--)
+ z->fraction[i] = z->fraction[i - 1];
+ t = c;
+ c = t / MP_BASE;
+ z->fraction[0] = t - MP_BASE * c;
+ z->exponent++;
+ }
+
+ if (c < 0) {
+ mperr("*** INTEGER OVERFLOW IN mp_multiply_integer, B TOO LARGE ***");
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ z->im_sign = 0;
+ z->im_exponent = 0;
+ memset(z->im_fraction, 0, sizeof(int) * MP_SIZE);
+ mp_normalize(z);
+}
+
+
+void
+mp_multiply_integer(const MPNumber *x, int64_t y, MPNumber *z)
+{
+ if (mp_is_complex(x)) {
+ MPNumber re_z, im_z;
+ mp_real_component(x, &re_z);
+ mp_imaginary_component(x, &im_z);
+ mp_multiply_integer_real(&re_z, y, &re_z);
+ mp_multiply_integer_real(&im_z, y, &im_z);
+ mp_set_from_complex(&re_z, &im_z, z);
+ }
+ else
+ mp_multiply_integer_real(x, y, z);
+}
+
+
+void
+mp_multiply_fraction(const MPNumber *x, int64_t numerator, int64_t denominator, MPNumber *z)
+{
+ if (denominator == 0) {
+ mperr(_("Division by zero is undefined"));
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ if (numerator == 0) {
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* Reduce to lowest terms */
+ mp_gcd(&numerator, &denominator);
+ mp_divide_integer(x, denominator, z);
+ mp_multiply_integer(z, numerator, z);
+}
+
+
+void
+mp_invert_sign(const MPNumber *x, MPNumber *z)
+{
+ mp_set_from_mp(x, z);
+ z->sign = -z->sign;
+ z->im_sign = -z->im_sign;
+}
+
+
+// FIXME: Is r->fraction large enough? It seems to be in practise but it may be MP_T+4 instead of MP_T
+// FIXME: There is some sort of stack corruption/use of unitialised variables here. Some functions are
+// using stack variables as x otherwise there are corruption errors. e.g. "Cos(45) - 1/Sqrt(2) = -0"
+// (try in scientific mode)
+void
+mp_normalize(MPNumber *x)
+{
+ int start_index;
+
+ /* Find first non-zero digit */
+ for (start_index = 0; start_index < MP_SIZE && x->fraction[start_index] == 0; start_index++);
+
+ /* Mark as zero */
+ if (start_index >= MP_SIZE) {
+ x->sign = 0;
+ x->exponent = 0;
+ return;
+ }
+
+ /* Shift left so first digit is non-zero */
+ if (start_index > 0) {
+ int i;
+
+ x->exponent -= start_index;
+ for (i = 0; (i + start_index) < MP_SIZE; i++)
+ x->fraction[i] = x->fraction[i + start_index];
+ for (; i < MP_SIZE; i++)
+ x->fraction[i] = 0;
+ }
+}
+
+
+static void
+mp_pwr(const MPNumber *x, const MPNumber *y, MPNumber *z)
+{
+ MPNumber t;
+
+ /* (-x)^y imaginary */
+ /* FIXME: Make complex numbers optional */
+ /*if (x->sign < 0) {
+ mperr(_("The power of negative numbers is only defined for integer exponents"));
+ mp_set_from_integer(0, z);
+ return;
+ }*/
+
+ /* 0^-y illegal */
+ if (mp_is_zero(x) && y->sign < 0) {
+ mperr(_("The power of zero is undefined for a negative exponent"));
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* x^0 = 1 */
+ if (mp_is_zero(y)) {
+ mp_set_from_integer(1, z);
+ return;
+ }
+
+ mp_ln(x, &t);
+ mp_multiply(y, &t, z);
+ mp_epowy(z, z);
+}
+
+
+static void
+mp_reciprocal_real(const MPNumber *x, MPNumber *z)
+{
+ MPNumber t1, t2;
+ int it0, t;
+
+ /* 1/0 invalid */
+ if (mp_is_zero(x)) {
+ mperr(_("Reciprocal of zero is undefined"));
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* Start by approximating value using floating point */
+ mp_set_from_mp(x, &t1);
+ t1.exponent = 0;
+ mp_set_from_float(1.0f / mp_cast_to_float(&t1), &t1);
+ t1.exponent -= x->exponent;
+
+ it0 = t = 3;
+ while(1) {
+ int ts2, ts3;
+
+ /* t1 = t1 - (t1 * ((x * t1) - 1)) (2*t1 - t1^2*x) */
+ mp_multiply(x, &t1, &t2);
+ mp_add_integer(&t2, -1, &t2);
+ mp_multiply(&t1, &t2, &t2);
+ mp_subtract(&t1, &t2, &t1);
+ if (t >= MP_T)
+ break;
+
+ /* FOLLOWING LOOP ALMOST DOUBLES T (POSSIBLE
+ * BECAUSE NEWTONS METHOD HAS 2ND ORDER CONVERGENCE).
+ */
+ ts3 = t;
+ t = MP_T;
+ do {
+ ts2 = t;
+ t = (t + it0) / 2;
+ } while (t > ts3);
+ t = min(ts2, MP_T);
+ }
+
+ /* RETURN IF NEWTON ITERATION WAS CONVERGING */
+ if (t2.sign != 0 && (t1.exponent - t2.exponent) << 1 < MP_T - it0) {
+ /* THE FOLLOWING MESSAGE MAY INDICATE THAT B**(T-1) IS TOO SMALL,
+ * OR THAT THE STARTING APPROXIMATION IS NOT ACCURATE ENOUGH.
+ */
+ mperr("*** ERROR OCCURRED IN MP_RECIPROCAL, NEWTON ITERATION NOT CONVERGING PROPERLY ***");
+ }
+
+ mp_set_from_mp(&t1, z);
+}
+
+
+void
+mp_reciprocal(const MPNumber *x, MPNumber *z)
+{
+ if (mp_is_complex(x)) {
+ MPNumber t1, t2;
+ MPNumber real_x, im_x;
+
+ mp_real_component(x, &real_x);
+ mp_imaginary_component(x, &im_x);
+
+ /* 1/(a+bi) = (a-bi)/(a+bi)(a-bi) = (a-bi)/(a²+b²) */
+ mp_multiply(&real_x, &real_x, &t1);
+ mp_multiply(&im_x, &im_x, &t2);
+ mp_add(&t1, &t2, &t1);
+ mp_reciprocal_real(&t1, z);
+ mp_conjugate(x, &t1);
+ mp_multiply(&t1, z, z);
+ }
+ else
+ mp_reciprocal_real(x, z);
+}
+
+
+static void
+mp_root_real(const MPNumber *x, int64_t n, MPNumber *z)
+{
+ float approximation;
+ int ex, np, it0, t;
+ MPNumber t1, t2;
+
+ /* x^(1/1) = x */
+ if (n == 1) {
+ mp_set_from_mp(x, z);
+ return;
+ }
+
+ /* x^(1/0) invalid */
+ if (n == 0) {
+ mperr(_("Root must be non-zero"));
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ np = abs(n);
+
+ /* LOSS OF ACCURACY IF NP LARGE, SO ONLY ALLOW NP <= MAX (B, 64) */
+ if (np > max(MP_BASE, 64)) {
+ mperr("*** ABS(N) TOO LARGE IN CALL TO MP_ROOT ***");
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* 0^(1/n) = 0 for positive n */
+ if (mp_is_zero(x)) {
+ mp_set_from_integer(0, z);
+ if (n <= 0)
+ mperr(_("Negative root of zero is undefined"));
+ return;
+ }
+
+ // FIXME: Imaginary root
+ if (x->sign < 0 && np % 2 == 0) {
+ mperr(_("nth root of negative number is undefined for even n"));
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* DIVIDE EXPONENT BY NP */
+ ex = x->exponent / np;
+
+ /* Get initial approximation */
+ mp_set_from_mp(x, &t1);
+ t1.exponent = 0;
+ approximation = exp(((float)(np * ex - x->exponent) * log((float)MP_BASE) -
+ log((fabs(mp_cast_to_float(&t1))))) / (float)np);
+ mp_set_from_float(approximation, &t1);
+ t1.sign = x->sign;
+ t1.exponent -= ex;
+
+ /* MAIN ITERATION LOOP */
+ it0 = t = 3;
+ while(1) {
+ int ts2, ts3;
+
+ /* t1 = t1 - ((t1 * ((x * t1^np) - 1)) / np) */
+ mp_xpowy_integer(&t1, np, &t2);
+ mp_multiply(x, &t2, &t2);
+ mp_add_integer(&t2, -1, &t2);
+ mp_multiply(&t1, &t2, &t2);
+ mp_divide_integer(&t2, np, &t2);
+ mp_subtract(&t1, &t2, &t1);
+
+ /* FOLLOWING LOOP ALMOST DOUBLES T (POSSIBLE BECAUSE
+ * NEWTONS METHOD HAS 2ND ORDER CONVERGENCE).
+ */
+ if (t >= MP_T)
+ break;
+
+ ts3 = t;
+ t = MP_T;
+ do {
+ ts2 = t;
+ t = (t + it0) / 2;
+ } while (t > ts3);
+ t = min(ts2, MP_T);
+ }
+
+ /* NOW R(I2) IS X**(-1/NP)
+ * CHECK THAT NEWTON ITERATION WAS CONVERGING
+ */
+ if (t2.sign != 0 && (t1.exponent - t2.exponent) << 1 < MP_T - it0) {
+ /* THE FOLLOWING MESSAGE MAY INDICATE THAT B**(T-1) IS TOO SMALL,
+ * OR THAT THE INITIAL APPROXIMATION OBTAINED USING ALOG AND EXP
+ * IS NOT ACCURATE ENOUGH.
+ */
+ mperr("*** ERROR OCCURRED IN MP_ROOT, NEWTON ITERATION NOT CONVERGING PROPERLY ***");
+ }
+
+ if (n >= 0) {
+ mp_xpowy_integer(&t1, n - 1, &t1);
+ mp_multiply(x, &t1, z);
+ return;
+ }
+
+ mp_set_from_mp(&t1, z);
+}
+
+
+void
+mp_root(const MPNumber *x, int64_t n, MPNumber *z)
+{
+ if (!mp_is_complex(x) && mp_is_negative(x) && n % 2 == 1) {
+ mp_abs(x, z);
+ mp_root_real(z, n, z);
+ mp_invert_sign(z, z);
+ }
+ else if (mp_is_complex(x) || mp_is_negative(x)) {
+ MPNumber r, theta;
+
+ mp_abs(x, &r);
+ mp_arg(x, MP_RADIANS, &theta);
+
+ mp_root_real(&r, n, &r);
+ mp_divide_integer(&theta, n, &theta);
+ mp_set_from_polar(&r, MP_RADIANS, &theta, z);
+ }
+ else
+ mp_root_real(x, n, z);
+}
+
+
+void
+mp_sqrt(const MPNumber *x, MPNumber *z)
+{
+ if (mp_is_zero(x))
+ mp_set_from_integer(0, z);
+ /* FIXME: Make complex numbers optional */
+ /*else if (x->sign < 0) {
+ mperr(_("Square root is undefined for negative values"));
+ mp_set_from_integer(0, z);
+ }*/
+ else {
+ MPNumber t;
+
+ mp_root(x, -2, &t);
+ mp_multiply(x, &t, z);
+ mp_ext(t.fraction[0], z->fraction[0], z);
+ }
+}
+
+
+void
+mp_factorial(const MPNumber *x, MPNumber *z)
+{
+ int i, value;
+
+ /* 0! == 1 */
+ if (mp_is_zero(x)) {
+ mp_set_from_integer(1, z);
+ return;
+ }
+ if (!mp_is_natural(x)) {
+ /* Translators: Error displayed when attempted take the factorial of a fractional number */
+ mperr(_("Factorial is only defined for natural numbers"));
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* Convert to integer - if couldn't be converted then the factorial would be too big anyway */
+ value = mp_cast_to_int(x);
+ mp_set_from_mp(x, z);
+ for (i = 2; i < value; i++)
+ mp_multiply_integer(z, i, z);
+}
+
+
+void
+mp_modulus_divide(const MPNumber *x, const MPNumber *y, MPNumber *z)
+{
+ MPNumber t1, t2;
+
+ if (!mp_is_integer(x) || !mp_is_integer(y)) {
+ /* Translators: Error displayed when attemping to do a modulus division on non-integer numbers */
+ mperr(_("Modulus division is only defined for integers"));
+ mp_set_from_integer(0, z);
+ }
+
+ mp_divide(x, y, &t1);
+ mp_floor(&t1, &t1);
+ mp_multiply(&t1, y, &t2);
+ mp_subtract(x, &t2, z);
+
+ mp_set_from_integer(0, &t1);
+ if ((mp_is_less_than(y, &t1) && mp_is_greater_than(z, &t1)) || mp_is_less_than(z, &t1))
+ mp_add(z, y, z);
+}
+
+
+void
+mp_xpowy(const MPNumber *x, const MPNumber *y, MPNumber *z)
+{
+ if (mp_is_integer(y)) {
+ mp_xpowy_integer(x, mp_cast_to_int(y), z);
+ } else {
+ MPNumber reciprocal;
+ mp_reciprocal(y, &reciprocal);
+ if (mp_is_integer(&reciprocal))
+ mp_root(x, mp_cast_to_int(&reciprocal), z);
+ else
+ mp_pwr(x, y, z);
+ }
+}
+
+
+void
+mp_xpowy_integer(const MPNumber *x, int64_t n, MPNumber *z)
+{
+ int i;
+ MPNumber t;
+
+ /* 0^-n invalid */
+ if (mp_is_zero(x) && n < 0) {
+ /* Translators: Error displayed when attempted to raise 0 to a negative exponent */
+ mperr(_("The power of zero is undefined for a negative exponent"));
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* x^0 = 1 */
+ if (n == 0) {
+ mp_set_from_integer(1, z);
+ return;
+ }
+
+ /* 0^n = 0 */
+ if (mp_is_zero(x)) {
+ mp_set_from_integer(0, z);
+ return;
+ }
+
+ /* x^1 = x */
+ if (n == 1) {
+ mp_set_from_mp(x, z);
+ return;
+ }
+
+ if (n < 0) {
+ mp_reciprocal(x, &t);
+ n = -n;
+ }
+ else
+ mp_set_from_mp(x, &t);
+
+ /* Multply x n times */
+ // FIXME: Can do mp_multiply(z, z, z) until close to answer (each call doubles number of multiples) */
+ mp_set_from_integer(1, z);
+ for (i = 0; i < n; i++)
+ mp_multiply(z, &t, z);
+}
+
+
+GList*
+mp_factorize(const MPNumber *x)
+{
+ GList *list = NULL;
+ MPNumber *factor = g_slice_alloc0(sizeof(MPNumber));
+ MPNumber value, tmp, divisor, root;
+
+ mp_abs(x, &value);
+
+ if (mp_is_zero(&value)) {
+ mp_set_from_mp(&value, factor);
+ list = g_list_append(list, factor);
+ return list;
+ }
+
+ mp_set_from_integer(1, &tmp);
+ if (mp_is_equal(&value, &tmp)) {
+ mp_set_from_mp(x, factor);
+ list = g_list_append(list, factor);
+ return list;
+ }
+
+ mp_set_from_integer(2, &divisor);
+ while (TRUE) {
+ mp_divide(&value, &divisor, &tmp);
+ if (mp_is_integer(&tmp)) {
+ value = tmp;
+ mp_set_from_mp(&divisor, factor);
+ list = g_list_append(list, factor);
+ factor = g_slice_alloc0(sizeof(MPNumber));
+ } else {
+ break;
+ }
+ }
+
+ mp_set_from_integer(3, &divisor);
+ mp_sqrt(&value, &root);
+ while (mp_is_less_equal(&divisor, &root)) {
+ mp_divide(&value, &divisor, &tmp);
+ if (mp_is_integer(&tmp)) {
+ value = tmp;
+ mp_sqrt(&value, &root);
+ mp_set_from_mp(&divisor, factor);
+ list = g_list_append(list, factor);
+ factor = g_slice_alloc0(sizeof(MPNumber));
+ } else {
+ mp_add_integer(&divisor, 2, &tmp);
+ divisor = tmp;
+ }
+ }
+
+ mp_set_from_integer(1, &tmp);
+ if (mp_is_greater_than(&value, &tmp)) {
+ mp_set_from_mp(&value, factor);
+ list = g_list_append(list, factor);
+ } else {
+ g_slice_free(MPNumber, factor);
+ }
+
+ if (mp_is_negative(x)) {
+ mp_invert_sign(list->data, list->data);
+ }
+
+ return list;
+}
diff --git a/src/mp.h b/src/mp.h
new file mode 100644
index 0000000..d71111d
--- /dev/null
+++ b/src/mp.h
@@ -0,0 +1,357 @@
+
+/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+/* This maths library is based on the MP multi-precision floating-point
+ * arithmetic package originally written in FORTRAN by Richard Brent,
+ * Computer Centre, Australian National University in the 1970's.
+ *
+ * It has been converted from FORTRAN into C using the freely available
+ * f2c translator, available via netlib on research.att.com.
+ *
+ * The subsequently converted C code has then been tidied up, mainly to
+ * remove any dependencies on the libI77 and libF77 support libraries.
+ *
+ * FOR A GENERAL DESCRIPTION OF THE PHILOSOPHY AND DESIGN OF MP,
+ * SEE - R. P. BRENT, A FORTRAN MULTIPLE-PRECISION ARITHMETIC
+ * PACKAGE, ACM TRANS. MATH. SOFTWARE 4 (MARCH 1978), 57-70.
+ * SOME ADDITIONAL DETAILS ARE GIVEN IN THE SAME ISSUE, 71-81.
+ * FOR DETAILS OF THE IMPLEMENTATION, CALLING SEQUENCES ETC. SEE
+ * THE MP USERS GUIDE.
+ */
+
+#ifndef MP_H
+#define MP_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <glib.h>
+
+/* Size of the multiple precision values */
+#define MP_SIZE 1000
+
+/* Base for numbers */
+#define MP_BASE 10000
+
+/* Object for a high precision floating point number representation
+ *
+ * x = sign * (MP_BASE^(exponent-1) + MP_BASE^(exponent-2) + ...)
+ */
+typedef struct
+{
+ /* Sign (+1, -1) or 0 for the value zero */
+ int sign, im_sign;
+
+ /* Exponent (to base MP_BASE) */
+ int exponent, im_exponent;
+
+ /* Normalized fraction */
+ int fraction[MP_SIZE], im_fraction[MP_SIZE];
+} MPNumber;
+
+typedef enum
+{
+ MP_RADIANS,
+ MP_DEGREES,
+ MP_GRADIANS
+} MPAngleUnit;
+
+/* Returns error string or NULL if no error */
+// FIXME: Global variable
+const char *mp_get_error(void);
+
+/* Clear any current error */
+void mp_clear_error(void);
+
+/* Returns:
+ * 0 if x == y
+ * <0 if x < y
+ * >0 if x > y
+ */
+int mp_compare_mp_to_mp(const MPNumber *x, const MPNumber *y);
+
+/* Return true if the value is x == 0 */
+bool mp_is_zero(const MPNumber *x);
+
+/* Return true if x < 0 */
+bool mp_is_negative(const MPNumber *x);
+
+/* Return true if x is integer */
+bool mp_is_integer(const MPNumber *x);
+
+/* Return true if x is a positive integer */
+bool mp_is_positive_integer(const MPNumber *x);
+
+/* Return true if x is a natural number (an integer ≥ 0) */
+bool mp_is_natural(const MPNumber *x);
+
+/* Return true if x has an imaginary component */
+bool mp_is_complex(const MPNumber *x);
+
+/* Return true if x == y */
+bool mp_is_equal(const MPNumber *x, const MPNumber *y);
+
+/* Return true if x ≥ y */
+bool mp_is_greater_equal(const MPNumber *x, const MPNumber *y);
+
+/* Return true if x > y */
+bool mp_is_greater_than(const MPNumber *x, const MPNumber *y);
+
+/* Return true if x ≤ y */
+bool mp_is_less_equal(const MPNumber *x, const MPNumber *y);
+
+/* Return true if x < y */
+bool mp_is_less_than(const MPNumber *x, const MPNumber *y);
+
+/* Sets z = |x| */
+void mp_abs(const MPNumber *x, MPNumber *z);
+
+/* Sets z = Arg(x) */
+void mp_arg(const MPNumber *x, MPAngleUnit unit, MPNumber *z);
+
+/* Sets z = ‾̅x */
+void mp_conjugate(const MPNumber *x, MPNumber *z);
+
+/* Sets z = Re(x) */
+void mp_real_component(const MPNumber *x, MPNumber *z);
+
+/* Sets z = Im(x) */
+void mp_imaginary_component(const MPNumber *x, MPNumber *z);
+
+/* Sets z = −x */
+void mp_invert_sign(const MPNumber *x, MPNumber *z);
+
+/* Sets z = x + y */
+void mp_add(const MPNumber *x, const MPNumber *y, MPNumber *z);
+
+/* Sets z = x + y */
+void mp_add_integer(const MPNumber *x, int64_t y, MPNumber *z);
+
+/* Sets z = x + numerator ÷ denominator */
+void mp_add_fraction(const MPNumber *x, int64_t numerator, int64_t denominator, MPNumber *z);
+
+/* Sets z = x − y */
+void mp_subtract(const MPNumber *x, const MPNumber *y, MPNumber *z);
+
+/* Sets z = x × y */
+void mp_multiply(const MPNumber *x, const MPNumber *y, MPNumber *z);
+
+/* Sets z = x × y */
+void mp_multiply_integer(const MPNumber *x, int64_t y, MPNumber *z);
+
+/* Sets z = x × numerator ÷ denominator */
+void mp_multiply_fraction(const MPNumber *x, int64_t numerator, int64_t denominator, MPNumber *z);
+
+/* Sets z = x ÷ y */
+void mp_divide(const MPNumber *x, const MPNumber *y, MPNumber *z);
+
+/* Sets z = x ÷ y */
+void mp_divide_integer(const MPNumber *x, int64_t y, MPNumber *z);
+
+/* Sets z = 1 ÷ x */
+void mp_reciprocal(const MPNumber *, MPNumber *);
+
+/* Sets z = sgn(x) */
+void mp_sgn(const MPNumber *x, MPNumber *z);
+
+void mp_integer_component(const MPNumber *x, MPNumber *z);
+
+/* Sets z = x mod 1 */
+void mp_fractional_component(const MPNumber *x, MPNumber *z);
+
+/* Sets z = {x} */
+void mp_fractional_part(const MPNumber *x, MPNumber *z);
+
+/* Sets z = ⌊x⌋ */
+void mp_floor(const MPNumber *x, MPNumber *z);
+
+/* Sets z = ⌈x⌉ */
+void mp_ceiling(const MPNumber *x, MPNumber *z);
+
+/* Sets z = [x] */
+void mp_round(const MPNumber *x, MPNumber *z);
+
+/* Sets z = ln x */
+void mp_ln(const MPNumber *x, MPNumber *z);
+
+/* Sets z = log_n x */
+void mp_logarithm(int64_t n, const MPNumber *x, MPNumber *z);
+
+/* Sets z = π */
+void mp_get_pi(MPNumber *z);
+
+/* Sets z = e */
+void mp_get_eulers(MPNumber *z);
+
+/* Sets z = i (√−1) */
+void mp_get_i(MPNumber *z);
+
+/* Sets z = n√x */
+void mp_root(const MPNumber *x, int64_t n, MPNumber *z);
+
+/* Sets z = √x */
+void mp_sqrt(const MPNumber *x, MPNumber *z);
+
+/* Sets z = x! */
+void mp_factorial(const MPNumber *x, MPNumber *z);
+
+/* Sets z = x mod y */
+void mp_modulus_divide(const MPNumber *x, const MPNumber *y, MPNumber *z);
+
+/* Sets z = x^y */
+void mp_xpowy(const MPNumber *x, const MPNumber *y, MPNumber *z);
+
+/* Sets z = x^y */
+void mp_xpowy_integer(const MPNumber *x, int64_t y, MPNumber *z);
+
+/* Sets z = e^x */
+void mp_epowy(const MPNumber *x, MPNumber *z);
+
+/* Returns a list of all prime factors in x as MPNumbers */
+GList* mp_factorize(const MPNumber *x);
+
+/* Sets z = x */
+void mp_set_from_mp(const MPNumber *x, MPNumber *z);
+
+/* Sets z = x */
+void mp_set_from_float(float x, MPNumber *z);
+
+/* Sets z = x */
+void mp_set_from_double(double x, MPNumber *z);
+
+/* Sets z = x */
+void mp_set_from_integer(int64_t x, MPNumber *z);
+
+/* Sets z = x */
+void mp_set_from_unsigned_integer(uint64_t x, MPNumber *z);
+
+/* Sets z = numerator ÷ denominator */
+void mp_set_from_fraction(int64_t numerator, int64_t denominator, MPNumber *z);
+
+/* Sets z = r(cos theta + i sin theta) */
+void mp_set_from_polar(const MPNumber *r, MPAngleUnit unit, const MPNumber *theta, MPNumber *z);
+
+/* Sets z = x + iy */
+void mp_set_from_complex(const MPNumber *x, const MPNumber *y, MPNumber *z);
+
+/* Sets z to be a uniform random number in the range [0, 1] */
+void mp_set_from_random(MPNumber *z);
+
+/* Sets z from a string representation in 'text'.
+ * Returns true on success.
+ */
+bool mp_set_from_string(const char *text, int default_base, MPNumber *z);
+
+/* Returns x as a native single-precision floating point number */
+float mp_cast_to_float(const MPNumber *x);
+
+/* Returns x as a native double-precision floating point number */
+double mp_cast_to_double(const MPNumber *x);
+
+/* Returns x as a native integer */
+int64_t mp_cast_to_int(const MPNumber *x);
+
+/* Returns x as a native unsigned integer */
+uint64_t mp_cast_to_unsigned_int(const MPNumber *x);
+
+/* Converts x to a string representation.
+ * The string is written into 'buffer' which is guaranteed to be at least 'buffer_length' octets in size.
+ * If not enough space is available the string is truncated.
+ * The numbers are written in 'base' (e.g. 10).
+ * If 'trim_zeroes' is non-zero then strip off trailing zeroes.
+ * Fractional components are truncated at 'max_digits' digits.
+ */
+void mp_cast_to_string(const MPNumber *x, int default_base, int base, int max_digits, bool trim_zeroes, char *buffer, int buffer_length);
+
+/* Converts x to a string representation in exponential form.
+ * The string is written into 'buffer' which is guaranteed to be at least 'buffer_length' octets in size.
+ * If not enough space is available the string is truncated.
+ * The numbers are written in 'base' (e.g. 10).
+ * If 'trim_zeroes' is non-zero then strip off trailing zeroes.
+ * Fractional components are truncated at 'max_digits' digits.
+ */
+void mp_cast_to_exponential_string(const MPNumber *x, int default_base, int base, int max_digits, bool trim_zeroes, bool eng_format, char *buffer, int buffer_length);
+
+/* Sets z = sin x */
+void mp_sin(const MPNumber *x, MPAngleUnit unit, MPNumber *z);
+
+/* Sets z = cos x */
+void mp_cos(const MPNumber *x, MPAngleUnit unit, MPNumber *z);
+
+/* Sets z = tan x */
+void mp_tan(const MPNumber *x, MPAngleUnit unit, MPNumber *z);
+
+/* Sets z = sin⁻¹ x */
+void mp_asin(const MPNumber *x, MPAngleUnit unit, MPNumber *z);
+
+/* Sets z = cos⁻¹ x */
+void mp_acos(const MPNumber *x, MPAngleUnit unit, MPNumber *z);
+
+/* Sets z = tan⁻¹ x */
+void mp_atan(const MPNumber *x, MPAngleUnit unit, MPNumber *z);
+
+/* Sets z = sinh x */
+void mp_sinh(const MPNumber *x, MPNumber *z);
+
+/* Sets z = cosh x */
+void mp_cosh(const MPNumber *x, MPNumber *z);
+
+/* Sets z = tanh x */
+void mp_tanh(const MPNumber *x, MPNumber *z);
+
+/* Sets z = sinh⁻¹ x */
+void mp_asinh(const MPNumber *x, MPNumber *z);
+
+/* Sets z = cosh⁻¹ x */
+void mp_acosh(const MPNumber *x, MPNumber *z);
+
+/* Sets z = tanh⁻¹ x */
+void mp_atanh(const MPNumber *x, MPNumber *z);
+
+/* Returns true if x is cannot be represented in a binary word of length 'wordlen' */
+bool mp_is_overflow(const MPNumber *x, int wordlen);
+
+/* Sets z = boolean AND for each bit in x and z */
+void mp_and(const MPNumber *x, const MPNumber *y, MPNumber *z);
+
+/* Sets z = boolean OR for each bit in x and z */
+void mp_or(const MPNumber *x, const MPNumber *y, MPNumber *z);
+
+/* Sets z = boolean XOR for each bit in x and z */
+void mp_xor(const MPNumber *x, const MPNumber *y, MPNumber *z);
+
+/* Sets z = boolean XNOR for each bit in x and z for word of length 'wordlen' */
+void mp_xnor(const MPNumber *x, const MPNumber *y, int wordlen, MPNumber *z);
+
+/* Sets z = boolean NOT for each bit in x and z for word of length 'wordlen' */
+void mp_not(const MPNumber *x, int wordlen, MPNumber *z);
+
+/* Sets z = x masked to 'wordlen' bits */
+void mp_mask(const MPNumber *x, int wordlen, MPNumber *z);
+
+/* Sets z = x shifted by 'count' bits. Positive shift increases the value, negative decreases */
+void mp_shift(const MPNumber *x, int count, MPNumber *z);
+
+/* Sets z to be the ones complement of x for word of length 'wordlen' */
+void mp_ones_complement(const MPNumber *x, int wordlen, MPNumber *z);
+
+/* Sets z to be the twos complement of x for word of length 'wordlen' */
+void mp_twos_complement(const MPNumber *x, int wordlen, MPNumber *z);
+
+#endif /* MP_H */
diff --git a/src/unittest.c b/src/unittest.c
new file mode 100644
index 0000000..791391b
--- /dev/null
+++ b/src/unittest.c
@@ -0,0 +1,761 @@
+/* Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "unittest.h"
+#include "mp-equation.h"
+
+static MPEquationOptions options;
+
+static int fails = 0;
+
+/* If we're not using GNU C, elide __attribute__ */
+#ifndef __GNUC__
+# define __attribute__(x) /*NOTHING*/
+#endif
+
+static void pass(const char *format, ...) __attribute__((format(printf, 1, 2)));
+static void fail(const char *format, ...) __attribute__((format(printf, 1, 2)));
+
+static void pass(const char *format, ...)
+{
+ va_list args;
+
+ printf(" PASS: ");
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ printf("\n");
+}
+
+static void fail(const char *format, ...)
+{
+ va_list args;
+
+ printf("*FAIL: ");
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ printf("\n");
+ fails++;
+}
+
+
+static const char *
+error_code_to_string(MPErrorCode error)
+{
+ static char error_string[1024];
+
+ if (error != PARSER_ERR_MP)
+ return mp_error_code_to_string(error);
+
+ snprintf(error_string, 1024, "PARSER_ERR_MP(\"%s\")", mp_get_error());
+ return error_string;
+}
+
+
+static void
+test(char *expression, char *expected, int expected_error)
+{
+ MPErrorCode error;
+ MPNumber result;
+ char result_str[1024] = "";
+
+ error = mp_equation_parse(expression, &options, &result, NULL);
+
+ if(error == 0) {
+ mp_cast_to_string(&result, options.base, options.base, 9, 1, result_str, 1024);
+ if(expected_error != 0)
+ fail("'%s' -> %s, expected error %s", expression, result_str, error_code_to_string(expected_error));
+ else if(strcmp(result_str, expected) != 0)
+ fail("'%s' -> '%s', expected '%s'", expression, result_str, expected);
+ else
+ pass("'%s' -> '%s'", expression, result_str);
+ }
+ else {
+ if(error == expected_error)
+ pass("'%s' -> error %s", expression, error_code_to_string(error));
+ else
+ fail("'%s' -> error %s, expected error %s", expression,
+ error_code_to_string(error), error_code_to_string(expected_error));
+ }
+}
+
+
+static void
+test_conversions()
+{
+ memset(&options, 0, sizeof(options));
+ options.base = 10;
+ options.wordlen = 32;
+ options.angle_units = MP_DEGREES;
+
+ /* Length */
+ test("1 meter in mm", "1000", 0);
+ test("1m in mm", "1000", 0);
+ test("1 inch in cm", "2.54", 0);
+
+ /* Area */
+ test("1m² in mm²", "1000000", 0);
+
+ /* Volume */
+ test("1m³ in mm³", "1000000000", 0);
+
+ /* Weight */
+ test("1 kg in pounds", "2.204622622", 0);
+
+ /* Time */
+ test("1 minute in seconds", "60", 0);
+}
+
+
+static int
+variable_is_defined(const char *name, void *data)
+{
+ return strcmp (name, "x") == 0 || strcmp (name, "y") == 0;
+}
+
+
+static int
+get_variable(const char *name, MPNumber *z, void *data)
+{
+ if (strcmp (name, "x") == 0) {
+ mp_set_from_integer (2, z);
+ return 1;
+ }
+ if (strcmp (name, "y") == 0) {
+ mp_set_from_integer (3, z);
+ return 1;
+ }
+ return 0;
+}
+
+
+static void
+set_variable(const char *name, const MPNumber *x, void *data)
+{
+}
+
+static void
+test_equations()
+{
+ memset(&options, 0, sizeof(options));
+ options.base = 10;
+ options.wordlen = 32;
+ options.angle_units = MP_DEGREES;
+ options.variable_is_defined = variable_is_defined;
+ options.get_variable = get_variable;
+ options.set_variable = set_variable;
+
+ options.base = 2;
+ test("2₁₀", "10", 0);
+
+ options.base = 8;
+ test("16434824₁₀", "76543210", 0);
+
+ options.base = 16;
+ test("FF", "FF", 0);
+ test("18364758544493064720₁₀", "FEDCBA9876543210", 0);
+
+ options.base = 10;
+ test("0₂", "0", 0); test("0₈", "0", 0); test("0", "0", 0); test("0₁₆", "0", 0);
+ test("1₂", "1", 0); test("1₈", "1", 0); test("1", "1", 0); test("1₁₆", "1", 0);
+ test("2₂", "", PARSER_ERR_INVALID); test("2₈", "2", 0); test("2", "2", 0); test("2₁₆", "2", 0);
+ test("3₂", "", PARSER_ERR_INVALID); test("3₈", "3", 0); test("3", "3", 0); test("3₁₆", "3", 0);
+ test("4₂", "", PARSER_ERR_INVALID); test("4₈", "4", 0); test("4", "4", 0); test("4₁₆", "4", 0);
+ test("5₂", "", PARSER_ERR_INVALID); test("5₈", "5", 0); test("5", "5", 0); test("5₁₆", "5", 0);
+ test("6₂", "", PARSER_ERR_INVALID); test("6₈", "6", 0); test("6", "6", 0); test("6₁₆", "6", 0);
+ test("7₂", "", PARSER_ERR_INVALID); test("7₈", "7", 0); test("7", "7", 0); test("7₁₆", "7", 0);
+ test("8₂", "", PARSER_ERR_INVALID); test("8₈", "", PARSER_ERR_INVALID); test("8", "8", 0); test("8₁₆", "8", 0);
+ test("9₂", "", PARSER_ERR_INVALID); test("9₈", "", PARSER_ERR_INVALID); test("9", "9", 0); test("9₁₆", "9", 0);
+ test("A₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("A₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("A", "", PARSER_ERR_UNKNOWN_VARIABLE); test("A₁₆", "10", 0);
+ test("B₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("B₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("B", "", PARSER_ERR_UNKNOWN_VARIABLE); test("B₁₆", "11", 0);
+ test("C₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("C₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("C", "", PARSER_ERR_UNKNOWN_VARIABLE); test("C₁₆", "12", 0);
+ test("D₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("D₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("D", "", PARSER_ERR_UNKNOWN_VARIABLE); test("D₁₆", "13", 0);
+ test("E₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("E₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("E", "", PARSER_ERR_UNKNOWN_VARIABLE); test("E₁₆", "14", 0);
+ test("F₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("F₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("F", "", PARSER_ERR_UNKNOWN_VARIABLE); test("F₁₆", "15", 0);
+ test("a₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("a₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("a", "", PARSER_ERR_UNKNOWN_VARIABLE); test("a₁₆", "10", 0);
+ test("b₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("b₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("b", "", PARSER_ERR_UNKNOWN_VARIABLE); test("b₁₆", "11", 0);
+ test("c₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("c₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("c", "", PARSER_ERR_UNKNOWN_VARIABLE); test("c₁₆", "12", 0);
+ test("d₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("d₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("d", "", PARSER_ERR_UNKNOWN_VARIABLE); test("d₁₆", "13", 0);
+ test("e₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("e₈", "", PARSER_ERR_UNKNOWN_VARIABLE); /* e is a built-in variable */ test("e₁₆", "14", 0);
+ test("f₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("f₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("f", "", PARSER_ERR_UNKNOWN_VARIABLE); test("f₁₆", "15", 0);
+
+ test("+1", "1", 0);
+ test("−1", "−1", 0);
+ test("+ 1", "1", 0); // FIXME: Should this be allowed?
+ test("− 1", "−1", 0); // FIXME: Should this be allowed?
+ test("++1", "1", PARSER_ERR_INVALID);
+ test("−−1", "1", 0);
+ test("255", "255", 0);
+ test("256", "256", 0);
+ test("½", "0.5", 0);
+ test("1½", "1.5", 0);
+ test("0°", "0", 0);
+ test("1°", "1", 0);
+ test("0°30'", "0.5", 0);
+ //test("0°0.1'", "1", 0); // FIXME: Not yet supported
+ test("0°0'1\"", "0.000277778", 0);
+ test("0°0'0.1\"", "0.000027778", 0);
+ test("1.00", "1", 0);
+ test("1.01", "1.01", 0);
+
+ test("١٢٣٤٥٦٧٨٩٠", "1234567890", 0);
+ test("۱۲۳۴۵۶۷۸۹۰", "1234567890", 0);
+
+/*
+ //test("2A", "2000000000000000", 0);
+ test("2T", "2000000000000", 0);
+ test("2G", "2000000000", 0);
+ test("2M", "2000000", 0);
+ test("2k", "2000", 0);
+ test("2c", "0.02", 0);
+ test("2d", "0.2", 0);
+ test("2c", "0.02", 0);
+ test("2m", "0.002", 0);
+ test("2u", "0.000002", 0);
+ test("2µ", "0.000002", 0);
+ test("2n", "0.000000002", 0);
+ //test("2p", "0.000000000002", 0); // FIXME: Need to print out significant figures, not decimal places
+ //test("2f", "0.000000000000002", 0); // FIXME: Need to print out significant figures, not decimal places
+ //test("2A3", "2300000000000000", 0);
+ test("2T3", "2300000000000", 0);
+ test("2G3", "2300000000", 0);
+ test("2M3", "2300000", 0);
+ test("2k3", "2300", 0);
+ test("2c3", "0.023", 0);
+ test("2d3", "0.23", 0);
+ test("2c3", "0.023", 0);
+ test("2m3", "0.0023", 0);
+ test("2u3", "0.0000023", 0);
+ test("2µ3", "0.0000023", 0);
+ //test("2n3", "0.0000000023", 0); // FIXME: Need to print out significant figures, not decimal places
+ //test("2p3", "0.0000000000023", 0); // FIXME: Need to print out significant figures, not decimal places
+ //test("2f3", "0.0000000000000023", 0); // FIXME: Need to print out significant figures, not decimal places
+*/
+
+ test("2×10^3", "2000", 0);
+ test("2×10^−3", "0.002", 0);
+
+ test("x", "2", 0);
+ test("y", "3", 0);
+ test("z", "", PARSER_ERR_UNKNOWN_VARIABLE);
+ test("2y", "6", 0);
+ test("y2", "", PARSER_ERR_INVALID);
+ test("y 2", "", PARSER_ERR_INVALID);
+ test("2z", "", PARSER_ERR_UNKNOWN_VARIABLE);
+ test("z2", "", PARSER_ERR_UNKNOWN_VARIABLE);
+ test("z 2", "", PARSER_ERR_UNKNOWN_VARIABLE);
+ test("z(2)", "", PARSER_ERR_UNKNOWN_VARIABLE);
+ test("y²", "9", 0);
+ test("2y²", "18", 0);
+ test("x×y", "6", 0);
+ test("xy", "6", 0);
+ test("yx", "6", 0);
+ test("2xy", "12", 0);
+ test("x²y", "12", 0);
+ test("xy²", "18", 0);
+ test("(xy)²", "36", 0);
+ test("2x²y", "24", 0);
+ test("2xy²", "36", 0);
+ test("2x²y²", "72", 0);
+ test("x²yx²y", "144", 0);
+ test("x³+2x²−5", "11", 0);
+ test("2(x+3y)", "22", 0);
+ test("x(x+3y)", "22", 0);
+ test("(x+3y)(2x-4y)", "−88", 0);
+ test("2x²+2xy−12y²", "−88", 0);
+
+ test("π", "3.141592654", 0);
+ test("e", "2.718281828", 0);
+
+ test("z=99", "99", 0);
+ test("longname=99", "99", 0);
+ //test("e=99", "", PARSER_ERR_BUILTIN_VARIABLE);
+
+ test("0+0", "0", 0);
+ test("1+1", "2", 0);
+ test("1+4", "5", 0);
+ test("4+1", "5", 0);
+ test("40000+0.001", "40000.001", 0);
+ test("0.001+40000", "40000.001", 0);
+ test("2-3", "−1", 0);
+ test("2−3", "−1", 0);
+ test("3−2", "1", 0);
+ test("40000−0.001", "39999.999", 0);
+ test("0.001−40000", "−39999.999", 0);
+ test("2*3", "6", 0);
+ test("2×3", "6", 0);
+ test("−2×3", "−6", 0);
+ test("2×−3", "−6", 0);
+ test("−2×−3", "6", 0);
+ test("6/3", "2", 0);
+ test("6÷3", "2", 0);
+ test("1÷2", "0.5", 0);
+ test("−6÷3", "−2", 0);
+ test("6÷−3", "−2", 0);
+ test("−6÷−3", "2", 0);
+ test("(−3)÷(−6)", "0.5", 0);
+ test("2÷2", "1", 0);
+ test("1203÷1", "1203", 0);
+ test("−0÷32352.689", "0", 0);
+ test("1÷4", "0.25", 0);
+ test("1÷3", "0.333333333", 0);
+ test("2÷3", "0.666666667", 0);
+ test("1÷0", "", PARSER_ERR_MP);
+ test("0÷0", "", PARSER_ERR_MP);
+
+ /* Precision */
+ test("1000000000000000−1000000000000000", "0", 0);
+ test("1000000000000000÷1000000000000000", "1", 0);
+ test("1000000000000000×0.000000000000001", "1", 0);
+
+ /* Order of operations */
+ test("1−0.9−0.1", "0", 0);
+ test("1+2×3", "7", 0);
+ test("1+(2×3)", "7", 0);
+ test("(1+2)×3", "9", 0);
+ test("(1+2×3)", "7", 0);
+
+ /* Percentage */
+ test("100%", "1", 0);
+ test("1%", "0.01", 0);
+ test("100+1%", "101", 0);
+ test("100−1%", "99", 0);
+ test("100×1%", "1", 0);
+ test("100÷1%", "10000", 0);
+
+ /* Factorial */
+ test("0!", "1", 0);
+ test("1!", "1", 0);
+ test("5!", "120", 0);
+ test("69!", "171122452428141311372468338881272839092270544893520369393648040923257279754140647424000000000000000", 0);
+ test("0.1!", "", PARSER_ERR_MP);
+ test("−1!", "−1", 0);
+ test("(−1)!", "", PARSER_ERR_MP);
+ test("−(1!)", "−1", 0);
+
+ /* Powers */
+ test("2²", "4", 0);
+ test("2³", "8", 0);
+ test("2¹⁰", "1024", 0);
+ test("(1+2)²", "9", 0);
+ test("(x)²", "4", 0);
+ test("|1−3|²", "4", 0);
+ test("|x|²", "4", 0);
+ test("0^0", "1", 0);
+ test("2^0", "1", 0);
+ test("2^1", "2", 0);
+ test("2^2", "4", 0);
+ test("2⁻¹", "0.5", 0);
+ //test("2⁻", "", PARSER_ERR_MP); // FIXME: Maybe an error in bison?
+ test("2^−1", "0.5", 0);
+ test("2^(−1)", "0.5", 0);
+ test("x⁻¹", "0.5", 0);
+ test("−10^2", "−100", 0);
+ test("(−10)^2", "100", 0);
+ test("−(10^2)", "−100", 0);
+ test("2^100", "1267650600228229401496703205376", 0);
+ test("4^3^2", "262144", 0);
+ test("4^(3^2)", "262144", 0);
+ test("(4^3)^2", "4096", 0);
+ test("√4", "2", 0);
+ test("√4−2", "0", 0);
+ test("∛8", "2", 0);
+ test("∜16", "2", 0);
+ test("₃√8", "2", 0);
+ test("₁₀√1024", "2", 0);
+ test("√(2+2)", "2", 0);
+ test("2√4", "4", 0);
+ test("2×√4", "4", 0);
+ test("Sqrt(4)", "2", 0);
+ test("Sqrt(2)", "1.414213562", 0);
+ test("4^0.5", "2", 0);
+ test("2^0.5", "1.414213562", 0);
+ test("₃√−8", "−2", 0);
+ test("(−8)^(1÷3)", "−2", 0);
+
+ test("0 mod 7", "0", 0);
+ test("6 mod 7", "6", 0);
+ test("7 mod 7", "0", 0);
+ test("8 mod 7", "1", 0);
+ test("−1 mod 7", "6", 0);
+
+ test("sgn 0", "0", 0);
+ test("sgn 3", "1", 0);
+ test("sgn −3", "−1", 0);
+ test("⌊3⌋", "3", 0);
+ test("⌈3⌉", "3", 0);
+ test("[3]", "3", 0);
+ test("⌊−3⌋", "−3", 0);
+ test("⌈−3⌉", "−3", 0);
+ test("[−3]", "−3", 0);
+ test("⌊3.2⌋", "3", 0);
+ test("⌈3.2⌉", "4", 0);
+ test("[3.2]", "3", 0);
+ test("⌊−3.2⌋", "−4", 0);
+ test("⌈−3.2⌉", "−3", 0);
+ test("[−3.2]", "−3", 0);
+ test("⌊3.5⌋", "3", 0);
+ test("⌈3.5⌉", "4", 0);
+ test("[3.5]", "4", 0);
+ test("⌊−3.5⌋", "−4", 0);
+ test("⌈−3.5⌉", "−3", 0);
+ test("[−3.5]", "−4", 0);
+ test("⌊3.7⌋", "3", 0);
+ test("⌈3.7⌉", "4", 0);
+ test("[3.7]", "4", 0);
+ test("⌊−3.7⌋", "−4", 0);
+ test("⌈−3.7⌉", "−3", 0);
+ test("[−3.7]", "−4", 0);
+ test("{3.2}", "0.2", 0);
+ test("{−3.2}", "0.8", 0);
+
+ test("|1|", "1", 0);
+ test("|−1|", "1", 0);
+ test("|3−5|", "2", 0);
+ test("|x|", "2", 0);
+ test("abs 1", "1", 0);
+ test("abs (−1)", "1", 0);
+
+ test("log 0", "", PARSER_ERR_MP);
+ test("log 1", "0", 0);
+ test("log 2", "0.301029996", 0);
+ test("log 10", "1", 0);
+ test("log₁₀ 10", "1", 0);
+ test("log₂ 2", "1", 0);
+ test("2 log 2", "0.602059991", 0);
+
+ test("ln 0", "", PARSER_ERR_MP);
+ test("ln 1", "0", 0);
+ test("ln 2", "0.693147181", 0);
+ test("ln e", "1", 0);
+ test("2 ln 2", "1.386294361", 0);
+
+ options.angle_units = MP_DEGREES;
+ test("sin 0", "0", 0);
+ test("sin 45 − 1÷√2", "0", 0);
+ test("sin 20 + sin(−20)", "0", 0);
+ test("sin 90", "1", 0);
+ test("sin 180", "0", 0);
+ test("2 sin 90", "2", 0);
+ test("sin²45", "0.5", 0);
+
+ test("cos 0", "1", 0);
+ test("cos 45 − 1÷√2", "0", 0);
+ test("cos 20 − cos (−20)", "0", 0);
+ test("cos 90", "0", 0);
+ test("cos 180", "−1", 0);
+ test("2 cos 0", "2", 0);
+ test("cos²45", "0.5", 0);
+
+ test("tan 0", "0", 0);
+ test("tan 10 − sin 10÷cos 10", "0", 0);
+ test("tan 90", "", PARSER_ERR_MP);
+ test("tan 10", "0.176326981", 0);
+ test("tan²10", "0.031091204", 0);
+
+ test("cos⁻¹ 0", "90", 0);
+ test("cos⁻¹ 1", "0", 0);
+ test("cos⁻¹ (−1)", "180", 0);
+ test("cos⁻¹ (1÷√2)", "45", 0);
+
+ test("sin⁻¹ 0", "0", 0);
+ test("sin⁻¹ 1", "90", 0);
+ test("sin⁻¹ (−1)", "−90", 0);
+ test("sin⁻¹ (1÷√2)", "45", 0);
+
+ test("cosh 0", "1", 0);
+ test("cosh 10 − (e^10 + e^−10)÷2", "0", 0);
+
+ test("sinh 0", "0", 0);
+ test("sinh 10 − (e^10 − e^−10)÷2", "0", 0);
+ test("sinh (−10) + sinh 10", "0", 0);
+
+ test("cosh² (−5) − sinh² (−5)", "1", 0);
+ test("tanh 0", "0", 0);
+ test("tanh 10 − sinh 10 ÷ cosh 10", "0", 0);
+
+ test("atanh 0", "0", 0);
+ test("atanh (1÷10) − 0.5 ln(11÷9)", "0", 0);
+
+ options.angle_units = MP_DEGREES;
+ test("sin 90", "1", 0);
+
+ options.angle_units = MP_RADIANS;
+ test("sin (π÷2)", "1", 0); // FIXME: Shouldn't need brackets
+
+ options.angle_units = MP_GRADIANS;
+ test("sin 100", "1", 0);
+
+ /* Complex numbers */
+ options.angle_units = MP_DEGREES;
+ test("i", "i", 0);
+ test("−i", "−i", 0);
+ test("2i", "2i", 0);
+ test("1+i", "1+i", 0);
+ test("i+1", "1+i", 0);
+ test("1−i", "1−i", 0);
+ test("i−1", "−1+i", 0);
+ test("i×i", "−1", 0);
+ test("i÷i", "1", 0);
+ test("1÷i", "−i", 0);
+ test("|i|", "1", 0);
+ test("|3+4i|", "5", 0);
+ test("arg 0", "", PARSER_ERR_MP);
+ test("arg 1", "0", 0);
+ test("arg (1+i)", "45", 0);
+ test("arg i", "90", 0);
+ test("arg (−1+i)", "135", 0);
+ test("arg −1", "180", 0);
+ test("arg (1+−i)", "−45", 0);
+ test("arg −i", "−90", 0);
+ test("arg (−1−i)", "−135", 0);
+ test("i⁻¹", "−i", 0);
+ test("√−1", "i", 0);
+ test("(−1)^0.5", "i", 0);
+ test("√−4", "2i", 0);
+ test("e^iπ", "−1", 0);
+ test("log (−10) − (1 + πi÷ln(10))", "0", 0);
+ test("ln (−e) − (1 + πi)", "0", 0);
+ test("sin(iπ÷4) − i×sinh(π÷4)", "0", 0);
+ test("cos(iπ÷4) − cosh(π÷4)", "0", 0);
+
+ /* Boolean */
+ test("0 and 0", "0", 0);
+ test("1 and 0", "0", 0);
+ test("0 and 1", "0", 0);
+ test("1 and 1", "1", 0);
+ test("3 and 5", "1", 0);
+
+ test("0 or 0", "0", 0);
+ test("1 or 0", "1", 0);
+ test("0 or 1", "1", 0);
+ test("1 or 1", "1", 0);
+ test("3 or 5", "7", 0);
+
+ test("0 xor 0", "0", 0);
+ test("1 xor 0", "1", 0);
+ test("0 xor 1", "1", 0);
+ test("1 xor 1", "0", 0);
+ test("3 xor 5", "6", 0);
+
+ options.base = 16;
+ test("ones 1", "FFFFFFFE", 0);
+ test("ones 7FFFFFFF", "80000000", 0);
+ test("twos 1", "FFFFFFFF", 0);
+ test("twos 7FFFFFFF", "80000001", 0);
+ test("~7A₁₆", "FFFFFF85", 0);
+
+ options.base = 2;
+ options.wordlen = 4;
+ test("1100∧1010", "1000", 0);
+ test("1100∨1010", "1110", 0);
+ test("1100⊻1010", "110", 0);
+ test("1100⊕1010", "110", 0);
+ //test("1100⊼1010", "0111", 0);
+ //test("1100⊽1010", "0001", 0);
+ //options.wordlen = 2;
+ //test("¬01₂", "10₂", 0);
+ //test("¬¬10₂", "10₂", 0);
+}
+
+
+static void
+print_number(MPNumber *x)
+{
+ int i, j;
+
+ printf("sign=%d exponent=%d fraction=%d", x->sign, x->exponent, x->fraction[0]);
+ for (i = 1; i < MP_SIZE; i++) {
+ for (j = i; j < MP_SIZE && x->fraction[j] == 0; j++);
+ if (j == MP_SIZE) {
+ printf(",...");
+ break;
+ }
+ printf(",%d", x->fraction[i]);
+ }
+}
+
+static void
+test_string(const char *number)
+{
+ MPNumber t;
+
+ mp_set_from_string(number, 10, &t);
+
+ printf("MPNumber(%s) -> {", number);
+ print_number(&t);
+ printf("}\n");
+}
+
+static void
+test_integer(int number)
+{
+ MPNumber t;
+
+ mp_set_from_integer(number, &t);
+
+ printf("MPNumber(%d) -> {", number);
+ print_number(&t);
+ printf("}\n");
+}
+
+#include "mp-private.h"
+static void
+test_numbers()
+{
+ printf("base=%d\n", MP_BASE);
+ test_integer(0);
+ test_integer(1);
+ test_integer(-1);
+ test_integer(2);
+ test_integer(9999);
+ test_integer(10000);
+ test_integer(10001);
+ test_integer(2147483647);
+
+ test_string("0");
+ test_string("1");
+ test_string("-1");
+ test_string("16383");
+ test_string("16384");
+ test_string("16385");
+ test_string("268435456");
+
+ test_string("0.1");
+ test_string("0.5");
+ test_string("0.25");
+ test_string("0.125");
+ test_string("0.0625");
+ test_string("0.00006103515625");
+ test_string("0.000030517578125");
+
+ test_string("1.00006103515625");
+ test_string("16384.00006103515625");
+}
+
+
+static void
+try(const char *string, bool result, bool expected)
+{
+ if ((result && !expected) || (!result && expected))
+ fail("%s -> %s, expected %s", string, expected ? "true" : "false", result ? "true" : "false");
+ else
+ pass("%s -> %s", string, result ? "true" : "false");
+}
+
+
+static void
+test_mp()
+{
+ MPNumber zero, one, minus_one;
+
+ mp_set_from_integer(0, &zero);
+ mp_set_from_integer(1, &one);
+ mp_set_from_integer(-1, &minus_one);
+
+ try("mp_is_zero(-1)", mp_is_zero(&minus_one), false);
+ try("mp_is_zero(0)", mp_is_zero(&zero), true);
+ try("mp_is_zero(1)", mp_is_zero(&one), false);
+
+ try("mp_is_negative(-1)", mp_is_negative(&minus_one), true);
+ try("mp_is_negative(0)", mp_is_negative(&zero), false);
+ try("mp_is_negative(1)", mp_is_negative(&one), false);
+
+ try("mp_is_integer(-1)", mp_is_integer(&minus_one), true);
+ try("mp_is_integer(0)", mp_is_integer(&zero), true);
+ try("mp_is_integer(1)", mp_is_integer(&one), true);
+
+ try("mp_is_positive_integer(-1)", mp_is_positive_integer(&minus_one), false);
+ try("mp_is_positive_integer(0)", mp_is_positive_integer(&zero), true);
+ try("mp_is_positive_integer(1)", mp_is_positive_integer(&one), true);
+
+ try("mp_is_natural(-1)", mp_is_natural(&minus_one), false);
+ try("mp_is_natural(0)", mp_is_natural(&zero), false);
+ try("mp_is_natural(1)", mp_is_natural(&one), true);
+
+ try("mp_is_complex(-1)", mp_is_complex(&minus_one), false);
+ try("mp_is_complex(0)", mp_is_complex(&zero), false);
+ try("mp_is_complex(1)", mp_is_complex(&one), false);
+
+ try("mp_is_equal(-1, -1)", mp_is_equal(&minus_one, &minus_one), true);
+ try("mp_is_equal(-1, 0)", mp_is_equal(&minus_one, &zero), false);
+ try("mp_is_equal(-1, 1)", mp_is_equal(&minus_one, &one), false);
+ try("mp_is_equal(0, -1)", mp_is_equal(&zero, &minus_one), false);
+ try("mp_is_equal(0, 0)", mp_is_equal(&zero, &zero), true);
+ try("mp_is_equal(0, 1)", mp_is_equal(&zero, &one), false);
+ try("mp_is_equal(1, -1)", mp_is_equal(&one, &minus_one), false);
+ try("mp_is_equal(1, 0)", mp_is_equal(&one, &zero), false);
+ try("mp_is_equal(1, 1)", mp_is_equal(&one, &one), true);
+
+ try("mp_is_greater_than(0, -1)", mp_is_greater_than (&zero, &minus_one), true);
+ try("mp_is_greater_than(0, 0)", mp_is_greater_than (&zero, &zero), false);
+ try("mp_is_greater_than(0, 1)", mp_is_greater_than (&zero, &one), false);
+ try("mp_is_greater_than(1, -1)", mp_is_greater_than (&one, &minus_one), true);
+ try("mp_is_greater_than(1, 0)", mp_is_greater_than (&one, &zero), true);
+ try("mp_is_greater_than(1, 1)", mp_is_greater_than (&one, &one), false);
+ try("mp_is_greater_than(-1, -1)", mp_is_greater_than (&minus_one, &minus_one), false);
+ try("mp_is_greater_than(-1, 0)", mp_is_greater_than (&minus_one, &zero), false);
+ try("mp_is_greater_than(-1, 1)", mp_is_greater_than (&minus_one, &one), false);
+
+ try("mp_is_greater_equal(0, -1)", mp_is_greater_equal (&zero, &minus_one), true);
+ try("mp_is_greater_equal(0, 0)", mp_is_greater_equal (&zero, &zero), true);
+ try("mp_is_greater_equal(0, 1)", mp_is_greater_equal (&zero, &one), false);
+ try("mp_is_greater_equal(1, -1)", mp_is_greater_equal (&one, &minus_one), true);
+ try("mp_is_greater_equal(1, 0)", mp_is_greater_equal (&one, &zero), true);
+ try("mp_is_greater_equal(1, 1)", mp_is_greater_equal (&one, &one), true);
+ try("mp_is_greater_equal(-1, -1)", mp_is_greater_equal (&minus_one, &minus_one), true);
+ try("mp_is_greater_equal(-1, 0)", mp_is_greater_equal (&minus_one, &zero), false);
+ try("mp_is_greater_equal(-1, 1)", mp_is_greater_equal (&minus_one, &one), false);
+
+ try("mp_is_less_than(0, -1)", mp_is_less_than (&zero, &minus_one), false);
+ try("mp_is_less_than(0, 0)", mp_is_less_than (&zero, &zero), false);
+ try("mp_is_less_than(0, 1)", mp_is_less_than (&zero, &one), true);
+ try("mp_is_less_than(1, -1)", mp_is_less_than (&one, &minus_one), false);
+ try("mp_is_less_than(1, 0)", mp_is_less_than (&one, &zero), false);
+ try("mp_is_less_than(1, 1)", mp_is_less_than (&one, &one), false);
+ try("mp_is_less_than(-1, -1)", mp_is_less_than (&minus_one, &minus_one), false);
+ try("mp_is_less_than(-1, 0)", mp_is_less_than (&minus_one, &zero), true);
+ try("mp_is_less_than(-1, 1)", mp_is_less_than (&minus_one, &one), true);
+
+ try("mp_is_less_equal(0, -1)", mp_is_less_equal (&zero, &minus_one), false);
+ try("mp_is_less_equal(0, 0)", mp_is_less_equal (&zero, &zero), true);
+ try("mp_is_less_equal(0, 1)", mp_is_less_equal (&zero, &one), true);
+ try("mp_is_less_equal(1, -1)", mp_is_less_equal (&one, &minus_one), false);
+ try("mp_is_less_equal(1, 0)", mp_is_less_equal (&one, &zero), false);
+ try("mp_is_less_equal(1, 1)", mp_is_less_equal (&one, &one), true);
+ try("mp_is_less_equal(-1, -1)", mp_is_less_equal (&minus_one, &minus_one), true);
+ try("mp_is_less_equal(-1, 0)", mp_is_less_equal (&minus_one, &zero), true);
+ try("mp_is_less_equal(-1, 1)", mp_is_less_equal (&minus_one, &one), true);
+}
+
+
+void
+unittest()
+{
+ test_mp();
+ test_numbers();
+ test_conversions();
+ test_equations();
+ exit(fails > 0 ? 1 : 0);
+}
diff --git a/src/unittest.h b/src/unittest.h
new file mode 100644
index 0000000..7eee87d
--- /dev/null
+++ b/src/unittest.h
@@ -0,0 +1,24 @@
+/* Copyright (c) 2008-2009 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef UNITTEST_H
+#define UNITTEST_H
+
+void unittest(void);
+
+#endif /* UNITTEST_H */