diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 99 | ||||
-rw-r--r-- | src/currency.c | 244 | ||||
-rw-r--r-- | src/currency.h | 67 | ||||
-rw-r--r-- | src/financial.c | 294 | ||||
-rw-r--r-- | src/financial.h | 41 | ||||
-rw-r--r-- | src/gcalccmd.c | 98 | ||||
-rw-r--r-- | src/gcalctool.c | 260 | ||||
-rw-r--r-- | src/math-buttons.c | 1790 | ||||
-rw-r--r-- | src/math-buttons.h | 62 | ||||
-rw-r--r-- | src/math-display.c | 358 | ||||
-rw-r--r-- | src/math-display.h | 52 | ||||
-rw-r--r-- | src/math-equation.c | 1701 | ||||
-rw-r--r-- | src/math-equation.h | 133 | ||||
-rw-r--r-- | src/math-preferences.c | 401 | ||||
-rw-r--r-- | src/math-preferences.h | 47 | ||||
-rw-r--r-- | src/math-variables.c | 163 | ||||
-rw-r--r-- | src/math-variables.h | 52 | ||||
-rw-r--r-- | src/math-window.c | 527 | ||||
-rw-r--r-- | src/math-window.h | 61 | ||||
-rw-r--r-- | src/mp-binary.c | 208 | ||||
-rw-r--r-- | src/mp-convert.c | 933 | ||||
-rw-r--r-- | src/mp-equation-lexer.l | 120 | ||||
-rw-r--r-- | src/mp-equation-parser.y | 266 | ||||
-rw-r--r-- | src/mp-equation-private.h | 64 | ||||
-rw-r--r-- | src/mp-equation.c | 493 | ||||
-rw-r--r-- | src/mp-equation.h | 77 | ||||
-rw-r--r-- | src/mp-private.h | 47 | ||||
-rw-r--r-- | src/mp-trigonometric.c | 628 | ||||
-rw-r--r-- | src/mp.c | 2092 | ||||
-rw-r--r-- | src/mp.h | 357 | ||||
-rw-r--r-- | src/unittest.c | 761 | ||||
-rw-r--r-- | src/unittest.h | 24 |
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(¤cies[i].value) || + mp_is_zero(¤cies[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], ¤cies[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(¤cies[from_index].value) || + mp_is_zero(¤cies[to_index].value)) { + mp_set_from_integer(0, to_amount); + return FALSE; + } + + mp_divide(from_amount, ¤cies[from_index].value, to_amount); + mp_multiply(to_amount, ¤cies[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\"", °rees, &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 */ |