[gcalctool] Port to Vala



commit 12ba2c81b0a81bb3ac776d1034a3c41b3173196a
Author: Robert Ancell <robert ancell canonical com>
Date:   Sun Oct 14 16:31:40 2012 +1300

    Port to Vala
    
    https://bugzilla.gnome.org/show_bug.cgi?id=640685

 data/preferences.ui          |  276 ----
 src/config.vapi              |   14 +
 src/currency-manager.c       |  626 --------
 src/currency-manager.h       |   47 -
 src/currency.c               |   96 --
 src/currency.h               |   54 -
 src/currency.vala            |  499 +++++++
 src/equation-lexer.vala      |  681 +++++++++
 src/equation-parser.vala     | 1721 ++++++++++++++++++++++
 src/equation.vala            |  323 +++++
 src/financial.c              |  286 ----
 src/financial.h              |   33 -
 src/financial.vala           |  249 ++++
 src/gcalccmd.c               |   98 --
 src/gcalccmd.vala            |   58 +
 src/gcalctool.c              |  578 --------
 src/gcalctool.vala           |  413 ++++++
 src/lexer.c                  |  587 --------
 src/lexer.h                  |   40 -
 src/math-buttons.c           | 1361 ------------------
 src/math-buttons.h           |   56 -
 src/math-buttons.vala        | 1003 +++++++++++++
 src/math-converter.c         |  440 ------
 src/math-converter.h         |   53 -
 src/math-converter.vala      |  279 ++++
 src/math-display.c           |  470 ------
 src/math-display.h           |   46 -
 src/math-display.vala        |  355 +++++
 src/math-equation.c          | 2083 ---------------------------
 src/math-equation.h          |  134 --
 src/math-equation.vala       | 1251 ++++++++++++++++
 src/math-preferences.c       |  395 -----
 src/math-preferences.h       |   41 -
 src/math-preferences.vala    |  257 ++++
 src/math-variable-popup.c    |  308 ----
 src/math-variable-popup.h    |   40 -
 src/math-variable-popup.vala |  184 +++
 src/math-variables.c         |  179 ---
 src/math-variables.h         |   48 -
 src/math-variables.vala      |  114 ++
 src/math-window.c            |  247 ----
 src/math-window.h            |   53 -
 src/math-window.vala         |  117 ++
 src/mp-binary.c              |  221 ---
 src/mp-convert.c             |  717 ---------
 src/mp-enums.c.template      |   36 -
 src/mp-enums.h.template      |   25 -
 src/mp-equation.c            |  322 -----
 src/mp-equation.h            |   69 -
 src/mp-private.h             |   39 -
 src/mp-serializer.c          |  617 --------
 src/mp-serializer.h          |   79 -
 src/mp-trigonometric.c       |  628 --------
 src/mp.c                     | 2096 ---------------------------
 src/mp.h                     |  330 -----
 src/number.vala              | 3272 ++++++++++++++++++++++++++++++++++++++++++
 src/parser.c                 | 1230 ----------------
 src/parser.h                 |   81 --
 src/parserfunc.c             |  971 -------------
 src/parserfunc.h             |   80 -
 src/prelexer.c               |  214 ---
 src/prelexer.h               |   93 --
 src/serializer.vala          |  435 ++++++
 src/test-equation.vala       |  601 ++++++++
 src/test-mp-equation.c       |  630 --------
 src/test-mp.c                |  240 ---
 src/test-number.vala         | 1165 +++++++++++++++
 src/unit-category.c          |  136 --
 src/unit-category.h          |   56 -
 src/unit-manager.c           |  268 ----
 src/unit-manager.h           |   54 -
 src/unit.c                   |  212 ---
 src/unit.h                   |   60 -
 src/unit.vala                |  327 +++++
 74 files changed, 13318 insertions(+), 18179 deletions(-)
---
diff --git a/src/config.vapi b/src/config.vapi
new file mode 100644
index 0000000..af5d8cd
--- /dev/null
+++ b/src/config.vapi
@@ -0,0 +1,14 @@
+public const string VERSION;
+public const string GETTEXT_PACKAGE;
+public const string LOCALE_DIR;
+public const string UI_DIR;
+
+[CCode (cheader_filename = "langinfo.h", cprefix = "")]
+public enum NLItem
+{
+    RADIXCHAR,
+    THOUSEP
+}
+
+[CCode (cheader_filename = "langinfo.h")]
+public unowned string nl_langinfo (NLItem item);
diff --git a/src/currency.vala b/src/currency.vala
new file mode 100644
index 0000000..eb38232
--- /dev/null
+++ b/src/currency.vala
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) 2008-2012 Robert Ancell.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+static bool downloading_imf_rates = false;
+static bool downloading_ecb_rates = false;
+static bool loaded_rates = false;
+private static CurrencyManager? default_currency_manager = null;
+
+public class CurrencyManager : Object
+{
+    private List<Currency> currencies;
+    public signal void updated ();
+
+    public static CurrencyManager get_default ()
+    {
+        if (default_currency_manager != null)
+            return default_currency_manager;
+
+        default_currency_manager = new CurrencyManager ();
+
+        default_currency_manager.currencies.append (new Currency ("AED", _("UAE Dirham"), "Ø.Ø"));
+        default_currency_manager.currencies.append (new Currency ("AUD", _("Australian Dollar"), "$"));
+        default_currency_manager.currencies.append (new Currency ("BGN", _("Bulgarian Lev"), "ÐÐ"));
+        default_currency_manager.currencies.append (new Currency ("BHD", _("Bahraini Dinar"), ".Ø.Ø"));
+        default_currency_manager.currencies.append (new Currency ("BND", _("Brunei Dollar"), "$"));
+        default_currency_manager.currencies.append (new Currency ("BRL", _("Brazilian Real"), "R$"));
+        default_currency_manager.currencies.append (new Currency ("BWP", _("Botswana Pula"), "P"));
+        default_currency_manager.currencies.append (new Currency ("CAD", _("Canadian Dollar"), "$"));
+        default_currency_manager.currencies.append (new Currency ("CFA", _("CFA Franc"), "Fr"));
+        default_currency_manager.currencies.append (new Currency ("CHF", _("Swiss Franc"), "Fr"));
+        default_currency_manager.currencies.append (new Currency ("CLP", _("Chilean Peso"), "$"));
+        default_currency_manager.currencies.append (new Currency ("CNY", _("Chinese Yuan"), "å"));
+        default_currency_manager.currencies.append (new Currency ("COP", _("Colombian Peso"), "$"));
+        default_currency_manager.currencies.append (new Currency ("CZK", _("Czech Koruna"), "KÄ"));
+        default_currency_manager.currencies.append (new Currency ("DKK", _("Danish Krone"), "kr"));
+        default_currency_manager.currencies.append (new Currency ("DZD", _("Algerian Dinar"), "Ø.Ø"));
+        default_currency_manager.currencies.append (new Currency ("EEK", _("Estonian Kroon"), "KR"));
+        default_currency_manager.currencies.append (new Currency ("EUR", _("Euro"), "â"));
+        default_currency_manager.currencies.append (new Currency ("GBP", _("Pound Sterling"), "Â"));
+        default_currency_manager.currencies.append (new Currency ("HKD", _("Hong Kong Dollar"), "$"));
+        default_currency_manager.currencies.append (new Currency ("HRK", _("Croatian Kuna"), "kn"));
+        default_currency_manager.currencies.append (new Currency ("HUF", _("Hungarian Forint"), "Ft"));
+        default_currency_manager.currencies.append (new Currency ("IDR", _("Indonesian Rupiah"), "Rp"));
+        default_currency_manager.currencies.append (new Currency ("ILS", _("Israeli New Shekel"), "â"));
+        default_currency_manager.currencies.append (new Currency ("INR", _("Indian Rupee"), "â"));
+        default_currency_manager.currencies.append (new Currency ("IRR", _("Iranian Rial"), "ï"));
+        default_currency_manager.currencies.append (new Currency ("ISK", _("Icelandic Krona"), "kr"));
+        default_currency_manager.currencies.append (new Currency ("JPY", _("Japanese Yen"), "Â"));
+        default_currency_manager.currencies.append (new Currency ("KRW", _("South Korean Won"), "â"));
+        default_currency_manager.currencies.append (new Currency ("KWD", _("Kuwaiti Dinar"), "Ù.Ø"));
+        default_currency_manager.currencies.append (new Currency ("KZT", _("Kazakhstani Tenge"), "â"));
+        default_currency_manager.currencies.append (new Currency ("LKR", _("Sri Lankan Rupee"), "Rs"));
+        default_currency_manager.currencies.append (new Currency ("LTL", _("Lithuanian Litas"), "Lt"));
+        default_currency_manager.currencies.append (new Currency ("LVL", _("Latvian Lats"), "Ls"));
+        default_currency_manager.currencies.append (new Currency ("LYD", _("Libyan Dinar"), "Ø.Ù"));
+        default_currency_manager.currencies.append (new Currency ("MUR", _("Mauritian Rupee"), "Rs"));
+        default_currency_manager.currencies.append (new Currency ("MXN", _("Mexican Peso"), "$"));
+        default_currency_manager.currencies.append (new Currency ("MYR", _("Malaysian Ringgit"), "RM"));
+        default_currency_manager.currencies.append (new Currency ("NOK", _("Norwegian Krone"), "kr"));
+        default_currency_manager.currencies.append (new Currency ("NPR", _("Nepalese Rupee"), "Rs"));
+        default_currency_manager.currencies.append (new Currency ("NZD", _("New Zealand Dollar"), "$"));
+        default_currency_manager.currencies.append (new Currency ("OMR", _("Omani Rial"), "Ø.Ø."));
+        default_currency_manager.currencies.append (new Currency ("PEN", _("Peruvian Nuevo Sol"), "S/."));
+        default_currency_manager.currencies.append (new Currency ("PHP", _("Philippine Peso"), "â"));
+        default_currency_manager.currencies.append (new Currency ("PKR", _("Pakistani Rupee"), "Rs"));
+        default_currency_manager.currencies.append (new Currency ("PLN", _("Polish Zloty"), "zÅ"));
+        default_currency_manager.currencies.append (new Currency ("QAR", _("Qatari Riyal"), "Ù.Ø"));
+        default_currency_manager.currencies.append (new Currency ("RON", _("New Romanian Leu"), "L"));
+        default_currency_manager.currencies.append (new Currency ("RUB", _("Russian Rouble"), "ÑÑÐ."));
+        default_currency_manager.currencies.append (new Currency ("SAR", _("Saudi Riyal"), "Ø.Ø"));
+        default_currency_manager.currencies.append (new Currency ("SEK", _("Swedish Krona"), "kr"));
+        default_currency_manager.currencies.append (new Currency ("SGD", _("Singapore Dollar"), "$"));
+        default_currency_manager.currencies.append (new Currency ("THB", _("Thai Baht"), "à"));
+        default_currency_manager.currencies.append (new Currency ("TND", _("Tunisian Dinar"), "Ø.Ø"));
+        default_currency_manager.currencies.append (new Currency ("TRY", _("New Turkish Lira"), "TL"));
+        default_currency_manager.currencies.append (new Currency ("TTD", _("T&T Dollar (TTD)"), "$"));
+        default_currency_manager.currencies.append (new Currency ("USD", _("US Dollar"), "$"));
+        default_currency_manager.currencies.append (new Currency ("UYU", _("Uruguayan Peso"), "$"));
+        default_currency_manager.currencies.append (new Currency ("VEF", _("Venezuelan BolÃvar"), "Bs F"));
+        default_currency_manager.currencies.append (new Currency ("ZAR", _("South African Rand"), "R"));
+
+        return default_currency_manager;
+    }
+
+    public List<Currency> get_currencies ()
+    {
+        var r = new List<Currency> ();
+        foreach (var c in currencies)
+            r.append (c);
+        return r;
+    }
+
+    public Currency? get_currency (string name)
+    {
+        foreach (var c in currencies)
+        {
+            if (name == c.name)
+            {
+                var value = c.get_value ();
+                if (value == null || value.is_negative () || value.is_zero ())
+                    return null;
+                else
+                    return c;
+            }
+        }
+
+        return null;
+    }
+
+    private string get_imf_rate_filepath ()
+    {
+        return Path.build_filename (Environment.get_user_cache_dir (), "gcalctool", "rms_five.xls");
+    }
+
+    private string get_ecb_rate_filepath ()
+    {
+        return Path.build_filename (Environment.get_user_cache_dir (), "gcalctool", "eurofxref-daily.xml");
+    }
+
+    private Currency add_currency (string short_name)
+    {
+        foreach (var c in currencies)
+            if (c.name == short_name)
+                return c;
+
+        warning ("Currency %s is not in the currency table", short_name);
+        var c = new Currency (short_name, short_name, short_name);
+        currencies.append (c);
+
+        return c;
+    }
+
+    /* A file needs to be redownloaded if it doesn't exist, or is too old.
+     * When an error occur, it probably won't hurt to try to download again.
+     */
+    private bool file_needs_update (string filename, double max_age)
+    {
+        if (!FileUtils.test (filename, FileTest.IS_REGULAR))
+            return true;
+
+        var buf = Posix.Stat ();
+        if (Posix.stat (filename, out buf) == -1)
+            return true;
+
+        var modify_time = buf.st_mtime;
+        var now = time_t ();
+        if (now - modify_time > max_age)
+            return true;
+
+        return false;
+    }
+
+    private void load_imf_rates ()
+    {
+        var name_map = new HashTable <string, string> (str_hash, str_equal);
+        name_map.insert ("Euro", "EUR");
+        name_map.insert ("Japanese Yen", "JPY");
+        name_map.insert ("U.K. Pound Sterling", "GBP");
+        name_map.insert ("U.S. Dollar", "USD");
+        name_map.insert ("Algerian Dinar", "DZD");
+        name_map.insert ("Australian Dollar", "AUD");
+        name_map.insert ("Bahrain Dinar", "BHD");
+        name_map.insert ("Botswana Pula", "BWP");
+        name_map.insert ("Brazilian Real", "BRL");
+        name_map.insert ("Brunei Dollar", "BND");
+        name_map.insert ("Canadian Dollar", "CAD");
+        name_map.insert ("Chilean Peso", "CLP");
+        name_map.insert ("Chinese Yuan", "CNY");
+        name_map.insert ("Colombian Peso", "COP");
+        name_map.insert ("Czech Koruna", "CZK");
+        name_map.insert ("Danish Krone", "DKK");
+        name_map.insert ("Hungarian Forint", "HUF");
+        name_map.insert ("Icelandic Krona", "ISK");
+        name_map.insert ("Indian Rupee", "INR");
+        name_map.insert ("Indonesian Rupiah", "IDR");
+        name_map.insert ("Iranian Rial", "IRR");
+        name_map.insert ("Israeli New Sheqel", "ILS");
+        name_map.insert ("Kazakhstani Tenge", "KZT");
+        name_map.insert ("Korean Won", "KRW");
+        name_map.insert ("Kuwaiti Dinar", "KWD");
+        name_map.insert ("Libyan Dinar", "LYD");
+        name_map.insert ("Malaysian Ringgit", "MYR");
+        name_map.insert ("Mauritian Rupee", "MUR");
+        name_map.insert ("Mexican Peso", "MXN");
+        name_map.insert ("Nepalese Rupee", "NPR");
+        name_map.insert ("New Zealand Dollar", "NZD");
+        name_map.insert ("Norwegian Krone", "NOK");
+        name_map.insert ("Rial Omani", "OMR");
+        name_map.insert ("Pakistani Rupee", "PKR");
+        name_map.insert ("Nuevo Sol", "PEN");
+        name_map.insert ("Philippine Peso", "PHP");
+        name_map.insert ("Polish Zloty", "PLN");
+        name_map.insert ("Qatar Riyal", "QAR");
+        name_map.insert ("Russian Ruble", "RUB");
+        name_map.insert ("Saudi Arabian Riyal", "SAR");
+        name_map.insert ("Singapore Dollar", "SGD");
+        name_map.insert ("South African Rand", "ZAR");
+        name_map.insert ("Sri Lanka Rupee", "LKR");
+        name_map.insert ("Swedish Krona", "SEK");
+        name_map.insert ("Swiss Franc", "CHF");
+        name_map.insert ("Thai Baht", "THB");
+        name_map.insert ("Trinidad And Tobago Dollar", "TTD");
+        name_map.insert ("Tunisian Dinar", "TND");
+        name_map.insert ("U.A.E. Dirham", "AED");
+        name_map.insert ("Peso Uruguayo", "UYU");
+        name_map.insert ("Bolivar Fuerte", "VEF");
+
+        var filename = get_imf_rate_filepath ();
+        string data;
+        try
+        {
+            FileUtils.get_contents (filename, out data);
+        }
+        catch (Error e)
+        {
+            warning ("Failed to read exchange rates: %s", e.message);
+            return;
+        }
+
+        var lines = data.split ("\n", 0);
+
+        var in_data = false;
+        foreach (var line in lines)
+        {
+            line = line.chug ();
+
+            /* Start after first blank line, stop on next */
+            if (line == "")
+            {
+                if (!in_data)
+                {
+                   in_data = true;
+                   continue;
+                }
+                else
+                   break;
+            }
+            if (!in_data)
+                continue;
+
+            var tokens = line.split ("\t", 0);
+            if (tokens[0] != "Currency")
+            {
+                int value_index;
+                for (value_index = 1; value_index < tokens.length; value_index++)
+                {
+                    var value = tokens[value_index].chug ();
+                    if (value != "")
+                        break;
+                }
+
+                if (value_index < tokens.length)
+                {
+                    var symbol = name_map.lookup (tokens[0]);
+                    if (symbol != null)
+                    {
+                        var c = get_currency (symbol);
+                        if (c == null)
+                        {
+                            debug ("Using IMF rate of %s for %s", tokens[value_index], symbol);
+                            c = add_currency (symbol);
+                        }
+                        var value = mp_set_from_string (tokens[value_index]);
+                        value = value.reciprocal ();
+                        c.set_value (value);
+                    }
+                    else
+                        warning ("Unknown currency '%s'", tokens[0]);
+                }
+            }
+        }
+    }
+
+    private void set_ecb_rate (Xml.Node node, Currency eur_rate)
+    {
+        string? name = null, value = null;
+
+        for (var attribute = node.properties; attribute != null; attribute = attribute->next)
+        {
+            var n = (Xml.Node*) attribute;
+            if (attribute->name == "currency")
+                name = n->get_content ();
+            else if (attribute->name == "rate")
+                value = n->get_content ();
+        }
+
+        /* Use data if value and no rate currently defined */
+        if (name != null && value != null && get_currency (name) == null)
+        {
+            debug ("Using ECB rate of %s for %s", value, name);
+            var c = add_currency (name);
+            var r = mp_set_from_string (value);
+            var v = eur_rate.get_value ();
+            v = v.multiply (r);
+            c.set_value (v);
+        }
+    }
+
+    private void set_ecb_fixed_rate (string name, string value, Currency eur_rate)
+    {
+        debug ("Using ECB fixed rate of %s for %s", value, name);
+        var c = add_currency (name);
+        var r = mp_set_from_string (value);
+        var v = eur_rate.get_value ();
+        v = v.divide (r);
+        c.set_value (v);
+    }
+
+    private void load_ecb_rates ()
+    {
+        /* Scale rates to the EUR value */
+        var eur_rate = get_currency ("EUR");
+        if (eur_rate == null)
+        {
+            warning ("Cannot use ECB rates as don't have EUR rate");
+            return;
+        }
+
+        /* Set some fixed rates */
+        set_ecb_fixed_rate ("EEK", "0.06391", eur_rate);
+        set_ecb_fixed_rate ("CFA", "0.152449", eur_rate);
+
+        Xml.Parser.init ();
+        var filename = get_ecb_rate_filepath ();
+        var document = Xml.Parser.read_file (filename);
+        if (document == null)
+        {
+            warning ("Couldn't parse ECB rate file %s", filename);
+            return;
+        }
+
+        var xpath_ctx = new Xml.XPath.Context (document);
+        if (xpath_ctx == null)
+        {
+            warning ("Couldn't create XPath context");
+            return;
+        }
+
+        xpath_ctx.register_ns ("xref", "http://www.ecb.int/vocabulary/2002-08-01/eurofxref";);
+        var xpath_obj = xpath_ctx.eval_expression ("//xref:Cube[ currency][@rate]");
+        if (xpath_obj == null)
+        {
+            warning ("Couldn't create XPath object");
+            return;
+        }
+        var len = (xpath_obj->nodesetval != null) ? xpath_obj->nodesetval->length () : 0;
+        for (var i = 0; i < len; i++)
+        {
+            var node = xpath_obj->nodesetval->item (i);
+
+            if (node->type == Xml.ElementType.ELEMENT_NODE)
+                set_ecb_rate (node, eur_rate);
+
+            /* Avoid accessing removed elements */
+            if (node->type != Xml.ElementType.NAMESPACE_DECL)
+                node = null;
+        }
+
+        Xml.Parser.cleanup ();
+    }
+
+    private bool load_rates ()
+    {
+        /* Already loaded */
+        if (loaded_rates)
+            return true;
+
+        /* In process */
+        if (downloading_imf_rates || downloading_ecb_rates)
+            return false;
+
+        /* Use the IMF provided values and top up with currencies tracked by the ECB and not the IMF */
+        load_imf_rates ();
+        load_ecb_rates ();
+
+        /* Check if we couldn't find out a currency */
+        foreach (var c in currencies)
+            if (c.get_value ().is_zero ())
+                warning ("Currency %s is not provided by IMF or ECB", c.name);
+
+        debug ("Rates loaded");
+        loaded_rates = true;
+
+        updated ();
+
+        return true;
+    }
+
+    public Number? get_value (string currency)
+    {
+        /* Update rates if necessary */
+        var path = get_imf_rate_filepath ();
+        if (!downloading_imf_rates && file_needs_update (path, 60 * 60 * 24 * 7))
+        {
+            downloading_imf_rates = true;
+            debug ("Downloading rates from the IMF...");
+            download_file ("http://www.imf.org/external/np/fin/data/rms_five.aspx?tsvflag=Y";, path, download_imf_cb);
+        }
+        path = get_ecb_rate_filepath ();
+        if (!downloading_ecb_rates && file_needs_update (path, 60 * 60 * 24 * 7))
+        {
+            downloading_ecb_rates = true;
+            debug ("Downloading rates from the ECB...");
+            download_file ("http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml";, path, download_ecb_cb);
+        }
+
+        if (!load_rates ())
+            return null;
+
+        var c = get_currency (currency);
+        if (c != null)
+            return c.get_value ();
+        else
+            return null;
+    }
+
+    private void download_file (string uri, string filename, AsyncReadyCallback callback)
+    {
+        var directory = Path.get_dirname (filename);
+        DirUtils.create_with_parents (directory, 0755);
+
+        var source = File.new_for_uri (uri);
+        var dest = File.new_for_path (filename);
+
+        source.copy_async.begin (dest, FileCopyFlags.OVERWRITE, GLib.Priority.DEFAULT, null, null, callback);
+    }
+
+    private void download_imf_cb (Object? object, AsyncResult result)
+    {
+        var f = object as File;
+
+        try
+        {
+            f.copy_async.end (result);
+            debug ("IMF rates updated");
+        }
+        catch (Error e)
+        {
+            warning ("Couldn't download IMF currency rate file: %s", e.message);
+        }
+       
+        downloading_imf_rates = false;
+        load_rates ();
+    }
+
+    private void download_ecb_cb (Object? object, AsyncResult result)
+    {
+        var f = object as File;
+        try
+        {
+            f.copy_async.end (result);
+            debug ("ECB rates updated");
+        }
+        catch (Error e)
+        {
+            warning ("Couldn't download ECB currency rate file: %s", e.message);
+        }
+        downloading_ecb_rates = false;
+        load_rates ();
+    }
+}
+
+public class Currency : Object
+{
+    private Number value;
+
+    private string _name;
+    public string name { owned get { return _name; } }
+
+    private string _display_name;
+    public string display_name { owned get { return _display_name; } }
+
+    private string _symbol;
+    public string symbol { owned get { return _symbol; } }
+
+    public Currency (string name, string display_name, string symbol)
+    {
+        _name = name;
+        _display_name = display_name;
+        _symbol = symbol;
+    }
+
+    public void set_value (Number value)
+    {
+        this.value = value;
+    }
+
+    public Number get_value ()
+    {
+        return value;
+    }
+}
diff --git a/src/equation-lexer.vala b/src/equation-lexer.vala
new file mode 100644
index 0000000..a14c83a
--- /dev/null
+++ b/src/equation-lexer.vala
@@ -0,0 +1,681 @@
+/*
+ * Copyright (C) 2012 Arth Patel
+ * Copyright (C) 2012 Robert Ancell
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+/* Enum for tokens generated by pre-lexer and lexer. */
+public enum LexerTokenType
+{
+    UNKNOWN,              /* Unknown */
+
+    /* These are all Pre-Lexer tokens, returned by pre-lexer */
+    PL_DECIMAL,           /* Decimal separator */
+    PL_DIGIT,             /* Decimal digit */
+    PL_HEX,               /* A-F of Hex digits */
+    PL_SUPER_DIGIT,       /* Super digits */
+    PL_SUPER_MINUS,       /* Super minus */
+    PL_SUB_DIGIT,         /* Sub digits */
+    PL_FRACTION,          /* Fractions */
+    PL_DEGREE,            /* Degree */
+    PL_MINUTE,            /* Minutes */
+    PL_SECOND,            /* Seconds */
+    PL_LETTER,            /* Alphabets */
+    PL_EOS,               /* End of stream */
+    PL_SKIP,              /* Skip this symbol (whitespace or newline). */
+
+    /* These are all tokens, returned by Lexer. */
+    ADD,                /* Plus */
+    SUBTRACT,           /* Minus */
+    MULTIPLY,           /* Multiply */
+    DIVIDE,             /* Divide  */
+    MOD,                /* Modulus */
+    L_FLOOR,            /* Floor ( Left ) */
+    R_FLOOR,            /* Floor ( Right ) */
+    L_CEILING,          /* Ceiling ( Left ) */
+    R_CEILING,          /* Ceiling ( Right ) */
+    ROOT,               /* Square root */
+    ROOT_3,             /* Cube root */
+    ROOT_4,             /* Fourth root */
+    NOT,                /* Bitwise NOT */
+    AND,                /* Bitwise AND */
+    OR,                 /* Bitwise OR */
+    XOR,                /* Bitwise XOR */
+    IN,                 /* IN ( for converter ) */
+    NUMBER,             /* Number */
+    SUP_NUMBER,         /* Super Number */
+    NSUP_NUMBER,        /* Negative Super Number */
+    SUB_NUMBER,         /* Sub Number */
+    FUNCTION,           /* Function */
+    VARIABLE,           /* Variable name */
+    ASSIGN,             /* = */
+    L_R_BRACKET,        /* ( */
+    R_R_BRACKET,        /* ) */
+    L_S_BRACKET,        /* [ */
+    R_S_BRACKET,        /* ] */
+    L_C_BRACKET,        /* { */
+    R_C_BRACKET,        /* } */
+    ABS,                /* | */
+    POWER,              /* ^ */
+    FACTORIAL,          /* ! */
+    PERCENTAGE          /* % */
+}
+
+// FIXME: Merge into lexer
+public class PreLexer
+{
+    public string stream; /* String being scanned */
+    public int index;      /* Current character index */
+    public int mark_index; /* Location, last marked. Useful for getting substrings as part of highlighting */
+    private bool eos = false;
+
+    public PreLexer (string input)
+    {
+        stream = input;
+        index = 0;
+        mark_index = 0;
+    }
+
+    /* Roll back last scanned unichar. */
+    public void roll_back ()
+    {
+        if (eos)
+        {
+            eos = false;
+            return;
+        }
+        unichar c;
+        stream.get_prev_char (ref index, out c);
+    }
+
+    /* Set marker index. To be used for highlighting and error reporting. */
+    public void set_marker ()
+    {
+        mark_index = index;
+    }
+
+    /* Get marked substring. To be used for error reporting. */
+    public string get_marked_substring ()
+    {
+        return stream.substring (mark_index, index - mark_index);
+    }
+
+    /* Pre-Lexer tokanizer. To be called only by Lexer. */
+    public LexerTokenType get_next_token ()
+    {
+        unichar c;
+        if (!stream.get_next_char (ref index, out c))
+        {
+            // We have to flag if we ran out of chars, as roll_back from PL_EOS should have no effect
+            eos = true;
+            return LexerTokenType.PL_EOS;
+        }
+        eos = false;
+
+        if (c == ',' || c == '.')
+            return LexerTokenType.PL_DECIMAL;
+            
+        if (c.isdigit ())
+            return LexerTokenType.PL_DIGIT;
+
+        if ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
+            return LexerTokenType.PL_HEX;
+
+        if (c == 'â' || c == 'Â' || c == 'Â' || c == 'Â' || c == 'â' || c == 'â' || c == 'â' || c == 'â' || c == 'â' || c == 'â')
+            return LexerTokenType.PL_SUPER_DIGIT;
+
+        if (c == 'â')
+            return LexerTokenType.PL_SUPER_MINUS;
+
+        if (c == 'â' || c == 'â' || c == 'â' || c == 'â' || c == 'â' || c == 'â' || c == 'â' || c == 'â' || c == 'â' || c == 'â')
+            return LexerTokenType.PL_SUB_DIGIT;
+
+        if (c == 'Â' || c == 'â' || c == 'â' || c == 'Â' || c == 'Â' || c == 'â' || c == 'â' || c == 'â' || c == 'â' || c == 'â' || c == 'â' || c == 'â' || c == 'â' || c == 'â' || c == 'â')
+            return LexerTokenType.PL_FRACTION;
+
+        if (c == 'Â')
+            return LexerTokenType.PL_DEGREE;
+
+        if (c == '\'')
+            return LexerTokenType.PL_MINUTE;
+
+        if (c == '"')
+            return LexerTokenType.PL_SECOND;
+
+        if (c.isalpha ())
+            return LexerTokenType.PL_LETTER;
+
+        if (c == 'â')
+            return LexerTokenType.AND;
+
+        if (c == 'â')
+            return LexerTokenType.OR;
+
+        if (c == 'â' || c == 'â')
+            return LexerTokenType.XOR;
+
+        if (c == 'Â' || c == '~')
+            return LexerTokenType.NOT;
+
+        if (c == '+')
+            return LexerTokenType.ADD;
+
+        if (c == '-' || c == 'â' || c == 'â')
+            return LexerTokenType.SUBTRACT;
+
+        if (c == '*' || c == 'Ã')
+            return LexerTokenType.MULTIPLY;
+
+        if (c == '/' || c == 'â' || c == 'Ã')
+            return LexerTokenType.DIVIDE;
+
+        if (c == 'â')
+            return LexerTokenType.L_FLOOR;
+
+        if (c == 'â')
+            return LexerTokenType.R_FLOOR;
+
+        if (c == 'â')
+            return LexerTokenType.L_CEILING;
+
+        if (c == 'â')
+            return LexerTokenType.R_CEILING;
+
+        if (c == 'â')
+            return LexerTokenType.ROOT;
+
+        if (c == 'â')
+            return LexerTokenType.ROOT_3;
+
+        if (c == 'â')
+            return LexerTokenType.ROOT_4;
+
+        if (c == '=')
+            return LexerTokenType.ASSIGN;
+
+        if (c == '(')
+            return LexerTokenType.L_R_BRACKET;
+
+        if (c == ')')
+            return LexerTokenType.R_R_BRACKET;
+
+        if (c == '[')
+            return LexerTokenType.L_S_BRACKET;
+
+        if (c == ']')
+            return LexerTokenType.R_S_BRACKET;
+
+        if (c == '{')
+            return LexerTokenType.L_C_BRACKET;
+
+        if (c == '}')
+            return LexerTokenType.R_C_BRACKET;
+
+        if (c == '|')
+            return LexerTokenType.ABS;
+
+        if (c == '^')
+            return LexerTokenType.POWER;
+
+        if (c == '!')
+            return LexerTokenType.FACTORIAL;
+
+        if (c == '%')
+            return LexerTokenType.PERCENTAGE;
+
+        if (c == ' ' || c == '\r' || c == '\t' || c == '\n')
+            return LexerTokenType.PL_SKIP;
+
+        return LexerTokenType.UNKNOWN;
+    }
+}
+
+/* Structure to hold single token. */
+public class LexerToken
+{
+    public string text;                /* Copy of token string. */
+    public uint start_index;           /* Start index in original stream. */
+    public uint end_index;             /* End index in original stream. */
+    public LexerTokenType type;  /* Type of token. */
+}
+
+/* Structure to hold lexer state and all the tokens. */
+public class Lexer
+{
+    private Parser parser;           /* Pointer to the parser parser. */
+    private PreLexer prelexer;       /* Pre-lexer  Pre-lexer is part of lexer. */
+    public List<LexerToken> tokens;  /* Pointer to the dynamic array of LexerTokens. */
+    private uint next_token;         /* Index of next, to be sent, token. */
+    private int number_base;
+
+    public Lexer (string input, Parser parser, int number_base = 10)
+    {
+        prelexer = new PreLexer (input);
+        tokens = new List<LexerToken> ();
+        next_token = 0;
+        this.parser = parser;
+        this.number_base = number_base;
+    }
+
+    public void scan ()
+    {
+        while (true)
+        {
+            var token = insert_next_token ();
+            tokens.append (token);
+            if (token.type == LexerTokenType.PL_EOS)
+                break;
+        }
+    }
+
+    /* Get next token interface. Will be called by parser to get pointer to next token in token stream. */
+    public LexerToken get_next_token ()
+    {
+        var token = tokens.nth_data (next_token);
+        next_token++;
+        if (next_token >= tokens.length ())
+            next_token = tokens.length ();
+       
+        return token;
+    }
+
+    /* Roll back one lexer token. */
+    public void roll_back ()
+    {
+        if (next_token > 0)
+            next_token--;
+    }
+
+    private bool check_if_function ()
+    {
+        var name = prelexer.get_marked_substring ();
+
+        if (parser.function_is_defined (name))
+            return true;
+        else
+            return false;
+    }
+
+    private bool check_if_number ()
+    {
+        int count = 0;
+        var text = prelexer.get_marked_substring ();
+
+        var tmp = mp_set_from_string (text, number_base);
+        if (tmp != null)
+            return true;
+        else
+        {
+            /* Try to rollback several characters to see, if that yields any number. */
+            while (text != "")
+            {
+                tmp = mp_set_from_string (text, number_base);
+                if (tmp != null)
+                    return true;
+                count++;
+                prelexer.roll_back ();
+                text = prelexer.get_marked_substring ();
+            }
+
+            /* Undo all rollbacks. */
+            while (count-- > 0)
+                prelexer.get_next_token ();
+
+            return false;
+        }
+    }
+
+    /* Insert generated token to the lexer */
+    private LexerToken insert_token (LexerTokenType type)
+    {
+        var token = new LexerToken ();
+        token.text = prelexer.get_marked_substring ();
+        token.start_index = prelexer.mark_index;
+        token.end_index = prelexer.index;
+        token.type = type;
+
+        return token;
+    }
+
+    /* Generates next token from pre-lexer stream and call insert_token () to insert it at the end. */
+    private LexerToken insert_next_token ()
+    {
+        /* Mark start of next token */
+        prelexer.set_marker ();
+
+        /* Ignore whitespace */
+        var type = prelexer.get_next_token ();
+        while (type == LexerTokenType.PL_SKIP)
+        {
+            prelexer.set_marker ();
+            type = prelexer.get_next_token ();
+        }
+
+        if (type == LexerTokenType.AND || type == LexerTokenType.OR || type == LexerTokenType.XOR || type == LexerTokenType.NOT || type == LexerTokenType.ADD || type == LexerTokenType.SUBTRACT || type == LexerTokenType.MULTIPLY || type == LexerTokenType.DIVIDE || type == LexerTokenType.L_FLOOR || type == LexerTokenType.R_FLOOR || type == LexerTokenType.L_CEILING || type == LexerTokenType.R_CEILING || type == LexerTokenType.ROOT || type == LexerTokenType.ROOT_3 || type == LexerTokenType.ROOT_4 || type == LexerTokenType.ASSIGN || type == LexerTokenType.L_R_BRACKET || type == LexerTokenType.R_R_BRACKET || type == LexerTokenType.L_S_BRACKET || type == LexerTokenType.R_S_BRACKET || type == LexerTokenType.L_C_BRACKET || type == LexerTokenType.R_C_BRACKET || type == LexerTokenType.ABS || type == LexerTokenType.POWER || type == LexerTokenType.FACTORIAL || type == LexerTokenType.PERCENTAGE)
+            return insert_token (type);
+
+        /* [LexerTokenType.PL_SUPER_MINUS][LexerTokenType.PL_SUPER_DIGIT]+ */
+        if (type == LexerTokenType.PL_SUPER_MINUS)
+        {
+            if ((type = prelexer.get_next_token ()) != LexerTokenType.PL_SUPER_DIGIT)
+            {
+                /* ERROR: expected LexerTokenType.PL_SUP_DIGIT */
+                parser.set_error (ErrorCode.MP, prelexer.get_marked_substring (), prelexer.mark_index, prelexer.index);
+                return insert_token (LexerTokenType.UNKNOWN);
+            }
+
+            /* Get all LexerTokenType.PL_SUPER_DIGITs. */
+            while (prelexer.get_next_token () == LexerTokenType.PL_SUPER_DIGIT);
+            prelexer.roll_back ();
+
+            return insert_token (LexerTokenType.NSUP_NUMBER);
+        }
+
+        /* [LexerTokenType.PL_SUPER_DIGIT]+ */
+        if (type == LexerTokenType.PL_SUPER_DIGIT)
+        {
+            while (prelexer.get_next_token () == LexerTokenType.PL_SUPER_DIGIT);
+            prelexer.roll_back ();
+
+            return insert_token (LexerTokenType.SUP_NUMBER);
+        }
+
+        /* [LexerTokenType.PL_SUB_DIGIT]+ */
+        if (type == LexerTokenType.PL_SUB_DIGIT)
+        {
+            while (prelexer.get_next_token () == LexerTokenType.PL_SUB_DIGIT);
+            prelexer.roll_back ();
+
+            return insert_token (LexerTokenType.SUB_NUMBER);
+        }
+
+        /* [LexerTokenType.PL_FRACTION] */
+        if (type == LexerTokenType.PL_FRACTION)
+            return insert_token (LexerTokenType.NUMBER);
+
+        if (type == LexerTokenType.PL_DIGIT)
+            return insert_digit ();
+
+        if (type == LexerTokenType.PL_DECIMAL)
+            return insert_decimal ();
+
+        if (type == LexerTokenType.PL_HEX)
+            return insert_hex ();
+
+        if (type == LexerTokenType.PL_LETTER)
+            return insert_letter ();
+
+        if (type == LexerTokenType.PL_EOS)
+            return insert_token (LexerTokenType.PL_EOS);
+
+        /* ERROR: Unexpected token */
+        parser.set_error (ErrorCode.INVALID, prelexer.get_marked_substring (), prelexer.mark_index, prelexer.index);
+
+        return insert_token (LexerTokenType.UNKNOWN);
+    }
+
+    private LexerToken insert_digit ()
+    {
+        var type = prelexer.get_next_token ();
+        while (type == LexerTokenType.PL_DIGIT)
+            type = prelexer.get_next_token ();
+
+        if (type == LexerTokenType.PL_FRACTION)
+            return insert_token (LexerTokenType.NUMBER);
+        else if (type == LexerTokenType.PL_SUB_DIGIT)
+        {
+            while (prelexer.get_next_token () == LexerTokenType.PL_SUB_DIGIT);
+            prelexer.roll_back ();
+            return insert_token (LexerTokenType.NUMBER);
+        }
+        else if (type == LexerTokenType.PL_DEGREE)
+        {
+            type = prelexer.get_next_token ();
+            if (type == LexerTokenType.PL_DIGIT)
+            {
+                while ((type = prelexer.get_next_token ()) == LexerTokenType.PL_DIGIT);
+                if (type == LexerTokenType.PL_DECIMAL)
+                    return insert_angle_num_dm ();
+
+                else if (type == LexerTokenType.PL_MINUTE)
+                {
+                    type = prelexer.get_next_token ();
+                    if (type == LexerTokenType.PL_DIGIT)
+                    {
+                        while ((type = prelexer.get_next_token ()) == LexerTokenType.PL_DIGIT);
+                        if (type == LexerTokenType.PL_DECIMAL)
+                            return insert_angle_num_dms ();
+                        else if (type == LexerTokenType.PL_SECOND)
+                            return insert_token (LexerTokenType.NUMBER);
+                        else
+                        {
+                            /* ERROR: expected LexerTokenType.PL_SECOND */
+                            parser.set_error (ErrorCode.MP, prelexer.get_marked_substring (), prelexer.mark_index, prelexer.index);
+                            return insert_token (LexerTokenType.UNKNOWN);
+                        }
+                    }
+                    else if (type == LexerTokenType.PL_DECIMAL)
+                        return insert_angle_num_dms ();
+                    else
+                    {
+                        prelexer.roll_back ();
+                        return insert_token (LexerTokenType.NUMBER);
+                    }
+                }
+                else
+                {
+                    /* ERROR: expected LexerTokenType.PL_MINUTE | LexerTokenType.PL_DIGIT */
+                    parser.set_error (ErrorCode.MP, prelexer.get_marked_substring (), prelexer.mark_index, prelexer.index);
+                    return insert_token (LexerTokenType.UNKNOWN);
+                }
+            }
+            else if (type == LexerTokenType.PL_DECIMAL)
+                return insert_angle_num_dm ();
+            else
+                return insert_token (LexerTokenType.NUMBER);
+        }
+        else if (type == LexerTokenType.PL_DECIMAL)
+            return insert_decimal ();
+        else if (type == LexerTokenType.PL_HEX)
+            return insert_hex_dec ();
+        else
+        {
+            prelexer.roll_back ();
+            return insert_token (LexerTokenType.NUMBER);
+        }
+    }
+
+    private LexerToken insert_angle_num_dm ()
+    {
+        var type = prelexer.get_next_token ();
+        if (type != LexerTokenType.PL_DIGIT)
+        {
+            /* ERROR: expected LexerTokenType.PL_DIGIT */
+            parser.set_error (ErrorCode.MP, prelexer.get_marked_substring (), prelexer.mark_index, prelexer.index);
+            return insert_token (LexerTokenType.UNKNOWN);
+        }
+
+        while (type == LexerTokenType.PL_DIGIT);
+            type = prelexer.get_next_token ();
+
+        if (type == LexerTokenType.PL_MINUTE)
+            return insert_token (LexerTokenType.NUMBER);
+        else
+        {
+            /* ERROR: expected LexerTokenType.PL_MINUTE */
+            parser.set_error (ErrorCode.MP, prelexer.get_marked_substring (), prelexer.mark_index, prelexer.index);
+            return insert_token (LexerTokenType.UNKNOWN);
+        }
+    }
+
+    private LexerToken insert_angle_num_dms ()
+    {
+        var type = prelexer.get_next_token ();
+        if (type != LexerTokenType.PL_DIGIT)
+        {
+            /* ERROR: expected LexerTokenType.PL_DIGIT */
+            parser.set_error (ErrorCode.MP, prelexer.get_marked_substring (), prelexer.mark_index, prelexer.index);
+            return insert_token (LexerTokenType.UNKNOWN);
+        }
+        while ((type = prelexer.get_next_token ()) == LexerTokenType.PL_DIGIT);
+        if (type == LexerTokenType.PL_SECOND)
+            return insert_token (LexerTokenType.NUMBER);
+        else
+        {
+            /* ERROR: expected LexerTokenType.PL_SECOND */
+            parser.set_error (ErrorCode.MP, prelexer.get_marked_substring (), prelexer.mark_index, prelexer.index);
+            return insert_token (LexerTokenType.UNKNOWN);
+        }
+    }
+
+    private LexerToken insert_decimal ()
+    {
+        var type = prelexer.get_next_token ();
+        if (type == LexerTokenType.PL_DIGIT)
+        {
+            while ((type = prelexer.get_next_token ()) == LexerTokenType.PL_DIGIT);
+            if (type == LexerTokenType.PL_DEGREE)
+                return insert_token (LexerTokenType.NUMBER);
+            else if (type == LexerTokenType.PL_HEX)
+                return insert_decimal_hex ();
+            else if (type == LexerTokenType.PL_SUB_DIGIT)
+            {
+                while (prelexer.get_next_token () == LexerTokenType.PL_SUB_DIGIT);
+                prelexer.roll_back ();
+                return insert_token (LexerTokenType.NUMBER);
+            }
+            else
+            {
+                prelexer.roll_back ();
+                return insert_token (LexerTokenType.NUMBER);
+            }
+        }
+        else if (type == LexerTokenType.PL_HEX)
+            return insert_decimal_hex ();
+        else
+        {
+            /* ERROR: expected LexerTokenType.PL_DIGIT | LexerTokenType.PL_HEX */
+            parser.set_error (ErrorCode.MP, prelexer.get_marked_substring (), prelexer.mark_index, prelexer.index);
+            return insert_token (LexerTokenType.UNKNOWN);
+        }
+    }
+
+    private LexerToken insert_hex ()
+    {
+        var type = prelexer.get_next_token ();
+        while (type == LexerTokenType.PL_HEX)
+            type = prelexer.get_next_token ();
+
+        if (type == LexerTokenType.PL_DIGIT)
+            return insert_hex_dec ();
+        else if (type == LexerTokenType.PL_DECIMAL)
+            return insert_decimal_hex ();
+        else if (type == LexerTokenType.PL_SUB_DIGIT)
+        {
+            while (prelexer.get_next_token () == LexerTokenType.PL_SUB_DIGIT);
+            prelexer.roll_back ();
+
+            if (check_if_number ())
+                return insert_token (LexerTokenType.NUMBER);
+            else
+            {
+                if (check_if_function ())
+                    return insert_token (LexerTokenType.FUNCTION);
+                else
+                    return insert_token (LexerTokenType.VARIABLE);
+            }
+        }
+        else if (type == LexerTokenType.PL_LETTER)
+            return insert_letter ();
+        else
+        {
+            prelexer.roll_back ();
+            if (check_if_number ())
+                return insert_token (LexerTokenType.NUMBER);
+            else
+            {
+                if (check_if_function ())
+                    return insert_token (LexerTokenType.FUNCTION);
+                else
+                    return insert_token (LexerTokenType.VARIABLE);
+            }
+        }
+    }
+
+    private LexerToken insert_hex_dec ()
+    {
+        var type = prelexer.get_next_token ();
+        while (type == LexerTokenType.PL_DIGIT || type == LexerTokenType.PL_HEX)
+            type = prelexer.get_next_token ();
+
+        if (type == LexerTokenType.PL_DECIMAL)
+            return insert_decimal_hex ();
+        else if (type == LexerTokenType.PL_SUB_DIGIT)
+        {
+            while (prelexer.get_next_token () == LexerTokenType.PL_SUB_DIGIT);
+            prelexer.roll_back ();
+            return insert_token (LexerTokenType.NUMBER);
+        }
+        else
+        {
+            if (check_if_number ())
+                return insert_token (LexerTokenType.NUMBER);
+            /* ERROR: expected LexerTokenType.PL_DECIMAL | LexerTokenType.PL_DIGIT | LexerTokenType.PL_HEX */
+            parser.set_error (ErrorCode.MP, prelexer.get_marked_substring (), prelexer.mark_index, prelexer.index);
+            return insert_token (LexerTokenType.UNKNOWN);
+        }
+    }
+
+    private LexerToken insert_decimal_hex ()
+    {
+        /* Make up of digits and hexadecimal characters */
+        var type = prelexer.get_next_token ();
+        while (type == LexerTokenType.PL_DIGIT || type == LexerTokenType.PL_HEX)
+            type = prelexer.get_next_token ();
+
+        /* Allow a subdigit suffix */
+        while (type == LexerTokenType.PL_SUB_DIGIT)
+            type = prelexer.get_next_token ();
+
+        prelexer.roll_back ();
+
+        return insert_token (LexerTokenType.NUMBER);
+    }
+
+    private LexerToken insert_letter ()
+    {
+        /* Get string of letters */
+        var type = prelexer.get_next_token ();
+        while (type == LexerTokenType.PL_LETTER || type == LexerTokenType.PL_HEX)
+            type = prelexer.get_next_token ();
+
+        /* Allow a subdigit suffix */
+        while (type == LexerTokenType.PL_SUB_DIGIT)
+            type = prelexer.get_next_token ();
+
+        prelexer.roll_back ();
+
+        var name = prelexer.get_marked_substring ().down ();
+        if (name == "mod")
+            return insert_token (LexerTokenType.MOD);
+        if (name == "and")
+            return insert_token (LexerTokenType.AND);
+        if (name == "or")
+            return insert_token (LexerTokenType.OR);
+        if (name == "xor")
+            return insert_token (LexerTokenType.XOR);
+        if (name == "not")
+            return insert_token (LexerTokenType.NOT);
+        if (name == "in")
+            return insert_token (LexerTokenType.IN);
+        if (check_if_function ())
+            return insert_token (LexerTokenType.FUNCTION);
+        else
+            return insert_token (LexerTokenType.VARIABLE);
+    }
+}
diff --git a/src/equation-parser.vala b/src/equation-parser.vala
new file mode 100644
index 0000000..2410d7b
--- /dev/null
+++ b/src/equation-parser.vala
@@ -0,0 +1,1721 @@
+/*
+ * Copyright (C) 2012 Arth Patel
+ * Copyright (C) 2012 Robert Ancell
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+/* Operator Associativity. */
+public enum Associativity
+{
+    LEFT,
+    RIGHT
+}
+
+/* Operator Precedence. */
+private enum Precedence
+{
+    UNKNOWN         = 0,
+    ADD_SUBTRACT    = 1,
+    MULTIPLY        = 2,
+    MOD             = 3,
+    DIVIDE          = 4,
+    NOT             = 5,
+    ROOT            = 6,
+    FUNCTION        = 7,
+    BOOLEAN         = 8,
+    PERCENTAGE      = 9,
+    /* UNARY_MINUS and POWER must have same precedence. */
+    UNARY_MINUS     = 10,
+    POWER           = 10,
+    FACTORIAL       = 11,
+    NUMBER_VARIABLE = 12,
+    /* DEPTH should be always at the bottom. It stops node jumping off the current depth level. */
+    DEPTH
+}
+
+/* ParseNode structure for parse tree. */
+public class ParseNode
+{
+    public Parser parser;
+    public ParseNode? parent = null;
+    public ParseNode? left = null;
+    public ParseNode? right = null;
+    public LexerToken token;
+    public uint precedence;
+    public Associativity associativity;
+    public string? value;
+
+    public ParseNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity, string? value = null)
+    {
+        this.parser = parser;
+        this.token = token;
+        this.precedence = precedence;
+        this.associativity = associativity;
+        this.value = value;
+    }
+
+    public virtual Number? solve ()
+    {
+        return null;
+    }
+}
+
+public abstract class RNode : ParseNode
+{
+    public RNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number? solve ()
+    {
+        var r = right.solve ();
+        if (r == null)
+            return null;
+        return solve_r (r);
+    }
+
+    public abstract Number solve_r (Number r);
+}
+
+public abstract class LRNode : ParseNode
+{
+    public LRNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number? solve ()
+    {
+        var l = left.solve ();
+        var r = right.solve ();
+        if (l == null || r == null)
+            return null;
+        return solve_lr (l, r);
+    }
+
+    public abstract Number solve_lr (Number left, Number r);
+}
+
+public class ConstantNode : ParseNode
+{
+    public ConstantNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+    
+    public override Number? solve ()
+    {
+        return mp_set_from_string (token.text, parser.number_base);
+    }
+}
+
+public class AssignNode : RNode
+{
+    public AssignNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity) 
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_r (Number r)
+    {
+        parser.set_variable (left.token.text, r);
+        return r;
+    }
+}
+
+public class NameNode : ParseNode
+{
+    public NameNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity, string? text = null)
+    {
+        base (parser, token, precedence, associativity, text);
+    }
+}
+
+public class VariableNode : ParseNode
+{
+    public VariableNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number? solve ()
+    {   
+        /* If defined, then get the variable */
+        var ans = parser.get_variable (token.text);
+        if (ans != null)
+            return ans;
+
+        /* If has more than one character then assume a multiplication of variables */
+        // FIXME: Do this in the lexer
+        var value = new Number.integer (1);
+        var index = 0;
+        unichar c;
+        while (token.text.get_next_char (ref index, out c))
+        {
+            var t = parser.get_variable (c.to_string ());
+            if (t == null)
+            {
+                parser.set_error (ErrorCode.UNKNOWN_VARIABLE, token.text, token.start_index, token.end_index);
+                return null;
+            }
+            value = value.multiply (t);
+        }
+
+        return value;
+    }
+}
+
+public class VariableWithPowerNode : ParseNode
+{
+    public VariableWithPowerNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity, string text)
+    {
+        base (parser, token, precedence, associativity, text);
+    }
+
+    public override Number? solve ()
+    {
+        var pow = super_atoi (value);
+
+        value = null;
+
+        /* If defined, then get the variable */
+        var ans = parser.get_variable (token.text);
+        if (ans != null)
+            return ans.xpowy_integer (pow);
+
+        /* If has more than one character then assume a multiplication of variables */
+        // FIXME: Do in lexer
+        var value = new Number.integer (1);
+        var index = 0;
+        unichar c;
+        while (token.text.get_next_char (ref index, out c))
+        {
+            var t = parser.get_variable (c.to_string ());
+            if (t == null)
+            {
+                parser.set_error (ErrorCode.UNKNOWN_VARIABLE, token.text, token.start_index, token.end_index);
+                return null;
+            }
+
+            /* If last term do power */
+            var i = index;
+            unichar next;
+            if (!token.text.get_next_char (ref i, out next))
+                t = t.xpowy_integer (pow);
+            value = value.multiply (t);
+        }
+
+        return value;
+    }
+}
+
+public class FunctionNode : RNode
+{
+    public FunctionNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_r (Number r)
+    {
+        var ans = parser.get_function (token.text, r);
+        if (ans == null)
+            parser.set_error (ErrorCode.UNKNOWN_FUNCTION, token.text, token.start_index, token.end_index);
+
+        return ans;
+    }
+}
+
+public class FunctionWithPowerNode : ParseNode
+{
+    public FunctionWithPowerNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity, string text)
+    {
+        base (parser, token, precedence, associativity, text);
+    }
+
+    public override Number? solve ()
+    {
+        var val = right.solve ();
+        if (val == null)
+        {
+            value = null;
+            return null;
+        }
+        var tmp = parser.get_function (token.text, val);
+        if (tmp == null)
+        {
+            value = null;
+            parser.set_error (ErrorCode.UNKNOWN_FUNCTION, token.text, token.start_index, token.end_index);
+            return null;
+        }
+
+        var pow = super_atoi (value);
+        value = null;
+
+        return tmp.xpowy_integer (pow);
+    }
+}
+
+public class FunctionWithNegativePowerNode : ParseNode
+{
+    public FunctionWithNegativePowerNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity, string text)
+    {
+        base (parser, token, precedence, associativity, text);
+    }
+
+    public override Number? solve ()
+    {
+        var val = right.solve ();
+        if (val == null)
+        {
+            value = null;
+            return null;
+        }
+        var inv_name = token.text + "âÂ";
+        var tmp = parser.get_function (inv_name, val);
+        if (tmp == null)
+        {
+            value = null;
+            parser.set_error (ErrorCode.UNKNOWN_FUNCTION, token.text, token.start_index, token.end_index);
+            return null;
+        }
+
+        var pow = super_atoi (value);
+        value = null;
+
+        return tmp.xpowy_integer (-pow);
+    }
+}
+
+public class UnaryMinusNode : RNode
+{
+    public UnaryMinusNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_r (Number r)
+    {
+        return r.invert_sign ();
+    }
+}
+
+public class AbsoluteValueNode : RNode
+{
+    public AbsoluteValueNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_r (Number r)
+    {
+        return r.abs ();
+    }
+}
+
+public class FloorNode : RNode
+{
+    public FloorNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_r (Number r)
+    {
+        return r.floor ();
+    }
+}
+
+public class CeilingNode : RNode
+{
+    public CeilingNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_r (Number r)
+    {
+        return r.ceiling ();
+    }
+}
+
+public class FractionalComponentNode : RNode
+{
+    public FractionalComponentNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_r (Number r)
+    {
+        return r.fractional_part ();
+    }
+}
+
+public class RoundNode : RNode
+{
+    public RoundNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_r (Number r)
+    {
+        return r.round ();
+    }
+}
+
+public class PercentNode : RNode
+{
+    public PercentNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_r (Number r)
+    {
+        return r.divide_integer (100);
+    }
+}
+
+public class FactorialNode : RNode
+{
+    public FactorialNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_r (Number r)
+    {
+        return r.factorial ();
+    }
+}
+
+public class AddNode : LRNode
+{
+    public bool do_percentage = false;
+
+    public AddNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_lr (Number l, Number r)
+    {
+        if (do_percentage)
+        {
+            var per = r.add (new Number.integer (100));
+            per = per.divide_integer (100);
+            return l.multiply (per);
+        }
+        else
+            return l.add (r);
+    }
+}
+
+public class SubtractNode : LRNode
+{
+    public bool do_percentage = false;
+
+    public SubtractNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_lr (Number l, Number r)
+    {
+        if (do_percentage)
+        {
+            var per = r.add (new Number.integer (-100));
+            per = per.divide_integer (-100);
+            return l.multiply (per);
+        }
+        else
+            return l.subtract (r);
+    }
+}
+
+public class MultiplyNode : LRNode
+{
+    public MultiplyNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_lr (Number l, Number r)
+    {
+        return l.multiply (r);
+    }
+}
+
+public class DivideNode : LRNode
+{
+    public DivideNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_lr (Number l, Number r)
+    {
+        return l.divide (r);
+    }
+}
+
+public class ModulusDivideNode : LRNode
+{
+    public ModulusDivideNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_lr (Number l, Number r)
+    {
+        return l.modulus_divide (r);
+    }
+}
+
+public class RootNode : RNode
+{
+    private int n;
+
+    public RootNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity, int n)
+    {
+        base (parser, token, precedence, associativity);
+        this.n = n;
+    }
+
+    public override Number solve_r (Number r)
+    {
+        return r.root (n);
+    }
+}
+
+public class XPowYNode : LRNode
+{
+    public XPowYNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_lr (Number l, Number r)
+    {
+        return l.xpowy (r);
+    }
+}
+
+public class XPowYIntegerNode : ParseNode
+{
+    public XPowYIntegerNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number? solve ()
+    {
+        var val = left.solve ();
+        var pow = super_atoi (right.token.text);
+        if (val == null)
+            return null;
+
+        return val.xpowy_integer (pow);
+    }
+}
+
+public class NotNode : RNode
+{
+    public NotNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_r (Number r)
+    {
+        if (!mp_is_overflow (r, parser.wordlen))
+        {
+            parser.set_error (ErrorCode.OVERFLOW);
+            return new Number.integer (0);
+        }
+
+        return r.not (parser.wordlen);
+    }
+}
+
+public class AndNode : LRNode
+{
+    public AndNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_lr (Number l, Number r)
+    {
+        return l.and (r);
+    }
+}
+
+public class OrNode : LRNode
+{
+    public OrNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_lr (Number l, Number r)
+    {
+        return l.or (r);
+    }
+}
+
+public class XorNode : LRNode
+{
+    public XorNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_lr (Number l, Number r)
+    {
+        return l.xor (r);
+    }
+}
+
+public class ConvertNode : LRNode
+{
+    public ConvertNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number solve_lr (Number l, Number r)
+    {
+        string from;
+        if (left.value != null)
+        {
+            from = left.value;
+            left.value = null;
+        }
+        else
+            from = left.token.text;
+
+        string to;
+        if (right.value != null)
+        {
+            to = right.value;
+            right.value = null;
+        }
+        else
+            to = right.token.text;
+
+        var tmp = new Number.integer (1);
+
+        var ans = parser.convert (tmp, from, to);
+        if (ans == null)
+            parser.set_error (ErrorCode.UNKNOWN_CONVERSION);
+
+        return ans;
+    }
+}
+
+public class ConvertNumberNode : ParseNode
+{
+    public ConvertNumberNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number? solve ()
+    {
+        string from;
+        if (left.value != null)
+        {
+            from = left.value;
+            left.value = null;
+        }
+        else
+            from = left.token.text;
+
+        string to;
+        if (right.value != null)
+        {
+            to = right.value;
+            right.value = null;
+        }
+        else
+            to = right.token.text;
+
+        var tmp = mp_set_from_string (left.left.token.text, parser.number_base);
+        if (tmp == null)
+            return null;
+
+        var ans = parser.convert (tmp, from, to);
+        if (ans == null)
+            parser.set_error (ErrorCode.UNKNOWN_CONVERSION);
+
+        return ans;
+    }
+}
+
+public class Parser
+{
+    private string input;
+    private ParseNode root;
+    private ParseNode right_most;
+    private Lexer lexer;
+    public int number_base;
+    public int wordlen;
+    private uint depth_level;
+    private ErrorCode error;
+    private string error_token;
+    private int error_token_start;
+    private int error_token_end;
+
+    public Parser (string input, int number_base, int wordlen)
+    {
+        this.input = input;
+        lexer = new Lexer (input, this, number_base);
+        root = null;
+        depth_level = 0;
+        right_most = null;
+        this.number_base = number_base;
+        this.wordlen = wordlen;
+        error = ErrorCode.NONE;
+        error_token = null;
+        error_token_start = 0;
+        error_token_end = 0;
+    }
+
+    public void set_error (ErrorCode errorno, string? token = null, uint token_start = 0, uint token_end = 0)
+    {
+        error = errorno;
+        error_token = token;
+        error_token_start = input.char_count (token_start);
+        error_token_end = input.char_count (token_end);
+    }
+
+    public virtual bool variable_is_defined (string name)
+    {
+        return false;
+    }
+
+    public virtual Number? get_variable (string name)
+    {
+        return null;
+    }
+
+    public virtual void set_variable (string name, Number x)
+    {
+    }
+
+    public virtual bool function_is_defined (string name)
+    {
+        return false;
+    }
+
+    public virtual Number? get_function (string name, Number x)
+    {
+        return null;
+    }
+
+    public virtual Number? convert (Number x, string x_units, string z_units)
+    {
+        return null;
+    }
+
+    /* Start parsing input string. And call evaluate on success. */
+    public Number? parse (out ErrorCode error_code, out string? error_token, out uint error_start, out uint error_end)
+    {
+        /* Scan string and split into tokens */
+        lexer.scan ();
+
+        /* Parse tokens */
+        var ret = statement ();
+
+        var token = lexer.get_next_token ();
+        if (token.type == LexerTokenType.ASSIGN)
+        {
+            token = lexer.get_next_token ();
+            if (token.type != LexerTokenType.PL_EOS)
+            {
+                /* Full string is not parsed. */
+                if (error == 0)
+                    set_error (ErrorCode.INVALID, token.text, token.start_index, token.end_index);
+
+                error_code = error;
+                error_token = this.error_token;
+                error_start = error_token_start;
+                error_end = error_token_end;
+                return null;
+            }
+        }
+        if (token.type != LexerTokenType.PL_EOS)
+        {
+            /* Full string is not parsed. */
+            if (error == 0)
+                set_error (ErrorCode.INVALID, token.text, token.start_index, token.end_index);
+
+            error_code = error;
+            error_token = this.error_token;
+            error_start = error_token_start;
+            error_end = error_token_end;
+            return null;
+        }
+
+        /* Input can't be parsed with grammar. */
+        if (!ret)
+        {
+            error_code = error;
+            error_token = this.error_token;
+            error_start = error_token_start;
+            error_end = error_token_end;
+            return null;
+        }
+        var ans = root.solve ();
+        if (ans == null)
+        {
+            error_code = ErrorCode.INVALID;
+            error_token = null;
+            error_start = error_token_start;
+            error_end = error_token_end;
+            return null;
+        }
+
+        error_code = ErrorCode.NONE;
+        error_token = null;
+        error_start = 0;
+        error_end = 0;
+        return ans;
+    }
+
+    /* Converts LexerTokenType to Precedence value. */
+    private Precedence get_precedence (LexerTokenType type)
+    {
+        /* WARNING: This function doesn't work for Unary Plus and Unary Minus. Use their precedence directly while inserting them in tree. */
+        if (type == LexerTokenType.ADD || type == LexerTokenType.SUBTRACT)
+            return Precedence.ADD_SUBTRACT;
+        if (type == LexerTokenType.MULTIPLY)
+            return Precedence.MULTIPLY;
+        if (type == LexerTokenType.MOD)
+            return Precedence.MOD;
+        if (type == LexerTokenType.DIVIDE)
+            return Precedence.DIVIDE;
+        if (type == LexerTokenType.NOT)
+            return Precedence.NOT;
+        if (type == LexerTokenType.ROOT || type == LexerTokenType.ROOT_3 || type == LexerTokenType.ROOT_4)
+            return Precedence.ROOT;
+        if (type == LexerTokenType.FUNCTION)
+            return Precedence.FUNCTION;
+        if (type == LexerTokenType.AND || type == LexerTokenType.OR || type == LexerTokenType.XOR)
+            return Precedence.BOOLEAN;
+        if (type == LexerTokenType.PERCENTAGE)
+            return Precedence.PERCENTAGE;
+        if (type == LexerTokenType.POWER)
+            return Precedence.POWER;
+        if (type == LexerTokenType.FACTORIAL)
+            return Precedence.FACTORIAL;
+        if (type == LexerTokenType.NUMBER || type == LexerTokenType.VARIABLE)
+            return Precedence.NUMBER_VARIABLE;
+        return Precedence.UNKNOWN;
+    }
+
+    /* Return associativity of specific token type from precedence. */
+    private Associativity get_associativity_p (Precedence type)
+    {
+        if (type == Precedence.BOOLEAN || type == Precedence.DIVIDE || type == Precedence.MOD || type == Precedence.MULTIPLY || type == Precedence.ADD_SUBTRACT)
+            return Associativity.LEFT;
+        if (type == Precedence.POWER)
+            return Associativity.RIGHT;
+        /* For all remaining / non-associative operators, return Left Associativity. */
+        return Associativity.LEFT;
+    }
+
+    /* Return associativity of specific token by converting it to precedence first. */
+    private Associativity get_associativity (LexerToken token)
+    {
+        return get_associativity_p (get_precedence (token.type));
+    }
+
+    /* Generate precedence for a node from precedence value. Includes depth_level. */
+    private uint make_precedence_p (Precedence p)
+    {
+        return p + (depth_level * Precedence.DEPTH);
+    }
+
+    /* Generate precedence for a node from lexer token type. Includes depth_level. */
+    private uint make_precedence_t (LexerTokenType type)
+    {
+        return get_precedence (type) + (depth_level * Precedence.DEPTH);
+    }
+
+    /* Compares two nodes to decide, which will be parent and which will be child. */
+    private bool cmp_nodes (ParseNode? left, ParseNode? right)
+    {
+        /* Return values:
+         * true = right goes up (near root) in parse tree.
+         * false = left  goes up (near root) in parse tree.
+         */
+        if (left == null)
+            return false;
+        if (left.precedence > right.precedence)
+            return true;
+        else if (left.precedence < right.precedence)
+            return false;
+        else
+            return right.associativity != Associativity.RIGHT;
+    }
+
+    /* Unified interface (unary and binary nodes) to insert node into parse tree. */
+    private void insert_into_tree_all (ParseNode node, bool unary_function)
+    {
+        if (root == null)
+        {
+            root = node;
+            right_most = root;
+            return;
+        }
+        ParseNode tmp = right_most;
+        while (cmp_nodes (tmp, node))
+            tmp = tmp.parent;
+
+        if (unary_function)
+        {
+            /* If tmp is null, that means, we have to insert new node at root. */
+            if (tmp == null)
+            {
+                node.right = root;
+                node.right.parent = node;
+
+                root = node;
+            }
+            else
+            {
+                node.right = tmp.right;
+                if (node.right != null)
+                    node.right.parent = node;
+
+                tmp.right = node;
+                if (tmp.right != null)
+                    tmp.right.parent = tmp;
+
+            }
+            right_most = node;
+            while (right_most.right != null)
+                right_most = right_most.right;
+        }
+        else
+        {
+            /* If tmp is null, that means, we have to insert new node at root. */
+            if (tmp == null)
+            {
+                node.left = root;
+                node.left.parent = node;
+
+                root = node;
+            }
+            else
+            {
+                node.left = tmp.right;
+                if (node.left != null)
+                    node.left.parent = node;
+
+                tmp.right = node;
+                if (tmp.right != null)
+                    tmp.right.parent = tmp;
+
+            }
+            right_most = node;
+        }
+    }
+
+    /* Insert binary node into the parse tree. */
+    private void insert_into_tree (ParseNode node)
+    {
+        insert_into_tree_all (node, false);
+    }
+
+    /* Insert unary node into the parse tree. */
+    private void insert_into_tree_unary (ParseNode node)
+    {
+        insert_into_tree_all (node, true);
+    }
+
+    /* Recursive call to free every node of parse-tree. */
+    private void destroy_all_nodes (ParseNode node)
+    {
+        if (node == null)
+            return;
+
+        destroy_all_nodes (node.left);
+        destroy_all_nodes (node.right);
+        /* Don't call free for tokens, as they are allocated and freed in lexer. */
+        /* WARNING: If node.value is freed elsewhere, please assign it null before calling destroy_all_nodes (). */
+    }
+
+    /* LL (*) parser. Lookahead count depends on tokens. Handle with care. :P */
+
+    /* Check if string "name" is a valid variable for given Parser. It is the same code, used to get the value of variable in parserfunc.c. */
+    private bool check_variable (string name)
+    {
+        /* If defined, then get the variable */
+        if (variable_is_defined (name))
+            return true;
+
+        /* If has more than one character then assume a multiplication of variables */
+        var index = 0;
+        unichar c;
+        while (name.get_next_char (ref index, out c))
+        {
+            if (!variable_is_defined (c.to_string ()))
+                return false;
+        }
+
+        return true;
+    }
+
+    private bool statement ()
+    {
+        var token = lexer.get_next_token ();
+        if (token.type == LexerTokenType.VARIABLE)
+        {
+            var token_old = token;
+            token = lexer.get_next_token ();
+            if (token.type == LexerTokenType.ASSIGN)
+            {
+                insert_into_tree (new NameNode (this, token_old, make_precedence_p (Precedence.NUMBER_VARIABLE), get_associativity (token_old)));
+                insert_into_tree (new AssignNode (this, token, 0, get_associativity (token)));
+
+                if (!expression ())
+                    return false;
+
+                return true;
+            }
+            else if (token.type == LexerTokenType.IN)
+            {
+                lexer.roll_back ();
+                lexer.roll_back ();
+
+                if (!unit ())
+                    return false;
+                lexer.get_next_token ();
+
+                insert_into_tree (new ConvertNode (this, token, 0, get_associativity (token)));
+
+                if (!unit ())
+                    return false;
+
+                return true;
+            }
+            else if (token.type == LexerTokenType.SUP_NUMBER)
+            {
+                token = lexer.get_next_token ();
+                if (token.type == LexerTokenType.IN)
+                {
+                    lexer.roll_back ();
+                    lexer.roll_back ();
+                    lexer.roll_back ();
+                    if (!unit ())
+                        return false;
+                    lexer.get_next_token ();
+
+                    insert_into_tree (new ConvertNode (this, token, 0, get_associativity (token)));
+
+                    if (!unit ())
+                        return false;
+
+                    return true;
+                }
+                else
+                {
+                    lexer.roll_back ();
+                    lexer.roll_back ();
+                    lexer.roll_back ();
+
+                    if (!expression ())
+                        return false;
+
+                    return true;
+                }
+            }
+            else
+            {
+                lexer.roll_back ();
+                lexer.roll_back ();
+
+                if (!expression ())
+                    return false;
+
+                return true;
+            }
+        }
+        else if (token.type == LexerTokenType.NUMBER)
+        {
+            var token_old = token;
+            token = lexer.get_next_token ();
+            if (token.type == LexerTokenType.VARIABLE)
+            {
+                token = lexer.get_next_token ();
+                if (token.type == LexerTokenType.IN)
+                {
+                    lexer.roll_back ();
+                    lexer.roll_back ();
+
+                    insert_into_tree (new ConstantNode (this, token_old, make_precedence_t (token_old.type), get_associativity (token)));
+
+                    if (!unit ())
+                        return false;
+
+                    token = lexer.get_next_token ();
+                    insert_into_tree (new ConvertNumberNode (this, token, 0, get_associativity (token)));
+
+                    if (!unit ())
+                        return false;
+
+                    return true;
+                }
+                else if (token.type == LexerTokenType.SUP_NUMBER)
+                {
+                    token = lexer.get_next_token ();
+                    if (token.type == LexerTokenType.IN)
+                    {
+                        lexer.roll_back ();
+                        lexer.roll_back ();
+                        lexer.roll_back ();
+
+                        insert_into_tree (new ConstantNode (this, token_old, make_precedence_t (token_old.type), get_associativity (token)));
+
+                        if (!unit ())
+                            return false;
+                        token = lexer.get_next_token ();
+
+                        insert_into_tree (new ConvertNumberNode (this, token, 0, get_associativity (token)));
+
+                        if (!unit ())
+                            return false;
+                        return true;
+                    }
+                    else
+                    {
+                        lexer.roll_back ();
+                        lexer.roll_back ();
+                        lexer.roll_back ();
+                        lexer.roll_back ();
+                        if (!expression ())
+                            return false;
+                        return true;
+                    }
+                }
+                else
+                {
+                    lexer.roll_back ();
+                    lexer.roll_back ();
+                    lexer.roll_back ();
+                    if (!expression ())
+                        return false;
+                    return true;
+                }
+            }
+            else
+            {
+                lexer.roll_back ();
+                lexer.roll_back ();
+                if (!expression ())
+                    return false;
+                return true;
+            }
+        }
+        else
+        {
+            lexer.roll_back ();
+            if (!expression ())
+                return false;
+            return true;
+        }
+    }
+
+    private bool unit ()
+    {
+        var token = lexer.get_next_token ();
+        if (token.type == LexerTokenType.VARIABLE)
+        {
+            var token_old = token;
+            token = lexer.get_next_token ();
+            if (token.type == LexerTokenType.SUP_NUMBER)
+            {
+                insert_into_tree (new NameNode (this, token_old, make_precedence_t (token_old.type), get_associativity (token_old), token_old.text + token.text));
+                return true;
+            }
+            else
+            {
+                lexer.roll_back ();
+                insert_into_tree (new NameNode (this, token_old, make_precedence_t (token_old.type), get_associativity (token_old)));
+                return true;
+            }
+        }
+        else
+        {
+            lexer.roll_back ();
+            return false;
+        }
+    }
+
+    private bool expression ()
+    {
+        if (!expression_1 ())
+            return false;
+        if (!expression_2 ())
+            return false;
+
+        return true;
+    }
+
+    private bool expression_1 ()
+    {
+        var token = lexer.get_next_token ();
+
+        if (token.type == LexerTokenType.PL_EOS || token.type == LexerTokenType.ASSIGN)
+        {
+            lexer.roll_back ();
+            return false;
+        }
+
+        if (token.type == LexerTokenType.L_R_BRACKET)
+        {
+            depth_level++;
+
+            if (!expression ())
+                return false;
+
+            token = lexer.get_next_token ();
+            if (token.type == LexerTokenType.R_R_BRACKET)
+            {
+                depth_level--;
+                return true;
+            }
+            //Expected ")" here...
+            else
+                return false;
+        }
+        else if (token.type == LexerTokenType.L_S_BRACKET)
+        {
+            depth_level++;
+
+            /* Give round, preference of Precedence.UNKNOWN aka 0, to keep it on the top of expression. */
+
+            insert_into_tree_unary (new RoundNode (this, token, make_precedence_p (Precedence.UNKNOWN), get_associativity (token)));
+
+            if (!expression ())
+                return false;
+
+            token = lexer.get_next_token ();
+            if (token.type == LexerTokenType.R_S_BRACKET)
+            {
+                depth_level--;
+                return true;
+            }
+            else
+            //Expected "]" here...
+                return false;
+        }
+        else if (token.type == LexerTokenType.L_C_BRACKET)
+        {
+            depth_level++;
+
+            /* Give fraction, preference of Precedence.UNKNOWN aka 0, to keep it on the top of expression. */
+
+            insert_into_tree_unary (new FractionalComponentNode (this, token, make_precedence_p (Precedence.UNKNOWN), get_associativity (token)));
+
+            if (!expression ())
+                return false;
+
+            token = lexer.get_next_token ();
+            if (token.type == LexerTokenType.R_C_BRACKET)
+            {
+                depth_level--;
+                return true;
+            }
+            //Expected "}" here...
+            else
+                return false;
+        }
+        else if (token.type == LexerTokenType.ABS)
+        {
+            depth_level++;
+
+            /* Give abs, preference of Precedence.UNKNOWN aka 0, to keep it on the top of expression. */
+
+            insert_into_tree_unary (new AbsoluteValueNode (this, token, make_precedence_p (Precedence.UNKNOWN), get_associativity (token)));
+
+            if (!expression ())
+                return false;
+
+            token = lexer.get_next_token ();
+            if (token.type == LexerTokenType.ABS)
+            {
+                depth_level--;
+                return true;
+            }
+            //Expected "|" here...
+            else
+                return false;
+        }
+        else if (token.type == LexerTokenType.NOT)
+        {
+            insert_into_tree_unary (new NotNode (this, token, make_precedence_p (Precedence.NOT), get_associativity (token)));
+
+            if (!expression ())
+                return false;
+
+            return true;
+        }
+        else if (token.type == LexerTokenType.NUMBER)
+        {
+            insert_into_tree (new ConstantNode (this, token, make_precedence_t (token.type), get_associativity (token)));
+
+            token = lexer.get_next_token ();
+            lexer.roll_back ();
+
+            if (token.type == LexerTokenType.FUNCTION || token.type == LexerTokenType.VARIABLE || token.type == LexerTokenType.SUB_NUMBER || token.type == LexerTokenType.ROOT || token.type == LexerTokenType.ROOT_3 || token.type == LexerTokenType.ROOT_4)
+            {
+                insert_into_tree (new MultiplyNode (this, null, make_precedence_p (Precedence.MULTIPLY), get_associativity_p (Precedence.MULTIPLY)));
+
+                if (!variable ())
+                    return false;
+                else
+                    return true;
+            }
+            else
+                return true;
+        }
+        else if (token.type == LexerTokenType.L_FLOOR)
+        {
+            depth_level++;
+            /* Give floor, preference of Precedence.UNKNOWN aka 0, to keep it on the top of expression. */
+
+            insert_into_tree_unary (new FloorNode (this, null, make_precedence_p (Precedence.UNKNOWN), get_associativity_p (Precedence.UNKNOWN)));
+
+            if (!expression ())
+                return false;
+
+            token = lexer.get_next_token ();
+            if (token.type == LexerTokenType.R_FLOOR)
+            {
+                depth_level--;
+                return true;
+            }
+            //Expected â here...
+            else
+                return false;
+        }
+        else if (token.type == LexerTokenType.L_CEILING)
+        {
+            depth_level++;
+            /* Give ceiling, preference of Precedence.UNKNOWN aka 0, to keep it on the top of expression. */
+
+            insert_into_tree_unary (new CeilingNode (this, null, make_precedence_p (Precedence.UNKNOWN), get_associativity_p (Precedence.UNKNOWN)));
+
+            if (!expression ())
+                return false;
+
+            token = lexer.get_next_token ();
+            if (token.type == LexerTokenType.R_CEILING)
+            {
+                depth_level--;
+                return true;
+            }
+            //Expected â here...
+            else
+                return false;
+        }
+        else if (token.type == LexerTokenType.SUBTRACT)
+        {
+            insert_into_tree_unary (new UnaryMinusNode (this, token, make_precedence_p (Precedence.UNARY_MINUS), get_associativity_p (Precedence.UNARY_MINUS)));
+
+            if (!expression_1 ())
+                return false;
+
+            return true;
+        }
+        else if (token.type == LexerTokenType.ADD)
+        {
+            token = lexer.get_next_token ();
+            if (token.type == LexerTokenType.NUMBER)
+            {
+                /* Ignore ADD. It is not required. */
+                insert_into_tree (new ConstantNode (this, token, make_precedence_t (token.type), get_associativity (token)));
+                return true;
+            }
+            else
+                return false;
+        }
+        else
+        {
+            lexer.roll_back ();
+            if (!variable ())
+                return false;
+            else
+                return true;
+        }
+    }
+
+    private bool expression_2 ()
+    {
+        var token = lexer.get_next_token ();
+        if (token.type == LexerTokenType.L_R_BRACKET)
+        {
+            insert_into_tree (new MultiplyNode (this, null, make_precedence_p (Precedence.MULTIPLY), get_associativity_p (Precedence.MULTIPLY)));
+
+            depth_level++;
+            if (!expression ())
+                return false;
+            token = lexer.get_next_token ();
+            if (token.type == LexerTokenType.R_R_BRACKET)
+            {
+                depth_level--;
+
+                if (!expression_2 ())
+                    return false;
+
+                return true;
+            }
+            else
+                return false;
+        }
+        else if (token.type == LexerTokenType.POWER)
+        {
+            insert_into_tree (new XPowYNode (this, token, make_precedence_t (token.type), get_associativity (token)));
+
+            if (!expression_1 ())
+                return false;
+            if (!expression_2 ())
+                return false;
+
+            return true;
+        }
+        else if (token.type == LexerTokenType.SUP_NUMBER)
+        {
+            insert_into_tree (new XPowYIntegerNode (this, null, make_precedence_p (Precedence.POWER), get_associativity_p (Precedence.POWER)));
+            insert_into_tree (new NameNode (this, token, make_precedence_p (Precedence.NUMBER_VARIABLE), get_associativity_p (Precedence.NUMBER_VARIABLE)));
+
+            if (!expression_2 ())
+                return false;
+
+            return true;
+        }
+        else if (token.type == LexerTokenType.NSUP_NUMBER)
+        {
+            insert_into_tree (new XPowYIntegerNode (this, null, make_precedence_p (Precedence.POWER), get_associativity_p (Precedence.POWER)));
+            insert_into_tree (new NameNode (this, token, make_precedence_p (Precedence.NUMBER_VARIABLE), get_associativity_p (Precedence.NUMBER_VARIABLE)));
+
+            if (!expression_2 ())
+                return false;
+
+            return true;
+        }
+        else if (token.type == LexerTokenType.FACTORIAL)
+        {
+            insert_into_tree_unary (new FactorialNode (this, token, make_precedence_t (token.type), get_associativity (token)));
+
+            if (!expression_2 ())
+                return false;
+
+            return true;
+        }
+        else if (token.type == LexerTokenType.MULTIPLY)
+        {
+            insert_into_tree (new MultiplyNode (this, token, make_precedence_t (token.type), get_associativity (token)));
+
+            if (!expression_1 ())
+                return false;
+            if (!expression_2 ())
+                return false;
+
+            return true;
+        }
+        else if (token.type == LexerTokenType.PERCENTAGE)
+        {
+            insert_into_tree_unary (new PercentNode (this, token, make_precedence_t (token.type), get_associativity (token)));
+
+            if (!expression_2 ())
+                return false;
+
+            return true;
+        }
+        else if (token.type == LexerTokenType.AND)
+        {
+            insert_into_tree (new AndNode (this, token, make_precedence_t (token.type), get_associativity (token)));
+
+            if (!expression_1 ())
+                return false;
+            if (!expression_2 ())
+                return false;
+
+            return true;
+        }
+        else if (token.type == LexerTokenType.OR)
+        {
+            insert_into_tree (new OrNode (this, token, make_precedence_t (token.type), get_associativity (token)));
+
+            if (!expression_1 ())
+                return false;
+            if (!expression_2 ())
+                return false;
+
+            return true;
+        }
+        else if (token.type == LexerTokenType.XOR)
+        {
+            insert_into_tree (new XorNode (this, token, make_precedence_t (token.type), get_associativity (token)));
+
+            if (!expression_1 ())
+                return false;
+            if (!expression_2 ())
+                return false;
+
+            return true;
+        }
+        else if (token.type == LexerTokenType.DIVIDE)
+        {
+            insert_into_tree (new DivideNode (this, token, make_precedence_t (token.type), get_associativity (token)));
+
+            if (!expression_1 ())
+                return false;
+            if (!expression_2 ())
+                return false;
+
+            return true;
+        }
+        else if (token.type == LexerTokenType.MOD)
+        {
+            insert_into_tree (new ModulusDivideNode (this, token, make_precedence_t (token.type), get_associativity (token)));
+
+            if (!expression_1 ())
+                return false;
+            if (!expression_2 ())
+                return false;
+
+            return true;
+        }
+        else if (token.type == LexerTokenType.ADD)
+        {
+            var node = new AddNode (this, token, make_precedence_t (token.type), get_associativity (token));
+            insert_into_tree (node);
+
+            if (!expression_1 ())
+                return false;
+
+            token = lexer.get_next_token ();
+            if (token.type == LexerTokenType.PERCENTAGE)
+            {
+                //FIXME: This condition needs to be verified for all cases.. :(
+                if (node.right.precedence > Precedence.PERCENTAGE)
+                {
+                    node.precedence = Precedence.PERCENTAGE;
+                    node.do_percentage = true;
+                    return true;
+                }
+                else
+                {
+                    /* Assume '%' to be part of 'expression PERCENTAGE' statement. */
+                    lexer.roll_back ();
+                    if (!expression_2 ())
+                        return true;
+                }
+            }
+            else
+                lexer.roll_back ();
+
+            if (!expression_2 ())
+                return false;
+
+            return true;
+        }
+        else if (token.type == LexerTokenType.SUBTRACT)
+        {
+            var node = new SubtractNode (this, token, make_precedence_t (token.type), get_associativity (token));
+            insert_into_tree (node);
+
+            if (!expression_1 ())
+                return false;
+            token = lexer.get_next_token ();
+            if (token.type == LexerTokenType.PERCENTAGE)
+            {
+                //FIXME: This condition needs to be verified for all cases.. :(
+                if (node.right.precedence > Precedence.PERCENTAGE)
+                {
+                    node.precedence = Precedence.PERCENTAGE;
+                    node.do_percentage = true;
+                    return true;
+                }
+                else
+                {
+                    /* Assume '%' to be part of 'expression PERCENTAGE' statement. */
+                    lexer.roll_back ();
+                    if (!expression_2 ())
+                        return true;
+                }
+            }
+            else
+                lexer.roll_back ();
+
+            if (!expression_2 ())
+                return false;
+
+            return true;
+        }
+        else
+        {
+            lexer.roll_back ();
+            return true;
+        }
+    }
+
+    private bool variable ()
+    {
+        var token = lexer.get_next_token ();
+        if (token.type == LexerTokenType.FUNCTION)
+        {
+            var token_old = token;
+            token = lexer.get_next_token ();
+            if (token.type == LexerTokenType.SUP_NUMBER)
+            {
+                /* Pass power as void * value. That will be taken care in pf_apply_func_with_power. */
+
+                insert_into_tree_unary (new FunctionWithPowerNode (this, token_old, make_precedence_t (token_old.type), get_associativity (token_old), token.text));
+                if (!expression ())
+                    return false;
+
+                return true;
+            }
+            else if (token.type == LexerTokenType.NSUP_NUMBER)
+            {
+                /* Pass power as void * value. That will be taken care in pf_apply_func_with_npower. */
+
+                insert_into_tree_unary (new FunctionWithNegativePowerNode (this, token_old, make_precedence_t (token_old.type), get_associativity (token_old), token.text));
+                if (!expression ())
+                    return false;
+
+                return true;
+            }
+            else
+            {
+                lexer.roll_back ();
+                insert_into_tree_unary (new FunctionNode (this, token_old, make_precedence_t (token_old.type), get_associativity (token_old)));
+                if (!expression ())
+                    return false;
+
+                return true;
+            }
+        }
+        else if (token.type == LexerTokenType.SUB_NUMBER)
+        {
+            var token_old = token;
+            token = lexer.get_next_token ();
+            if (token.type == LexerTokenType.ROOT)
+            {
+                insert_into_tree_unary (new RootNode (this, token, make_precedence_t (token.type), get_associativity (token), sub_atoi (token_old.text)));
+                if (!expression ())
+                    return false;
+
+                return true;
+            }
+            else
+                return false;
+        }
+        else if (token.type == LexerTokenType.ROOT)
+        {
+            insert_into_tree_unary (new RootNode (this, token, make_precedence_t (token.type), get_associativity (token), 2));
+
+            if (!expression ())
+                return false;
+
+            return true;
+        }
+        else if (token.type == LexerTokenType.ROOT_3)
+        {
+            insert_into_tree_unary (new RootNode (this, token, make_precedence_t (token.type), get_associativity (token), 3));
+
+            if (!expression ())
+                return false;
+
+            return true;
+        }
+        else if (token.type == LexerTokenType.ROOT_4)
+        {
+            insert_into_tree_unary (new RootNode (this, token, make_precedence_t (token.type), get_associativity (token), 4));
+
+            if (!expression ())
+                return false;
+
+            return true;
+        }
+        else if (token.type == LexerTokenType.VARIABLE)
+        {
+            lexer.roll_back ();
+            //TODO: unknown function ERROR for (VARIABLE SUP_NUMBER expression).
+            if (!term ())
+                return false;
+
+            return true;
+        }
+        else
+            return false;
+    }
+
+    private bool term ()
+    {
+        var token = lexer.get_next_token ();
+
+        if (token.type == LexerTokenType.VARIABLE)
+        {
+            var token_old = token;
+            /* Check if the token is a valid variable or not. */
+            if (!check_variable (token.text))
+            {
+                set_error (ErrorCode.UNKNOWN_VARIABLE, token.text, token.start_index, token.end_index);
+                return false;
+            }
+            token = lexer.get_next_token ();
+            if (token.type == LexerTokenType.SUP_NUMBER)
+                insert_into_tree (new VariableWithPowerNode (this, token_old, make_precedence_t (token_old.type), get_associativity (token_old), token.text));
+            else
+            {
+                lexer.roll_back ();
+                insert_into_tree (new VariableNode (this, token_old, make_precedence_t (token_old.type), get_associativity (token_old)));
+            }
+
+            if (!term_2 ())
+                return false;
+
+            return true;
+        }
+        else
+            return false;
+    }
+
+    private bool term_2 ()
+    {
+        var token = lexer.get_next_token ();
+        lexer.roll_back ();
+
+        if (token.type == LexerTokenType.PL_EOS || token.type == LexerTokenType.ASSIGN)
+            return true;
+
+        if (token.type == LexerTokenType.VARIABLE)
+        {
+            /* Insert multiply in between two distinct (variable). */
+            insert_into_tree (new MultiplyNode (this, null, make_precedence_p (Precedence.MULTIPLY), get_associativity_p (Precedence.MULTIPLY)));
+
+            if (!term ())
+                return false;
+
+            return true;
+        }
+        else
+            return true;
+    }
+}
diff --git a/src/equation.vala b/src/equation.vala
new file mode 100644
index 0000000..d2a4465
--- /dev/null
+++ b/src/equation.vala
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2004-2008 Sami Pietila
+ * Copyright (C) 2008-2012 Robert Ancell.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+public int sub_atoi (string data)
+{
+    const unichar digits[] = {'â', 'â', 'â', 'â', 'â', 'â', 'â', 'â', 'â', 'â'};
+
+    var index = 0;
+    unichar c;
+    int value = 0;
+    while (data.get_next_char (ref index, out c))
+    {
+        var is_subdigit = false;
+        for (var i = 0; i < digits.length; i++)
+        {
+            if (c == digits[i])
+            {
+                value = value * 10 + i;
+                is_subdigit = true;
+                break;
+            }
+        }
+        if (!is_subdigit)
+            return -1;
+    }
+
+    return value;
+}
+
+public int super_atoi (string data)
+{
+    const unichar digits[] = {'â', 'Â', 'Â', 'Â', 'â', 'â', 'â', 'â', 'â', 'â'};
+
+    var index = 0;
+    unichar c;
+    data.get_next_char (ref index, out c);
+    int sign = 1;
+    if (c == 'â')
+        sign = -1;
+    else
+        index = 0;
+
+    int value = 0;
+    while (data.get_next_char (ref index, out c))
+    {
+        var is_superdigit = false;
+        for (var i = 0; i < digits.length; i++)
+        {
+            if (c == digits[i])
+            {
+                value = value * 10 + i;
+                is_superdigit = true;
+                break;
+            }
+        }
+        if (!is_superdigit)
+            return 0;
+    }
+
+    return sign * value;
+}
+
+string mp_error_code_to_string (ErrorCode error_code)
+{
+    switch (error_code)
+    {
+    case ErrorCode.NONE:
+        return "ErrorCode.NONE";
+    case ErrorCode.INVALID:
+        return "ErrorCode.INVALID";
+    case ErrorCode.OVERFLOW:
+        return "ErrorCode.OVERFLOW";
+    case ErrorCode.UNKNOWN_VARIABLE:
+        return "ErrorCode.UNKNOWN_VARIABLE";
+    case ErrorCode.UNKNOWN_FUNCTION:
+        return "ErrorCode.UNKNOWN_FUNCTION";
+    case ErrorCode.UNKNOWN_CONVERSION:
+        return "ErrorCode.UNKNOWN_CONVERSION";
+    case ErrorCode.MP:
+        return "ErrorCode.MP";
+    default:
+        return "Unknown parser error";
+    }
+}
+
+public enum ErrorCode
+{
+    NONE,
+    INVALID,
+    OVERFLOW,
+    UNKNOWN_VARIABLE,
+    UNKNOWN_FUNCTION,
+    UNKNOWN_CONVERSION,
+    MP
+}
+
+public class Equation
+{
+    public int base;
+    public int wordlen;
+    public AngleUnit angle_units;
+    private string expression;
+
+    public Equation (string expression)
+    {
+        this.expression = expression;
+    }
+
+    public new Number? parse (out ErrorCode error_code = null, out string error_token = null, out uint error_start = null, out uint error_end = null)
+    {
+        var parser = new EquationParser (this, expression);
+        mp_clear_error ();
+
+        var z = parser.parse (out error_code, out error_token, out error_start, out error_end);
+
+        /* Error during parsing */
+        if (error_code != ErrorCode.NONE)
+            return null;
+
+        if (mp_get_error () != null)
+        {
+            error_code = ErrorCode.MP;
+            return null;
+        }
+
+        return z;
+    }
+
+    public virtual bool variable_is_defined (string name)
+    {
+        return false;
+    }
+
+    public virtual Number? get_variable (string name)
+    {
+        return null;
+    }
+
+    public virtual void set_variable (string name, Number x)
+    {
+    }
+
+    public virtual bool function_is_defined (string name)
+    {
+        return false;
+    }
+
+    public virtual Number? get_function (string name, Number x)
+    {
+        return null;
+    }
+
+    public virtual Number? convert (Number x, string x_units, string z_units)
+    {
+        return null;
+    }
+}
+
+private class EquationParser : Parser
+{
+    private Equation equation;
+
+    public EquationParser (Equation equation, string expression)
+    {
+        base (expression, equation.base, equation.wordlen);
+        this.equation = equation;
+    }
+
+    protected override bool variable_is_defined (string name)
+    {
+        /* FIXME: Make more generic */
+        if (name == "e" || name == "i" || name == "Ï")
+            return true;
+
+        return equation.variable_is_defined (name);
+    }
+
+    protected override Number? get_variable (string name)
+    {
+        if (name == "e")
+            return new Number.eulers ();
+        else if (name == "i")
+            return new Number.i ();
+        else if (name == "Ï")
+            return new Number.pi ();
+        else
+            return equation.get_variable (name);
+    }
+
+    protected override void set_variable (string name, Number x)
+    {
+        // Reserved words, e, Ï, mod, and, or, xor, not, abs, log, ln, sqrt, int, frac, sin, cos, ...
+        if (name == "e" || name == "i" || name == "Ï")
+            return; // FALSE
+
+        equation.set_variable (name, x);
+    }
+
+    // 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
+
+    protected override bool function_is_defined (string name)
+    {
+        var lower_name = name.down ();
+
+        /* FIXME: Make more generic */
+        if (lower_name == "log" ||
+            (lower_name.has_prefix ("log") && sub_atoi (lower_name.substring (3)) >= 0) ||
+            lower_name == "ln" ||
+            lower_name == "sqrt" ||
+            lower_name == "abs" ||
+            lower_name == "sgn" ||
+            lower_name == "arg" ||
+            lower_name == "conj" ||
+            lower_name == "int" ||
+            lower_name == "frac" ||
+            lower_name == "floor" ||
+            lower_name == "ceil" ||
+            lower_name == "round" ||
+            lower_name == "re" ||
+            lower_name == "im" ||
+            lower_name == "sin" || lower_name == "cos" || lower_name == "tan" ||
+            lower_name == "asin" || lower_name == "acos" || lower_name == "atan" ||
+            lower_name == "sinâÂ" || lower_name == "cosâÂ" || lower_name == "tanâÂ" ||
+            lower_name == "sinh" || lower_name == "cosh" || lower_name == "tanh" ||
+            lower_name == "sinhâÂ" || lower_name == "coshâÂ" || lower_name == "tanhâÂ" ||
+            lower_name == "asinh" || lower_name == "acosh" || lower_name == "atanh" ||
+            lower_name == "ones" ||
+            lower_name == "twos")
+            return true;
+
+        return equation.function_is_defined (name);
+    }
+
+    protected override Number? get_function (string name, Number x)
+    {
+        var lower_name = name.down ();
+
+        // FIXME: Re Im ?
+
+        if (lower_name == "log")
+            return x.logarithm (10); // FIXME: Default to ln
+        else if (lower_name.has_prefix ("log"))
+        {
+            var number_base = sub_atoi (lower_name.substring (3));
+            if (number_base < 0)
+                return null;
+            else
+                return x.logarithm (number_base);
+        }
+        else if (lower_name == "ln")
+            return x.ln ();
+        else if (lower_name == "sqrt") // âx
+            return x.sqrt ();
+        else if (lower_name == "abs") // |x|
+            return x.abs ();
+        else if (lower_name == "sgn")
+            return x.sgn ();
+        else if (lower_name == "arg")
+            return x.arg (equation.angle_units);
+        else if (lower_name == "conj")
+            return x.conjugate ();
+        else if (lower_name == "int")
+            return x.integer_component ();
+        else if (lower_name == "frac")
+            return x.fractional_component ();
+        else if (lower_name == "floor")
+            return x.floor ();
+        else if (lower_name == "ceil")
+            return x.ceiling ();
+        else if (lower_name == "round")
+            return x.round ();
+        else if (lower_name == "re")
+            return x.real_component ();
+        else if (lower_name == "im")
+            return x.imaginary_component ();
+        else if (lower_name == "sin")
+            return x.sin (equation.angle_units);
+        else if (lower_name == "cos")
+            return x.cos (equation.angle_units);
+        else if (lower_name == "tan")
+            return x.tan (equation.angle_units);
+        else if (lower_name == "sinâÂ" || lower_name == "asin")
+            return x.asin (equation.angle_units);
+        else if (lower_name == "cosâÂ" || lower_name == "acos")
+            return x.acos (equation.angle_units);
+        else if (lower_name == "tanâÂ" || lower_name == "atan")
+            return x.atan (equation.angle_units);
+        else if (lower_name == "sinh")
+            return x.sinh ();
+        else if (lower_name == "cosh")
+            return x.cosh ();
+        else if (lower_name == "tanh")
+            return x.tanh ();
+        else if (lower_name == "sinhâÂ" || lower_name == "asinh")
+            return x.asinh ();
+        else if (lower_name == "coshâÂ" || lower_name == "acosh")
+            return x.acosh ();
+        else if (lower_name == "tanhâÂ" || lower_name == "atanh")
+            return x.atanh ();
+        else if (lower_name == "ones")
+            return x.ones_complement (equation.wordlen);
+        else if (lower_name == "twos")
+            return x.twos_complement (equation.wordlen);
+        else
+            return equation.get_function (name, x);
+    }
+
+    protected override Number? convert (Number x, string x_units, string z_units)
+    {
+        return equation.convert (x, x_units, z_units);
+    }
+}
diff --git a/src/financial.vala b/src/financial.vala
new file mode 100644
index 0000000..c7c758f
--- /dev/null
+++ b/src/financial.vala
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (C) 2008-2012 Robert Ancell
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+public enum FinancialDialog
+{
+    CTRM_DIALOG,
+    DDB_DIALOG,
+    FV_DIALOG,
+    GPM_DIALOG,
+    PMT_DIALOG,
+    PV_DIALOG,
+    RATE_DIALOG,
+    SLN_DIALOG,
+    SYD_DIALOG,
+    TERM_DIALOG
+}
+
+public void do_finc_expression (MathEquation equation, FinancialDialog function, Number arg1, Number arg2, Number arg3, Number arg4)
+{
+    Number result;
+    switch (function)
+    {
+    case FinancialDialog.CTRM_DIALOG:
+        result = calc_ctrm (equation, arg1, arg2, arg3);
+        break;
+    case FinancialDialog.DDB_DIALOG:
+        result = calc_ddb (equation, arg1, arg2, arg3);
+        break;
+    case FinancialDialog.FV_DIALOG:
+        result = calc_fv (equation, arg1, arg2, arg3);
+        break;
+    case FinancialDialog.GPM_DIALOG:
+        result = calc_gpm (equation, arg1, arg2);
+        break;
+    case FinancialDialog.PMT_DIALOG:
+        result = calc_pmt (equation, arg1, arg2, arg3);
+        break;
+    case FinancialDialog.PV_DIALOG:
+        result = calc_pv (equation, arg1, arg2, arg3);
+        break;
+    case FinancialDialog.RATE_DIALOG:
+        result = calc_rate (equation, arg1, arg2, arg3);
+        break;
+    case FinancialDialog.SLN_DIALOG:
+        result = calc_sln (equation, arg1, arg2, arg3);
+        break;
+    case FinancialDialog.SYD_DIALOG:
+        result = calc_syd (equation, arg1, arg2, arg3, arg4);
+        break;
+    case FinancialDialog.TERM_DIALOG:
+        result = calc_term (equation, arg1, arg2, arg3);
+        break;
+    default:
+        result = new Number.integer (0);
+        break;
+    }
+
+    equation.set_number (result);
+}
+
+private Number calc_ctrm (MathEquation equation, Number pint, Number fv, Number pv)
+{
+    /*  Cterm - pint (periodic interest rate).
+     *          fv  (future value).
+     *          pv  (present value).
+     *
+     *          RESULT = log (fv / pv) / log (1 + pint)
+     */
+    var t1 = fv.divide (pv);
+    var t2 = t1.ln ();
+    var t3 = pint.add (new Number.integer (1));
+    var t4 = t3.ln ();
+    return t2.divide (t4);
+}
+
+private Number calc_ddb (MathEquation equation, Number cost, Number life, Number 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
+     *
+     */
+
+    var z = new Number.integer (0);
+
+    var tbv = new Number.integer (0);
+    var len = period.to_integer ();
+    for (var i = 0; i < len; i++)
+    {
+        var t1 = cost.subtract (tbv);
+        var t2 = t1.multiply_integer (2);
+        z = t2.divide (life);
+        t1 = tbv;
+        tbv = t1.add (z); /* TODO: why result is tbv, for next loop? */
+    }
+
+    if (len >= 0)
+        equation.status = _("Error: the number of periods must be positive");
+
+    return z;
+}
+
+private Number calc_fv (MathEquation equation, Number pmt, Number pint, Number n)
+{
+    /*  Fv    - pmt (periodic payment).
+     *          pint (periodic interest rate).
+     *          n   (number of periods).
+     *
+     *          RESULT = pmt * (pow (1 + pint, n) - 1) / pint
+     */
+
+    var t1 = pint.add (new Number.integer (1));
+    var t2 = t1.xpowy (n);
+    var t3 = t2.add (new Number.integer (-1));
+    var t4 = pmt.multiply (t3);
+    return t4.divide (pint);
+}
+
+private Number calc_gpm (MathEquation equation, Number cost, Number margin)
+{
+    /*  Gpm   - cost (cost of sale).
+     *          margin (gross profit margin.
+     *
+     *          RESULT = cost / (1 - margin)
+     */
+
+    var t1 = new Number.integer (1);
+    var t2 = t1.subtract (margin);
+    return cost.divide (t2);
+}
+
+private Number calc_pmt (MathEquation equation, Number prin, Number pint, Number n)
+{
+    /*  Pmt   - prin (principal).
+     *          pint  (periodic interest rate).
+     *          n    (term).
+     *
+     *          RESULT = prin * (pint / (1 - pow (pint + 1, -1 * n)))
+     */
+
+    var t1 = pint.add (new Number.integer (1));
+    var t2 = n.multiply_integer (-1);
+    var t3 = t1.xpowy (t2);
+    var t4 = t3.multiply_integer (-1);
+    t1 = t4.add (new Number.integer (1));
+    t2 = pint.divide (t1);
+    return prin.multiply (t2);
+}
+
+private Number calc_pv (MathEquation equation, Number pmt, Number pint, Number n)
+{
+    /*  Pv    - pmt (periodic payment).
+     *          pint (periodic interest rate).
+     *          n   (term).
+     *
+     *          RESULT = pmt * (1 - pow (1 + pint, -1 * n)) / pint
+     */
+
+    var t1 = pint.add (new Number.integer (1));
+    var t2 = n.multiply_integer (-1);
+    var t3 = t1.xpowy (t2);
+    var t4 = t3.multiply_integer (-1);
+    t1 = t4.add (new Number.integer (1));
+    t2 = t1.divide (pint);
+    return pmt.multiply (t2);
+}
+
+private Number calc_rate (MathEquation equation, Number fv, Number pv, Number n)
+{
+    /*  Rate  - fv (future value).
+     *          pv (present value).
+     *          n  (term).
+     *
+     *          RESULT = pow (fv / pv, 1 / n) - 1
+     */
+
+    var t1 = fv.divide (pv);
+    var t2 = new Number.integer (1);
+    var t3 = t2.divide (n);
+    var t4 = t1.xpowy (t3);
+    return t4.add (new Number.integer (-1));
+}
+
+private Number calc_sln (MathEquation equation, Number cost, Number salvage, Number life)
+{
+    /*  Sln   - cost    (cost of the asset).
+     *          salvage (salvage value of the asset).
+     *          life    (useful life of the asset).
+     *
+     *          RESULT = (cost - salvage) / life
+     */
+
+    var t1 = cost.subtract (salvage);
+    return t1.divide (life);
+}
+
+private Number calc_syd (MathEquation equation, Number cost, Number salvage, Number life, Number 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
+     */
+
+    var t3 = life.subtract (period).add (new Number.integer (1));
+    var t2 = life.add (new Number.integer (1));
+    var t4 = life.multiply (t2);
+    var t1 = t4.divide (new Number.integer (2));
+    t2 = t3.divide (t1);
+    t1 = cost.subtract (salvage);
+    return t1.multiply (t2);
+}
+
+private Number calc_term (MathEquation equation, Number pmt, Number fv, Number pint)
+{
+    /*  Term  - pmt (periodic payment).
+     *          fv  (future value).
+     *          pint (periodic interest rate).
+     *
+     *          RESULT = log (1 + (fv * pint / pmt)) / log (1 + pint)
+     */
+
+    var t1 = pint.add (new Number.integer (1));
+    var t2 = t1.ln ();
+    t1 = fv.multiply (pint);
+    var t3 = t1.divide (pmt);
+    var t4 = t3.add (new Number.integer (1));
+    t1 = t4.ln ();
+    return t1.divide (t2);
+}
diff --git a/src/gcalccmd.vala b/src/gcalccmd.vala
new file mode 100644
index 0000000..bc5e7a0
--- /dev/null
+++ b/src/gcalccmd.vala
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2009 Rich Burridge
+ * Copyright (C) 2012 Robert Ancell
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+private const int MAXLINE = 1024;
+
+private static Serializer result_serializer;
+
+static void solve (string equation)
+{
+    var e = new Equation (equation);
+    e.base = 10;
+    e.wordlen = 32;
+    e.angle_units = AngleUnit.DEGREES;
+
+    ErrorCode ret;
+    var z = e.parse (out ret, null);
+
+    if (z != null)
+        stdout.printf ("%s\n", result_serializer.to_string (z));
+    else if (ret == ErrorCode.MP)
+        stderr.printf ("Error %s\n", mp_get_error ());
+    else
+        stderr.printf ("Error %d\n", ret);
+}
+
+public static int main (string[] args)
+{
+    /* Seed random number generator. */
+    var now = new DateTime.now_utc ();
+    Random.set_seed (now.get_microsecond ());
+
+    Intl.setlocale (LocaleCategory.ALL, "");
+
+    result_serializer = new Serializer (DisplayFormat.AUTOMATIC, 10, 9);
+
+    var buffer = new char[1024];
+    while (true)
+    {
+        stdout.printf ("> ");
+        var line = stdin.gets (buffer);
+
+        line = line.strip ();
+        if (line == null || line == "exit" || line == "quit" || line == "")
+            break;
+
+        solve (line);
+    }
+
+    return Posix.EXIT_SUCCESS;
+}
diff --git a/src/gcalctool.vala b/src/gcalctool.vala
new file mode 100644
index 0000000..3f5924a
--- /dev/null
+++ b/src/gcalctool.vala
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (C) 2008-2012 Robert Ancell
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+public class GCalctool : Gtk.Application
+{
+    private Settings settings;
+    private MathWindow window;
+    private MathPreferencesDialog preferences_dialog;
+
+    private const ActionEntry[] app_entries =
+    {
+        { "copy", copy_cb, null, null, null },
+        { "paste", paste_cb, null, null, null },
+        { "undo", undo_cb, null, null, null },
+        { "redo", redo_cb, null, null, null },
+        { "mode", mode_changed_cb, "s", "\"basic\"", null },
+        { "preferences", show_preferences_cb, null, null, null },
+        { "help", help_cb, null, null, null },
+        { "about", about_cb, null, null, null },
+        { "quit", quit_cb, null, null, null },
+    };
+    
+    public GCalctool ()
+    {
+        Object (flags : ApplicationFlags.NON_UNIQUE);
+    }
+
+    protected override void startup ()
+    {
+        base.startup ();
+
+        settings = new Settings ("org.gnome.gcalctool");
+        var accuracy = settings.get_int ("accuracy");
+        var word_size = settings.get_int ("word-size");
+        var number_base = settings.get_int ("base");
+        var show_tsep = settings.get_boolean ("show-thousands");
+        var show_zeroes = settings.get_boolean ("show-zeroes");
+        var number_format = (DisplayFormat) settings.get_enum ("number-format");
+        var angle_units = (AngleUnit) settings.get_enum ("angle-units");
+        var button_mode = (ButtonMode) settings.get_enum ("button-mode");
+        var source_currency = settings.get_string ("source-currency");
+        var target_currency = settings.get_string ("target-currency");
+        var source_units = settings.get_string ("source-units");
+        var target_units = settings.get_string ("target-units");
+
+        var equation = new MathEquation ();
+        equation.accuracy = accuracy;
+        equation.word_size = word_size;
+        equation.show_thousands_separators = show_tsep;
+        equation.show_trailing_zeroes = show_zeroes;
+        equation.number_format = number_format;
+        equation.angle_units = angle_units;
+        equation.source_currency = source_currency;
+        equation.target_currency = target_currency;
+        equation.source_units = source_units;
+        equation.target_units = target_units;
+
+        add_action_entries (app_entries, this);
+
+        window = new MathWindow (this, equation);
+        var buttons = window.buttons;
+        buttons.programming_base = number_base;
+        buttons.mode = button_mode; // FIXME: We load the basic buttons even if we immediately switch to the next type
+        buttons.notify["mode"].connect ((pspec) => { mode_cb (); });
+        mode_cb ();
+
+        var menu = new Menu ();
+
+        var section = new Menu ();
+        section.append (_("Basic"), "app.mode::basic");
+        section.append (_("Advanced"), "app.mode::advanced");
+        section.append (_("Financial"), "app.mode::financial");
+        section.append (_("Programming"), "app.mode::programming");
+        menu.append_section (_("Mode"), section);
+
+        section = new Menu ();
+        section.append (_("Preferences"), "app.preferences");
+        menu.append_section (null, section);
+
+        section = new Menu ();
+        section.append (_("About Calculator"), "app.about");
+        section.append (_("Help"), "app.help");
+        section.append (_("Quit"), "app.quit");
+        menu.append_section (null, section);
+
+        set_app_menu (menu);
+
+        add_accelerator ("<control>Q", "app.quit", null);
+        add_accelerator ("F1", "app.help", null);
+        add_accelerator ("<control>C", "app.copy", null);
+        add_accelerator ("<control>V", "app.paste", null);
+        add_accelerator ("<control>Z", "app.undo", null);
+        add_accelerator ("<control><shift>Z", "app.redo", null);
+    }
+
+    protected override void activate ()
+    {
+        base.activate ();
+
+        window.present ();
+    }
+
+    protected override void shutdown ()
+    {
+        base.shutdown ();
+
+        var equation = window.equation;
+        var buttons = window.buttons;
+
+        settings.set_enum ("button-mode", buttons.mode);
+        settings.set_int ("accuracy", equation.accuracy);
+        settings.set_int ("word-size", equation.word_size);
+        settings.set_boolean ("show-thousands", equation.show_thousands_separators);
+        settings.set_boolean ("show-zeroes", equation.show_trailing_zeroes);
+        settings.set_enum ("number-format", equation.number_format);
+        settings.set_enum ("angle-units", equation.angle_units);
+        settings.set_string ("source-currency", equation.source_currency);
+        settings.set_string ("target-currency", equation.target_currency);
+        settings.set_string ("source-units", equation.source_units);
+        settings.set_string ("target-units", equation.target_units);
+        settings.set_int ("base", buttons.programming_base);
+    }
+
+    private static void solve (string equation)
+    {
+        var e = new SolveEquation (equation);
+        e.base = 10;
+        e.wordlen = 32;
+        e.angle_units = AngleUnit.DEGREES;
+
+        ErrorCode error;
+        var result = e.parse (out error);
+        if (result != null)
+        {
+            var serializer = new Serializer (DisplayFormat.AUTOMATIC, 10, 9);
+            stdout.printf ("%s\n", serializer.to_string (result));
+            Posix.exit (0);
+        }
+        else if (error == ErrorCode.MP)
+        {
+            stderr.printf ("Error: %s\n", mp_get_error ());
+            Posix.exit (Posix.EXIT_FAILURE);
+        }
+        else
+        {
+            stderr.printf ("Error: %s\n", mp_error_code_to_string (error));
+            Posix.exit (Posix.EXIT_FAILURE);
+        }
+    }
+
+    private static void usage (string progname, bool show_application, bool show_gtk)
+    {
+        stderr.printf (/* Description on how to use gcalctool displayed on command-line */
+                       _("Usage:\n  %s â Perform mathematical calculations"), progname);
+
+        stderr.printf ("\n\n");
+
+        stderr.printf (/* 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"));
+        stderr.printf ("\n\n");
+
+        if (show_gtk)
+        {
+            stderr.printf (/* 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"));
+            stderr.printf ("\n\n");
+        }
+
+        if (show_application)
+        {
+            stderr.printf (/* Description on gcalctool application options displayed on command-line */
+                           _("Application Options:\n  -s, --solve <equation>          Solve the given equation"));
+            stderr.printf ("\n\n");
+        }
+    }
+
+    private static void get_options (string[] args)
+    {
+        var progname = Path.get_basename (args[0]);
+
+        for (var i = 1; i < args.length; i++)
+        {
+            var arg = args[i];
+
+            if (arg == "-v" || arg == "--version")
+            {
+                /* NOTE: Is not translated so can be easily parsed */
+                stderr.printf ("%1$s %2$s\n", progname, VERSION);
+                Posix.exit (Posix.EXIT_SUCCESS);
+            }
+            else if (arg == "-h" || arg == "-?" || arg == "--help")
+            {
+                usage (progname, true, false);
+                Posix.exit (Posix.EXIT_SUCCESS);
+            }
+            else if (arg == "--help-all")
+            {
+                usage (progname, true, true);
+                Posix.exit (Posix.EXIT_SUCCESS);
+            }
+            else if (arg == "--help-gtk")
+            {
+                usage (progname, false, true);
+                Posix.exit (Posix.EXIT_SUCCESS);
+            }
+            else if (arg == "-s" || arg == "--solve")
+            {
+                i++;
+                if (i >= args.length)
+                {
+                    stderr.printf (/* Error printed to stderr when user uses --solve argument without an equation */
+                                   _("Argument --solve requires an equation to solve"));
+                    stderr.printf ("\n");
+                    Posix.exit (Posix.EXIT_FAILURE);
+                }
+                else
+                    solve (args[i]);
+            }
+            else
+            {
+                stderr.printf (/* Error printed to stderr when user provides an unknown command-line argument */
+                               _("Unknown argument '%s'"), arg);
+                stderr.printf ("\n");
+                usage (progname, true, false);
+                Posix.exit (Posix.EXIT_FAILURE);
+            }
+        }
+    }
+
+    private void mode_cb ()
+    {
+        var buttons = window.buttons;
+        var state = "basic";
+        switch (buttons.mode)
+        {
+        default:
+        case ButtonMode.BASIC:
+            state = "basic";
+            //FIXME: Should it revert to decimal mode? equation.number_format = NumberFormat.DECIMAL;
+            break;
+
+        case ButtonMode.ADVANCED:
+            state = "advanced";
+            break;
+
+        case ButtonMode.FINANCIAL:
+            state = "financial";
+            break;
+
+        case ButtonMode.PROGRAMMING:
+            state = "programming";
+            break;
+        }
+
+        var action = lookup_action ("mode") as SimpleAction;
+        action.set_state (new Variant.string (state));
+    }
+
+    private void copy_cb ()
+    {
+        window.equation.copy ();
+    }
+
+    private void paste_cb ()
+    {
+        window.equation.paste ();
+    }
+
+    private void undo_cb ()
+    {
+        window.equation.undo ();
+    }
+
+    private void redo_cb ()
+    {
+        window.equation.redo ();
+    }
+
+    private void mode_changed_cb (SimpleAction action, Variant? parameter)
+    {
+        var mode = ButtonMode.BASIC;
+        var mode_str = parameter.get_string (null);
+        if (mode_str == "basic")
+            mode = ButtonMode.BASIC;
+        else if (mode_str == "advanced")
+            mode = ButtonMode.ADVANCED;
+        else if (mode_str == "financial")
+            mode = ButtonMode.FINANCIAL;
+        else if (mode_str == "programming")
+            mode = ButtonMode.PROGRAMMING;
+        window.buttons.mode = mode;
+    }
+
+    private void show_preferences_cb ()
+    {
+        if (preferences_dialog == null)
+        {
+            preferences_dialog = new MathPreferencesDialog (window.equation);
+            preferences_dialog.set_transient_for (window);
+        }
+        preferences_dialog.present ();
+    }
+
+    private void help_cb ()
+    {
+        try
+        {
+            Gtk.show_uri (window.get_screen (), "help:gcalctool", Gtk.get_current_event_time ());
+        }
+        catch (Error e)
+        {
+            /* Translators: Error message displayed when unable to launch help browser */
+            var message = _("Unable to open help file");
+
+            var d = new Gtk.MessageDialog (window,
+                                           Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                                           Gtk.MessageType.ERROR,
+                                           Gtk.ButtonsType.CLOSE,
+                                           "%s", message);
+            d.format_secondary_text ("%s", e.message);
+            d.run ();
+            d.destroy ();
+        }
+    }
+
+    private void about_cb ()
+    {
+        string[] authors =
+        {
+            "Rich Burridge <rich burridge gmail com>",
+            "Robert Ancell <robert ancell gmail com>",
+            "Klaus NiederkrÃger <kniederk umpa ens-lyon fr>",
+            "Robin Sonefors <ozamosi flukkost nu>",
+            null
+        };
+        string[] documenters =
+        {
+            "Sun Microsystems",
+            null
+        };
+
+        /* The translator credits. Please translate this with your name (s). */
+        var translator_credits = _("translator-credits");
+
+        /* The license this software is under (GPL2+) */
+        var license = _("Gcalctool is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation; either version 2 of the License, or\n(at your option) any later version.\n\nGcalctool is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with Gcalctool; if not, write to the Free Software Foundation, Inc.,\n51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA");
+
+        Gtk.show_about_dialog (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");
+    }
+
+    private void quit_cb ()
+    {
+        window.destroy ();
+    }
+
+    public static int main (string[] args)
+    {
+        Intl.setlocale (LocaleCategory.ALL, "");
+        Intl.bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR);
+        Intl.bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+        Intl.textdomain (GETTEXT_PACKAGE);
+
+        /* Seed random number generator. */
+        var now = new DateTime.now_utc ();
+        Random.set_seed (now.get_microsecond ());
+
+        get_options (args);
+
+        Gtk.init (ref args);
+
+        Gtk.Window.set_default_icon_name ("accessories-calculator");
+
+        var app = new GCalctool ();
+
+        return app.run (args);
+    }
+}
+
+private class SolveEquation : Equation
+{
+    public SolveEquation (string text)
+    {
+        base (text);
+    }
+
+    public override Number? convert (Number x, string x_units, string z_units)
+    {
+        return UnitManager.get_default ().convert_by_symbol (x, x_units, z_units);
+    }
+}
\ No newline at end of file
diff --git a/src/math-buttons.vala b/src/math-buttons.vala
new file mode 100644
index 0000000..102eedf
--- /dev/null
+++ b/src/math-buttons.vala
@@ -0,0 +1,1003 @@
+/*
+ * Copyright (C) 2008-2012 Robert Ancell
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+public enum ButtonMode
+{
+    BASIC,
+    ADVANCED,
+    FINANCIAL,
+    PROGRAMMING
+}
+
+public class MathButtons : Gtk.Box
+{
+    private MathEquation equation;
+
+    private ButtonMode _mode;
+    public ButtonMode mode
+    {
+        get { return _mode; }
+        set
+        {
+            if (_mode == value)
+                return;
+            _mode = value;
+
+            if (mode == ButtonMode.PROGRAMMING)
+                equation.number_base = _programming_base;
+            else
+                equation.number_base = 10;
+
+            load_buttons ();
+
+            converter.set_visible (mode == ButtonMode.ADVANCED || mode == ButtonMode.FINANCIAL);
+            if (mode == ButtonMode.ADVANCED)
+            {
+                converter.set_category (null);
+                converter.set_conversion (equation.source_units, equation.target_units);
+            }
+            else if (mode == ButtonMode.FINANCIAL)
+            {
+                converter.set_category ("currency");
+                converter.set_conversion (equation.source_currency, equation.target_currency);
+            }
+        }
+    }
+    private int _programming_base = 10;
+
+    private MathConverter converter;
+
+    private Gtk.Builder basic_ui;
+    private Gtk.Builder advanced_ui;
+    private Gtk.Builder financial_ui;
+    private Gtk.Builder programming_ui;
+
+    private Gtk.Widget bas_panel;
+    private Gtk.Widget adv_panel;
+    private Gtk.Widget fin_panel;
+    private Gtk.Widget prog_panel;
+    private Gtk.Widget? active_panel = null;
+
+    private Gtk.Menu shift_left_menu;
+    private Gtk.Menu shift_right_menu;
+
+    private Gtk.Menu function_menu;
+
+    private List<Gtk.ToggleButton> superscript_toggles;
+    private List<Gtk.ToggleButton> subscript_toggles;
+
+    private Gtk.ComboBox base_combo;
+    private Gtk.Label base_label;
+    private Gtk.Widget bit_panel;
+    private List<Gtk.Label> bit_labels;
+
+    private Gtk.Dialog character_code_dialog;
+    private Gtk.Entry character_code_entry;
+
+    /* The names of each field in the dialogs for the financial functions */
+    private const string[] ctrm_entries =  {"ctrm_pint", "ctrm_fv", "ctrm_pv"};
+    private const string[] ddb_entries = {"ddb_cost", "ddb_life", "ddb_period"};
+    private const string[] fv_entries = {"fv_pmt", "fv_pint", "fv_n"};
+    private const string[] gpm_entries = {"gpm_cost", "gpm_margin"};
+    private const string[] pmt_entries = {"pmt_prin", "pmt_pint", "pmt_n"};
+    private const string[] pv_entries = {"pv_pmt", "pv_pint", "pv_n"};
+    private const string[] rate_entries = {"rate_fv", "rate_pv", "rate_n"};
+    private const string[] sln_entries = {"sln_cost", "sln_salvage", "sln_life"};
+    private const string[] syd_entries = {"syd_cost", "syd_salvage", "syd_life"};
+    private const string[] term_entries = {"term_pmt","term_fv", "term_pint"};
+
+    public MathButtons (MathEquation equation)
+    {
+        Object (orientation: Gtk.Orientation.VERTICAL);
+        spacing = 6;
+        show.connect (load_buttons);
+        this.equation = equation;
+
+        equation.notify["display"].connect ((pspec) => { update_bit_panel (); });
+        equation.notify["number-mode"].connect ((pspec) => { number_mode_changed_cb (); });
+        equation.notify["angle-units"].connect ((pspec) => { update_bit_panel (); });
+        equation.notify["number-format"].connect ((pspec) => { update_bit_panel (); });
+        number_mode_changed_cb ();
+        update_bit_panel ();
+    }
+
+    private void load_finc_dialogs ()
+    {
+        load_finc_dialog ("ctrm_dialog", ctrm_entries, FinancialDialog.CTRM_DIALOG);
+        load_finc_dialog ("ddb_dialog", ddb_entries, FinancialDialog.DDB_DIALOG);
+        load_finc_dialog ("fv_dialog", fv_entries, FinancialDialog.FV_DIALOG);
+        load_finc_dialog ("gpm_dialog", gpm_entries, FinancialDialog.GPM_DIALOG);
+        load_finc_dialog ("pmt_dialog", pmt_entries, FinancialDialog.PMT_DIALOG);
+        load_finc_dialog ("pv_dialog", pv_entries, FinancialDialog.PV_DIALOG);
+        load_finc_dialog ("rate_dialog", rate_entries, FinancialDialog.RATE_DIALOG);
+        load_finc_dialog ("sln_dialog", sln_entries, FinancialDialog.SLN_DIALOG);
+        load_finc_dialog ("syd_dialog", syd_entries, FinancialDialog.SYD_DIALOG);
+        load_finc_dialog ("term_dialog", term_entries, FinancialDialog.TERM_DIALOG);
+    }
+
+    private void load_finc_dialog (string name, string[] entry_names, FinancialDialog function)
+    {
+        var dialog = financial_ui.get_object (name) as Gtk.Dialog;
+        dialog.set_data<int> ("finc-function", function);
+        dialog.response.connect (finc_response_cb);
+        for (var i = 0; i < entry_names.length; i++)
+        {
+            var entry = financial_ui.get_object (entry_names[i]) as Gtk.Entry;
+            if (i != ctrm_entries.length - 1)
+                entry.set_data<Gtk.Entry> ("next-entry", financial_ui.get_object (entry_names[i+1]) as Gtk.Entry);
+            entry.activate.connect (finc_activate_cb);
+        }
+    }
+
+    private void update_bit_panel ()
+    {
+        if (bit_panel == null)
+            return;
+
+        var x = equation.number;
+
+        uint64 bits = 0;
+        var enabled = x != null;
+        if (enabled)
+        {
+            var max = new Number.unsigned_integer (uint64.MAX);
+            var fraction = x.fractional_part ();
+            if (x.is_negative () || x.compare (max) > 0 || !fraction.is_zero ())
+                enabled = false;
+            else
+                bits = x.to_unsigned_integer ();
+        }
+
+        bit_panel.set_sensitive (enabled);
+        base_label.set_sensitive (enabled);
+
+        if (!enabled)
+            return;
+
+        var i = 0;
+        foreach (var label in bit_labels)
+        {
+            var text = " 0";
+            if ((bits & (1LL << i)) != 0)
+                text = " 1";
+            label.set_text (text);
+            i++;
+        }
+
+        var number_base = equation.number_base;
+        var label = "";
+        if (number_base != 8)
+            label += "%lloâ".printf (bits);
+        if (number_base != 10)
+        {
+            if (label != "")
+                label += " = ";
+            label += "%lluââ".printf (bits);
+        }
+        if (number_base != 16)
+        {
+            if (label != "")
+                label += " = ";
+            label += "%llXââ".printf (bits);
+        }
+
+        base_label.set_text (label);
+    }
+
+    private void base_combobox_changed_cb (Gtk.ComboBox combo)
+    {
+        var model = combo.get_model ();
+        Gtk.TreeIter iter;
+        combo.get_active_iter (out iter);
+        int value;
+        model.get (iter, 1, out value, -1);
+
+        programming_base = value;
+    }
+
+    private void base_changed_cb ()
+    {
+        if (mode != ButtonMode.PROGRAMMING)
+            return;
+
+        _programming_base = equation.number_base;
+
+        var model = base_combo.get_model ();
+        Gtk.TreeIter iter;
+        var valid = model.get_iter_first (out iter);
+        while (valid)
+        {
+            int v;
+            model.get (iter, 1, out v, -1);
+            if (v == programming_base)
+                break;
+            valid = model.iter_next (ref iter);
+        }
+        if (!valid)
+            valid = model.get_iter_first (out iter);
+
+        base_combo.set_active_iter (iter);
+    }
+
+    private Gtk.Widget load_mode (ButtonMode mode)
+    {
+        const string 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 };
+
+        Gtk.Builder builder;
+        string builder_filename;
+        switch (mode)
+        {
+        default:
+        case ButtonMode.BASIC:
+            if (bas_panel != null)
+                return bas_panel;
+            builder = basic_ui = new Gtk.Builder ();
+            builder_filename = Path.build_filename (UI_DIR, "buttons-basic.ui");
+            break;
+        case ButtonMode.ADVANCED:
+            if (adv_panel != null)
+                return adv_panel;
+            builder = advanced_ui = new Gtk.Builder ();
+            builder_filename = Path.build_filename (UI_DIR, "buttons-advanced.ui");
+            break;
+        case ButtonMode.FINANCIAL:
+            if (fin_panel != null)
+                return fin_panel;
+            builder = financial_ui = new Gtk.Builder ();
+            builder_filename = Path.build_filename (UI_DIR, "buttons-financial.ui");
+            break;
+        case ButtonMode.PROGRAMMING:
+            if (prog_panel != null)
+                return prog_panel;
+            builder = programming_ui = new Gtk.Builder ();
+            builder_filename = Path.build_filename (UI_DIR, "buttons-programming.ui");            
+            break;
+        }
+
+        // FIXME: Show dialog if failed to load
+        try
+        {
+            builder.add_objects_from_file (builder_filename, objects);
+        }
+        catch (Error e)
+        {
+            warning ("Error loading button UI: %s", e.message);
+        }
+        var panel = builder.get_object ("button_panel") as Gtk.Widget;
+        pack_end (panel, true, true, 0);
+
+        switch (mode)
+        {
+        default:
+        case ButtonMode.BASIC:
+            bas_panel = panel;
+            break;
+        case ButtonMode.ADVANCED:
+            adv_panel = panel;
+            break;
+        case ButtonMode.FINANCIAL:
+            fin_panel = panel;
+            break;
+        case ButtonMode.PROGRAMMING:
+            prog_panel = panel;
+            break;
+        }
+
+        /* Configure buttons */
+        /* Tooltip for the Pi button */
+        setup_button (builder, "pi", "Ï", _("Pi [Ctrl+P]"));
+        /* Tooltip for the Euler's Number button */
+        setup_button (builder, "eulers_number", "e", _("Eulerâs Number"));
+        setup_button (builder, "imaginary", "i", null);
+        setup_button (builder, "numeric_point", null, null);
+        /* Tooltip for the subscript button */
+        setup_button (builder, "subscript", null, _("Subscript mode [Alt]"));
+        /* Tooltip for the superscript button */
+        setup_button (builder, "superscript", null, _("Superscript mode [Ctrl]"));
+        /* Tooltip for the scientific exponent button */
+        setup_button (builder, "exponential", null, _("Scientific exponent [Ctrl+E]"));
+        /* Tooltip for the add button */
+        setup_button (builder, "add",                "+", _("Add [+]"));
+        /* Tooltip for the subtract button */
+        setup_button (builder, "subtract",           "â", _("Subtract [-]"));
+        /* Tooltip for the multiply button */
+        setup_button (builder, "multiply",           "Ã", _("Multiply [*]"));
+        /* Tooltip for the divide button */
+        setup_button (builder, "divide",             "Ã", _("Divide [/]"));
+        /* Tooltip for the modulus divide button */
+        setup_button (builder, "modulus_divide",     " mod ", _("Modulus divide"));
+        /* Tooltip for the additional functions button */
+        setup_button (builder, "function",           null, _("Additional Functions"));
+        /* Tooltip for the exponent button */
+        setup_button (builder, "x_pow_y",            "^", _("Exponent [^ or **]"));
+        /* Tooltip for the square button */
+        setup_button (builder, "x_squared",          "Â", _("Square [Ctrl+2]"));
+        /* Tooltip for the percentage button */
+        setup_button (builder, "percentage",         "%", _("Percentage [%]"));
+        /* Tooltip for the factorial button */
+        setup_button (builder, "factorial",          "!", _("Factorial [!]"));
+        /* Tooltip for the absolute value button */
+        setup_button (builder, "abs",                "|", _("Absolute value [|]"));
+        /* Tooltip for the complex argument component button */
+        setup_button (builder, "arg",                "Arg ", _("Complex argument"));
+        /* Tooltip for the complex conjugate button */
+        setup_button (builder, "conjugate",          "conj ", _("Complex conjugate"));
+        /* Tooltip for the root button */
+        setup_button (builder, "root",               "â", _("Root [Ctrl+R]"));
+        /* Tooltip for the square root button */
+        setup_button (builder, "square_root",        "â", _("Square root [Ctrl+R]"));
+        /* Tooltip for the logarithm button */
+        setup_button (builder, "logarithm",          "log ", _("Logarithm"));
+        /* Tooltip for the natural logarithm button */
+        setup_button (builder, "natural_logarithm",  "ln ", _("Natural Logarithm"));
+        /* Tooltip for the sine button */
+        setup_button (builder, "sine",               "sin ", _("Sine"));
+        /* Tooltip for the cosine button */
+        setup_button (builder, "cosine",             "cos ", _("Cosine"));
+        /* Tooltip for the tangent button */
+        setup_button (builder, "tangent",            "tan ", _("Tangent"));
+        /* Tooltip for the hyperbolic sine button */
+        setup_button (builder, "hyperbolic_sine",    "sinh ", _("Hyperbolic Sine"));
+        /* Tooltip for the hyperbolic cosine button */
+        setup_button (builder, "hyperbolic_cosine",  "cosh ", _("Hyperbolic Cosine"));
+        /* Tooltip for the hyperbolic tangent button */
+        setup_button (builder, "hyperbolic_tangent", "tanh ", _("Hyperbolic Tangent"));
+        /* Tooltip for the inverse button */
+        setup_button (builder, "inverse",            "âÂ", _("Inverse [Ctrl+I]"));
+        /* Tooltip for the boolean AND button */
+        setup_button (builder, "and",                "â", _("Boolean AND"));
+        /* Tooltip for the boolean OR button */
+        setup_button (builder, "or",                 "â", _("Boolean OR"));
+        /* Tooltip for the exclusive OR button */
+        setup_button (builder, "xor",                "â", _("Boolean Exclusive OR"));
+        /* Tooltip for the boolean NOT button */
+        setup_button (builder, "not",                "Â", _("Boolean NOT"));
+        /* Tooltip for the integer component button */
+        setup_button (builder, "integer_portion",    "int ", _("Integer Component"));
+        /* Tooltip for the fractional component button */
+        setup_button (builder, "fractional_portion", "frac ", _("Fractional Component"));
+        /* Tooltip for the real component button */
+        setup_button (builder, "real_portion",       "Re ", _("Real Component"));
+        /* Tooltip for the imaginary component button */
+        setup_button (builder, "imaginary_portion",  "Im ", _("Imaginary Component"));
+        /* Tooltip for the ones' complement button */
+        setup_button (builder, "ones_complement",    "ones ", _("Ones' Complement"));
+        /* Tooltip for the two's complement button */
+        setup_button (builder, "twos_complement",    "twos ", _("Two's Complement"));
+        /* Tooltip for the truncate button */
+        setup_button (builder, "trunc",              "trunc ", _("Truncate"));
+        /* Tooltip for the start group button */
+        setup_button (builder, "start_group",        "(", _("Start Group [(]"));
+        /* Tooltip for the end group button */
+        setup_button (builder, "end_group",          ")", _("End Group [)]"));
+        /* Tooltip for the memory button */
+        setup_button (builder, "memory", null, _("Memory"));
+        /* Tooltip for the insert character code button */
+        setup_button (builder, "character", null, _("Insert Character Code"));
+        /* Tooltip for the solve button */
+        setup_button (builder, "result", null, _("Calculate Result"));
+        /* Tooltip for the factor button */
+        setup_button (builder, "factor", null, _("Factorize [Ctrl+F]"));
+        /* Tooltip for the clear button */
+        setup_button (builder, "clear", null, _("Clear Display [Escape]"));
+        /* Tooltip for the undo button */
+        setup_button (builder, "undo", null, _("Undo [Ctrl+Z]"));
+        /* Tooltip for the shift left button */
+        setup_button (builder, "shift_left", null, _("Shift Left"));
+        /* Tooltip for the shift right button */
+        setup_button (builder, "shift_right", null, _("Shift Right"));
+        /* Tooltip for the compounding term button */
+        setup_button (builder, "finc_compounding_term", null, _("Compounding Term"));
+        /* Tooltip for the double declining depreciation button */
+        setup_button (builder, "finc_double_declining_depreciation", null, _("Double Declining Depreciation"));
+        /* Tooltip for the future value button */
+        setup_button (builder, "finc_future_value", null, _("Future Value"));
+        /* Tooltip for the financial term button */
+        setup_button (builder, "finc_term", null, _("Financial Term"));
+        /* Tooltip for the sum of the years digits depreciation button */
+        setup_button (builder, "finc_sum_of_the_years_digits_depreciation", null, _("Sum of the Years Digits Depreciation"));
+        /* Tooltip for the straight line depreciation button */
+        setup_button (builder, "finc_straight_line_depreciation", null, _("Straight Line Depreciation"));
+        /* Tooltip for the periodic interest rate button */
+        setup_button (builder, "finc_periodic_interest_rate", null, _("Periodic Interest Rate"));
+        /* Tooltip for the present value button */
+        setup_button (builder, "finc_present_value", null, _("Present Value"));
+        /* Tooltip for the periodic payment button */
+        setup_button (builder, "finc_periodic_payment", null, _("Periodic Payment"));
+        /* Tooltip for the gross profit margin button */
+        setup_button (builder, "finc_gross_profit_margin", null, _("Gross Profit Margin"));
+
+        /* Set special button data */
+        for (var i = 0; i < 16; i++)
+        {
+            var name = "calc_%d_button".printf (i);
+            var button = builder.get_object (name) as Gtk.Button;
+            if (button != null)
+            {
+                button.set_data<int> ("calc_digit", i);
+                button.set_label (equation.get_digit_text (i).to_string ());
+                button.clicked.connect ((widget) => { equation.insert_digit (widget.get_data<int> ("calc_digit")); });
+            }
+        }
+        var button = builder.get_object ("calc_subtract_button") as Gtk.Button;
+        if (button != null)
+            button.clicked.connect (() => { equation.insert_subtract (); });
+        button = builder.get_object ("calc_undo_button") as Gtk.Button;
+        if (button != null)
+            button.clicked.connect (() => { equation.undo (); });
+        button = builder.get_object ("calc_result_button") as Gtk.Button;
+        if (button != null)
+            button.clicked.connect (() => { equation.solve (); });
+        button = builder.get_object ("calc_clear_button") as Gtk.Button;
+        if (button != null)
+            button.clicked.connect (() => { equation.clear (); });
+        button = builder.get_object ("calc_memory_button") as Gtk.Button;
+        if (button != null)
+            button.clicked.connect (memory_cb);
+        button = builder.get_object ("calc_function_button") as Gtk.Button;
+        if (button != null)
+            button.clicked.connect (function_cb);
+        button = builder.get_object ("calc_factor_button") as Gtk.Button;
+        if (button != null)
+            button.clicked.connect (() => { equation.factorize (); });
+        button = builder.get_object ("calc_exponential_button") as Gtk.Button;
+        if (button != null)
+            button.clicked.connect (() => { equation.insert_exponent (); });
+        button = builder.get_object ("calc_shift_left_button") as Gtk.Button;
+        if (button != null)
+            button.clicked.connect (shift_left_cb);
+        button = builder.get_object ("calc_shift_right_button") as Gtk.Button;
+        if (button != null)
+            button.clicked.connect (shift_right_cb);
+        button = builder.get_object ("calc_character_button") as Gtk.Button;
+        if (button != null)
+            button.clicked.connect (insert_character_code_cb);
+        button = builder.get_object ("calc_numeric_point_button") as Gtk.Button;
+        if (button != null)
+        {
+            button.set_label (equation.serializer.get_radix ().to_string ());
+            button.clicked.connect (() => { equation.insert_numeric_point (); });
+        }
+
+        var toggle_button = builder.get_object ("calc_superscript_button") as Gtk.ToggleButton;
+        if (toggle_button != null)
+        {
+            superscript_toggles.append (toggle_button);
+            if (equation.number_mode == NumberMode.SUPERSCRIPT)
+                toggle_button.set_active (true);
+            toggle_button.clicked.connect (set_superscript_cb);
+        }
+        toggle_button = builder.get_object ("calc_subscript_button") as Gtk.ToggleButton;
+        if (toggle_button != null)
+        {
+            subscript_toggles.append (toggle_button);
+            if (equation.number_mode == NumberMode.SUBSCRIPT)
+                toggle_button.set_active (true);
+            toggle_button.clicked.connect (set_subscript_cb);
+        }
+
+        if (mode == ButtonMode.PROGRAMMING)
+        {
+            base_label = builder.get_object ("base_label") as Gtk.Label;
+            character_code_dialog = builder.get_object ("character_code_dialog") as Gtk.Dialog;
+            character_code_dialog.response.connect (character_code_dialog_response_cb);
+            character_code_dialog.delete_event.connect (character_code_dialog_delete_cb);
+            character_code_entry = builder.get_object ("character_code_entry") as Gtk.Entry;
+            character_code_entry.activate.connect (character_code_dialog_activate_cb);
+
+            bit_panel = builder.get_object ("bit_table") as Gtk.Widget;
+            bit_labels = new List<Gtk.Label> ();
+            var i = 0;
+            while (true)
+            {
+                var name = "bit_label_%d".printf (i);
+                var label = builder.get_object (name) as Gtk.Label;
+                if (label == null)
+                    break;
+                bit_labels.append (label);
+                name = "bit_eventbox_%d".printf (i);
+                var box = builder.get_object (name) as Gtk.EventBox;
+                box.set_data<int> ("bit_index", i);
+                box.button_press_event.connect (bit_toggle_cb);
+                i++;
+            }
+            bit_labels.reverse ();
+
+            base_combo = builder.get_object ("base_combo") as Gtk.ComboBox;
+            var model = new Gtk.ListStore (2, typeof (string), typeof (int));
+            base_combo.model = model;
+            Gtk.TreeIter iter;
+            model.append (out iter);
+            model.set (iter, 0,
+                       /* Number display mode combo: Binary, e.g. 10011010010â */
+                       _("Binary"), 1, 2, -1);
+            model.append (out iter);
+            model.set (iter, 0,
+                       /* Number display mode combo: Octal, e.g. 2322â */
+                       _("Octal"), 1, 8, -1);
+            model.append (out iter);
+            model.set (iter, 0,
+                       /* Number display mode combo: Decimal, e.g. 1234 */
+                       _("Decimal"), 1, 10, -1);
+            model.append (out iter);
+            model.set (iter, 0,
+                       /* Number display mode combo: Hexadecimal, e.g. 4D2ââ */
+                       _("Hexadecimal"), 1, 16, -1);
+            var renderer = new Gtk.CellRendererText ();
+            base_combo.pack_start (renderer, true);
+            base_combo.add_attribute (renderer, "text", 0);
+
+            base_combo.changed.connect (base_combobox_changed_cb);
+            equation.notify["number-base"].connect ((pspec) => { base_changed_cb (); } );
+            base_changed_cb ();
+        }
+
+        /* Setup financial functions */
+        if (mode == ButtonMode.FINANCIAL)
+        {
+            load_finc_dialogs ();
+
+            button = builder.get_object ("calc_finc_compounding_term_button") as Gtk.Button;
+            if (button != null)
+            {
+                button.set_data<string> ("finc-dialog-name", "ctrm_dialog");
+                button.clicked.connect (finc_cb);
+            }
+            button = builder.get_object ("calc_finc_double_declining_depreciation_button") as Gtk.Button;
+            if (button != null)
+            {
+                button.set_data<string> ("finc-dialog-name", "ddb_dialog");
+                button.clicked.connect (finc_cb);
+            }
+            button = builder.get_object ("calc_finc_future_value_button") as Gtk.Button;
+            if (button != null)
+            {
+                button.set_data<string> ("finc-dialog-name", "fv_dialog");
+                button.clicked.connect (finc_cb);
+            }
+            button = builder.get_object ("calc_finc_gross_profit_margin_button") as Gtk.Button;
+            if (button != null)
+            {
+                button.set_data<string> ("finc-dialog-name", "gpm_dialog");
+                button.clicked.connect (finc_cb);
+            }
+            button = builder.get_object ("calc_finc_periodic_payment_button") as Gtk.Button;
+            if (button != null)
+            {
+                button.set_data<string> ("finc-dialog-name", "pmt_dialog");
+                button.clicked.connect (finc_cb);
+            }
+            button = builder.get_object ("calc_finc_present_value_button") as Gtk.Button;
+            if (button != null)
+            {
+                button.set_data<string> ("finc-dialog-name", "pv_dialog");
+                button.clicked.connect (finc_cb);
+            }
+            button = builder.get_object ("calc_finc_periodic_interest_rate_button") as Gtk.Button;
+            if (button != null)
+            {
+                button.set_data<string> ("finc-dialog-name", "rate_dialog");
+                button.clicked.connect (finc_cb);
+            }
+            button = builder.get_object ("calc_finc_straight_line_depreciation_button") as Gtk.Button;
+            if (button != null)
+            {
+                button.set_data<string> ("finc-dialog-name", "sln_dialog");
+                button.clicked.connect (finc_cb);
+            }
+            button = builder.get_object ("calc_finc_sum_of_the_years_digits_depreciation_button") as Gtk.Button;
+            if (button != null)
+            {
+                button.set_data<string> ("finc-dialog-name", "syd_dialog");
+                button.clicked.connect (finc_cb);
+            }
+            button = builder.get_object ("calc_finc_term_button") as Gtk.Button;
+            if (button != null)
+            {
+                button.set_data<string> ("finc-dialog-name", "term_dialog");
+                button.clicked.connect (finc_cb);
+            }
+        }
+
+        builder.connect_signals (this);
+
+        update_bit_panel ();
+
+        return panel;
+    }
+
+    private void setup_button (Gtk.Builder builder, string name, string? data, string? tooltip)
+    {
+        var widget_name = "calc_%s_button".printf (name);
+        var button = builder.get_object (widget_name) as Gtk.Button;
+        if (button == null)
+            return;
+
+        if (data != null)
+        {
+            button.set_data<string> ("calc_text", data);
+            button.clicked.connect ((widget) =>  { equation.insert (widget.get_data<string> ("calc_text")); });
+        }
+
+        if (tooltip != null)
+            button.set_tooltip_text (tooltip);
+
+        button.get_accessible ().set_name (name);
+    }
+
+    private void converter_changed_cb ()
+    {
+        Unit from_unit, to_unit;
+        converter.get_conversion (out from_unit, out to_unit);
+        if (mode == ButtonMode.FINANCIAL)
+        {
+            equation.source_currency = from_unit.name;
+            equation.target_currency = to_unit.name;
+        }
+        else
+        {
+            equation.source_units = from_unit.name;
+            equation.target_units = to_unit.name;
+        }
+    }
+
+    private void load_buttons ()
+    {
+        if (!get_visible ())
+            return;
+
+        if (converter == null)
+        {
+            converter = new MathConverter (equation);
+            converter.changed.connect (converter_changed_cb);
+            pack_start (converter, false, true, 0);
+        }
+
+        var panel = load_mode (mode);
+        if (active_panel == panel)
+            return;
+
+        /* Hide old buttons */
+        if (active_panel != null)
+            active_panel.hide ();
+
+        /* Load and display new buttons */
+        active_panel = panel;
+        if (panel != null)
+            panel.show ();
+    }
+
+    public int programming_base
+    {
+        get { return _programming_base; }
+        set
+        {
+            if (_programming_base == value)
+                return;
+
+            _programming_base = value;
+            notify_property ("programming-base");
+
+            if (mode == ButtonMode.PROGRAMMING)
+                equation.number_base = value;
+        }
+    }
+
+    private void popup_button_menu (Gtk.Button button, Gtk.Menu menu)
+    {
+        menu.set_data<Gtk.Widget> ("button", button);
+        menu.popup (null, null, button_menu_position_func, 1, Gtk.get_current_event_time ());
+    }
+
+    private void button_menu_position_func (Gtk.Menu menu, out int x, out int y, out bool push_in)
+    {
+        var button = menu.get_data<Gtk.Button> ("button");
+        int origin_x, origin_y;
+        button.get_window ().get_origin (out origin_x, out origin_y);
+        var border = button.get_border_width ();
+        Gtk.Allocation allocation;
+        button.get_allocation (out allocation);
+        x = (int) (origin_x + allocation.x + border);
+        y = (int) (origin_y + allocation.y + border);
+    }
+
+    private void memory_cb (Gtk.Widget widget)
+    {
+        var popup = new MathVariablePopup (equation);
+        popup.set_transient_for (widget.get_toplevel () as Gtk.Window);
+
+        Gtk.Allocation allocation;
+        widget.get_allocation (out allocation);
+        int x, y;
+        widget.get_window ().get_root_coords (allocation.x, allocation.y, out x, out y);
+        popup.move (x, y);
+        popup.show ();
+    }
+
+    private void shift_left_cb (Gtk.Button button)
+    {
+        if (shift_left_menu == null)
+        {
+            shift_left_menu = new Gtk.Menu ();
+            shift_left_menu.set_reserve_toggle_size (false);
+
+            for (var i = 1; i < 16; i++)
+            {
+                string format;
+                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);
+                }
+                var text = format.printf (i);
+                var label = new Gtk.Label.with_mnemonic (text);
+
+                var item = new Gtk.MenuItem ();
+                item.set_data<int> ("shiftcount", i);
+                item.add (label);
+                shift_left_menu.append (item);
+                item.activate.connect ((widget) => { equation.shift (widget.get_data<int> ("shiftcount")); });
+
+                label.show ();
+                item.show ();
+            }
+        }
+
+        popup_button_menu (button, shift_left_menu);
+    }
+
+    private void shift_right_cb (Gtk.Button button)
+    {
+        if (shift_right_menu == null)
+        {
+            shift_right_menu = new Gtk.Menu ();
+            shift_right_menu.set_reserve_toggle_size (false);
+
+            for (var i = 1; i < 16; i++)
+            {
+                string format;
+                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);
+                }
+                var text = format.printf (i);
+                var label = new Gtk.Label.with_mnemonic (text);
+
+                var item = new Gtk.MenuItem ();
+                item.set_data<int> ("shiftcount", -i);
+                item.add (label);
+                shift_right_menu.append (item);
+                item.activate.connect ((widget) => { equation.shift (widget.get_data<int> ("shiftcount")); });
+
+                label.show ();
+                item.show ();
+            }
+        }
+
+        popup_button_menu (button, shift_right_menu);
+    }
+
+    private void function_cb (Gtk.Button button)
+    {
+        if (function_menu == null)
+        {
+            function_menu = new Gtk.Menu ();
+            function_menu.set_reserve_toggle_size (false);
+
+            /* Tooltip for the integer component button */
+            add_function_menu_item (function_menu, _("Integer Component"), "int ");
+            /* Tooltip for the fractional component button */
+            add_function_menu_item (function_menu, _("Fractional Component"), "frac ");
+            /* Tooltip for the round button */
+            add_function_menu_item (function_menu, _("Round"), "round ");
+            /* Tooltip for the floor button */
+            add_function_menu_item (function_menu, _("Floor"), "floor ");
+            /* Tooltip for the ceiling button */
+            add_function_menu_item (function_menu, _("Ceiling"), "ceil ");
+            /* Tooltip for the ceiling button */
+            add_function_menu_item (function_menu, _("Sign"), "sgn ");
+        }
+
+        popup_button_menu (button, function_menu);
+    }
+
+    private void add_function_menu_item (Gtk.Menu menu, string label, string function)
+    {
+        var item = new Gtk.MenuItem.with_label (label);
+        item.set_data<string> ("function", function);
+        menu.append (item);
+        item.activate.connect ((widget) => { equation.insert (widget.get_data<string> ("function")); });
+        item.show ();
+    }
+
+    private void finc_cb (Gtk.Widget widget)
+    {
+        var name = widget.get_data<string> ("finc-dialog-name");
+        var dialog = financial_ui.get_object (name) as Gtk.Dialog;
+        dialog.run ();
+        dialog.hide ();
+    }
+
+    private void insert_character_code_cb (Gtk.Widget widget)
+    {
+        character_code_dialog.present ();
+    }
+
+    private void finc_activate_cb (Gtk.Widget widget)
+    {
+        var next_entry = widget.get_data<Gtk.Entry> ("next-entry");
+        if (next_entry == null)
+        {
+            var dialog = widget.get_toplevel () as Gtk.Dialog;
+            if (dialog.is_toplevel ())
+            {
+                dialog.response (Gtk.ResponseType.OK);
+                return;
+            }
+        }
+        else
+            next_entry.grab_focus ();
+    }
+
+    private void finc_response_cb (Gtk.Widget widget, int response_id)
+    {
+        if (response_id != Gtk.ResponseType.OK)
+            return;
+
+        var function = (FinancialDialog) widget.get_data<int> ("finc-function");
+        var entries = new string[0];
+        switch (function)
+        {
+        case FinancialDialog.CTRM_DIALOG:
+            entries = ctrm_entries;
+            break;
+        case FinancialDialog.DDB_DIALOG:
+            entries = ddb_entries;
+            break;
+        case FinancialDialog.FV_DIALOG:
+            entries = fv_entries;
+            break;
+        case FinancialDialog.GPM_DIALOG:
+            entries = gpm_entries;
+            break;
+        case FinancialDialog.PMT_DIALOG:
+            entries = pmt_entries;
+            break;
+        case FinancialDialog.PV_DIALOG:
+            entries = pv_entries;
+            break;
+        case FinancialDialog.RATE_DIALOG:
+            entries = rate_entries;
+            break;
+        case FinancialDialog.SLN_DIALOG:
+            entries = sln_entries;
+            break;
+        case FinancialDialog.SYD_DIALOG:
+            entries = syd_entries;
+            break;
+        case FinancialDialog.TERM_DIALOG:
+            entries = term_entries;
+            break;
+        }
+
+        Number arg[4] = { new Number.integer (0), new Number.integer (0), new Number.integer (0), new Number.integer (0) };
+        for (var i = 0; i < entries.length; i++)
+        {
+            var entry = financial_ui.get_object (entries[i]) as Gtk.Entry;
+            arg[i] = mp_set_from_string (entry.get_text ());
+            entry.set_text ("0");
+        }
+        var first_entry = financial_ui.get_object (entries[0]) as Gtk.Entry;
+        first_entry.grab_focus ();
+
+        do_finc_expression (equation, function, arg[0], arg[1], arg[2], arg[3]);
+    }
+
+    private void character_code_dialog_response_cb (Gtk.Widget dialog, int response_id)
+    {
+        var text = character_code_entry.get_text ();
+
+        if (response_id == Gtk.ResponseType.OK)
+        {
+            var x = new Number.integer (0);
+            for (var i = 0; text[i] != '\0'; i++)
+            {
+                x = x.add (new Number.integer (text[i]));
+                x = x.shift (8);
+            }
+
+            equation.insert_number (x);
+        }
+
+        dialog.hide ();
+    }
+
+    private void character_code_dialog_activate_cb (Gtk.Widget entry)
+    {
+        character_code_dialog_response_cb (character_code_dialog, Gtk.ResponseType.OK);
+    }
+
+    private bool character_code_dialog_delete_cb (Gtk.Widget dialog, Gdk.EventAny event)
+    {
+        character_code_dialog_response_cb (dialog, Gtk.ResponseType.CANCEL);
+        return true;
+    }
+
+    private bool bit_toggle_cb (Gtk.Widget event_box, Gdk.EventButton event)
+    {
+        equation.toggle_bit (event_box.get_data<int> ("bit_index"));
+        return true;
+    }
+
+    private void remove_trailing_spaces ()
+    {
+        var insert_mark = equation.get_insert ();
+        Gtk.TextIter start, end;
+        equation.get_iter_at_mark (out end, insert_mark);
+        start = end;
+        while (start.backward_char ())
+        {
+            if (!start.get_char ().isspace ())
+                break;
+            equation.delete (ref start, ref end);
+        }
+    }
+
+    private void set_superscript_cb (Gtk.Button widget)
+    {
+        var button = widget as Gtk.ToggleButton;
+
+        if (button.get_active ())
+        {
+            equation.number_mode = NumberMode.SUPERSCRIPT;
+            if (!equation.has_selection)
+                remove_trailing_spaces ();
+        }
+        else if (equation.number_mode == NumberMode.SUPERSCRIPT)
+            equation.number_mode = NumberMode.NORMAL;
+    }
+
+    private void set_subscript_cb (Gtk.Button widget)
+    {
+        var button = widget as Gtk.ToggleButton;
+
+        if (button.get_active ())
+        {
+            equation.number_mode = NumberMode.SUBSCRIPT;
+            if (!equation.has_selection)
+                remove_trailing_spaces ();
+        }
+        else if (equation.number_mode == NumberMode.SUBSCRIPT)
+            equation.number_mode = NumberMode.NORMAL;
+    }
+
+    private void number_mode_changed_cb ()
+    {
+        var mode = equation.number_mode;
+        foreach (var toggle in superscript_toggles)
+            toggle.set_active (mode == NumberMode.SUPERSCRIPT);
+        foreach (var toggle in subscript_toggles)
+            toggle.set_active (mode == NumberMode.SUBSCRIPT);
+    }
+}
diff --git a/src/math-converter.vala b/src/math-converter.vala
new file mode 100644
index 0000000..15eba6f
--- /dev/null
+++ b/src/math-converter.vala
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2008-2012 Robert Ancell
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+public class MathConverter : Gtk.Box
+{
+    private MathEquation equation;
+
+    private string category;
+
+    private Gtk.ComboBox from_combo;
+    private Gtk.ComboBox to_combo;
+
+    private Gtk.Label result_label;
+    
+    public signal void changed ();
+
+    public MathConverter (MathEquation equation)
+    {
+        Object (orientation: Gtk.Orientation.HORIZONTAL);
+        this.equation = equation;
+
+        spacing = 6;
+
+        var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+        hbox.show ();
+        pack_start (hbox, false, true, 0);
+
+        from_combo = new Gtk.ComboBox ();
+
+        var renderer = new Gtk.CellRendererText ();
+        from_combo.pack_start (renderer, true);
+        from_combo.add_attribute (renderer, "text", 0);
+        from_combo.set_cell_data_func (renderer, from_cell_data_func);
+        from_combo.changed.connect (from_combobox_changed_cb);
+        from_combo.show ();
+        hbox.pack_start (from_combo, false, true, 0);
+
+        var label = new Gtk.Label (/* Label that is displayed between the two conversion combo boxes, e.g. "[degrees] in [radians]" */
+                                   _(" in "));
+        label.show ();
+        hbox.pack_start (label, false, true, 5);
+
+        to_combo = new Gtk.ComboBox ();
+        renderer = new Gtk.CellRendererText ();
+        to_combo.pack_start (renderer, true);
+        to_combo.add_attribute (renderer, "text", 0);
+        to_combo.changed.connect (to_combobox_changed_cb);
+        to_combo.show ();
+        hbox.pack_start (to_combo, false, true, 0);
+
+        var swap_button = new Gtk.Button.with_label ("â");
+        swap_button.set_tooltip_text (/* Tooltip for swap conversion button */
+                                      _("Switch conversion units"));
+        swap_button.set_relief (Gtk.ReliefStyle.NONE);
+        swap_button.clicked.connect (swap_button_clicked_cb);
+        swap_button.show ();
+        hbox.pack_start (swap_button, false, true, 0);
+
+        result_label = new Gtk.Label ("");
+        result_label.set_alignment (1.0f, 0.5f);
+        result_label.sensitive = false;
+        result_label.show ();
+        pack_start (result_label, true, true, 0);
+
+        CurrencyManager.get_default ().updated.connect (() => { update_result_label (); });
+        equation.notify["display"].connect ((pspec) => { update_result_label (); });
+
+        update_from_model ();
+    }
+
+    public void set_category (string? category)
+    {
+        if (this.category == category)
+            return;
+        this.category = category;
+
+        update_from_model ();
+    }
+
+    public string get_category ()
+    {
+        return category;
+    }
+
+    public void set_conversion (/*string category,*/ string unit_a, string unit_b)
+    {
+        var ua = UnitManager.get_default ().get_unit_by_name (unit_a);
+        var ub = UnitManager.get_default ().get_unit_by_name (unit_b);
+        if (ua == null || ub == null)
+        {
+            /* Select the first unit */
+            var model = from_combo.get_model ();
+            Gtk.TreeIter iter;
+            if (model.get_iter_first (out iter))
+            {
+                Gtk.TreeIter child_iter;
+                while (model.iter_children (out child_iter, iter))
+                    iter = child_iter;
+                from_combo.set_active_iter (iter);
+            }
+            return;
+        }
+
+        set_active_unit (from_combo, null, ua);
+        set_active_unit (to_combo, null, ub);
+    }
+
+    public void get_conversion (out Unit from_unit, out Unit to_unit)
+    {
+        Gtk.TreeIter from_iter, to_iter;
+
+        from_combo.get_active_iter (out from_iter);
+        to_combo.get_active_iter (out to_iter);
+
+        from_combo.get_model ().get (from_iter, 2, out from_unit, -1);
+        to_combo.get_model ().get (to_iter, 2, out to_unit, -1);
+    }
+
+    private void update_result_label ()
+    {
+        var x = equation.number;
+        if (x == null)
+            return;
+
+        var z = convert_equation (x);
+        result_label.sensitive = z != null;
+        if (z != null)
+        {
+            Unit source_unit, target_unit;
+            get_conversion (out source_unit, out target_unit);
+
+            var source_text = source_unit.format (x);
+            var target_text = target_unit.format (z);
+            result_label.set_text ("%s = %s".printf (source_text, target_text));
+        }
+    }
+
+    private void update_from_model ()
+    {
+        var from_model = new Gtk.TreeStore (3, typeof (string), typeof (UnitCategory), typeof (Unit));
+
+        if (category == null)
+        {
+            var categories = UnitManager.get_default ().get_categories ();
+            foreach (var category in categories)
+            {
+                Gtk.TreeIter parent;
+                from_model.append (out parent, null);
+                from_model.set (parent, 0, category.display_name, 1, category, -1);
+
+                foreach (var unit in category.get_units ())
+                {
+                    Gtk.TreeIter iter;
+                    from_model.append (out iter, parent);
+                    from_model.set (iter, 0, unit.display_name, 1, category, 2, unit, -1);
+                }
+            }
+        }
+        else
+        {
+            var c = UnitManager.get_default ().get_category (category);
+            foreach (var unit in c.get_units ())
+            {
+                Gtk.TreeIter iter;
+                from_model.append (out iter, null);
+                from_model.set (iter, 0, unit.display_name, 1, c, 2, unit, -1);
+            }
+        }
+
+        from_combo.model = from_model;
+    }
+
+    private bool iter_is_unit (Gtk.TreeModel model, Gtk.TreeIter iter, Unit unit)
+    {
+        Unit u;
+        model.get (iter, 2, out u, -1);
+        return u == unit;
+    }
+
+    private bool set_active_unit (Gtk.ComboBox combo, Gtk.TreeIter? iter, Unit unit)
+    {
+        var model = combo.get_model ();
+        if (iter != null && iter_is_unit (model, iter, unit))
+        {
+            combo.set_active_iter (iter);
+            return true;
+        }
+
+        Gtk.TreeIter child_iter;
+        if (!model.iter_children (out child_iter, iter))
+            return false;
+
+        do
+        {
+            if (set_active_unit (combo, child_iter, unit))
+                return true;
+        } while (model.iter_next (ref child_iter));
+
+        return false;
+    }
+
+    private void from_combobox_changed_cb ()
+    {
+        var model = from_combo.get_model ();
+        Gtk.TreeIter iter;
+        if (!from_combo.get_active_iter (out iter))
+            return;
+        UnitCategory category;
+        Unit unit;
+        model.get (iter, 1, out category, 2, out unit, -1);
+
+        /* Set the to combobox to be the list of units can be converted to */
+        var to_model = new Gtk.ListStore (3, typeof (string), typeof (UnitCategory), typeof (Unit));
+        foreach (var u in category.get_units ())
+        {
+            if (u == unit)
+                continue;
+            to_model.append (out iter);
+            to_model.set (iter, 0, u.display_name, 1, category, 2, u, -1);
+        }
+        to_combo.model = to_model;
+
+        /* Select the first possible unit */
+        to_combo.set_active (0);
+    }
+
+    private void to_combobox_changed_cb ()
+    {
+        /* Conversion must have changed */
+        update_result_label ();
+        changed ();
+    }
+
+    private void from_cell_data_func (Gtk.CellLayout cell_layout, Gtk.CellRenderer cell, Gtk.TreeModel tree_model, Gtk.TreeIter iter)
+    {
+        cell.set ("sensitive", !tree_model.iter_has_child (iter));
+    }
+
+    private void swap_button_clicked_cb ()
+    {
+        var x = equation.number;
+        if (x != null)
+        {
+            var z = convert_equation (x);
+            if (z != null)
+                equation.set_number (z);
+        }
+
+        Unit from_unit, to_unit;
+        get_conversion (out from_unit, out to_unit);
+        set_active_unit (from_combo, null, to_unit);
+        set_active_unit (to_combo, null, from_unit);
+
+        update_result_label ();
+    }
+
+    private Number? convert_equation (Number x)
+    {
+        Gtk.TreeIter from_iter, to_iter;
+        if (!from_combo.get_active_iter (out from_iter))
+            return null;
+        if (!to_combo.get_active_iter (out to_iter))
+            return null;
+
+        UnitCategory category;
+        Unit source_unit, target_unit;
+        from_combo.model.get (from_iter, 1, out category, 2, out source_unit, -1);
+        to_combo.model.get (to_iter, 2, out target_unit, -1);
+
+        return category.convert (x, source_unit, target_unit);
+    }
+}
diff --git a/src/math-display.vala b/src/math-display.vala
new file mode 100644
index 0000000..a466f26
--- /dev/null
+++ b/src/math-display.vala
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2008-2012 Robert Ancell
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+public class MathDisplay : Gtk.Viewport
+{
+    /* Equation being displayed */
+    private MathEquation _equation;
+    public MathEquation equation { get { return _equation; } }
+
+    /* Display widget */
+    Gtk.TextView text_view;
+
+    /* Buffer that shows errors etc */
+    Gtk.TextBuffer info_buffer;
+
+    /* Spinner widget that shows if we're calculating a response */
+    Gtk.Spinner spinner;
+
+    public MathDisplay (MathEquation equation)
+    {
+        _equation = equation;
+
+        var main_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
+        add (main_box);
+
+        text_view = new Gtk.TextView.with_buffer (equation);
+        text_view.set_wrap_mode (Gtk.WrapMode.WORD);
+        text_view.set_accepts_tab (false);
+        text_view.set_pixels_above_lines (8);
+        text_view.set_pixels_below_lines (2);
+        /* TEMP: Disabled for now as GTK+ doesn't properly render a right aligned right margin, see bug #482688 */
+        /*text_view.set_right_margin (6);*/
+        text_view.set_justification (Gtk.Justification.RIGHT);
+        text_view.ensure_style ();
+        var font_desc = text_view.get_style ().font_desc.copy ();
+        font_desc.set_size (16 * Pango.SCALE);
+        text_view.modify_font (font_desc);
+        text_view.set_name ("displayitem");
+        text_view.get_accessible ().set_role (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>
+        text_view.key_press_event.connect (key_press_cb);
+
+        main_box.pack_start (text_view, true, true, 0);
+
+        var info_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
+        main_box.pack_start (info_box, false, true, 0);
+
+        var info_view = new Gtk.TextView ();
+        info_view.set_wrap_mode (Gtk.WrapMode.WORD);
+        info_view.set_can_focus (true); // FIXME: This should be false but it locks the cursor inside the main view for some reason
+        info_view.set_cursor_visible (false); // FIXME: Just here so when incorrectly gets focus doesn't look editable
+        info_view.set_editable (false);
+        info_view.set_justification (Gtk.Justification.RIGHT);
+        /* TEMP: Disabled for now as GTK+ doesn't properly render a right aligned right margin, see bug #482688 */
+        /*info_view.set_right_margin (6);*/
+        info_box.pack_start (info_view, true, true, 0);
+        info_buffer = info_view.get_buffer ();
+
+        spinner = new Gtk.Spinner ();
+        info_box.pack_end (spinner, false, false, 0);
+        style = info_view.get_style ();
+        for (var i = 0; i < 5; i++)
+            modify_bg (i, style.base[i]);
+
+        info_box.show ();
+        info_view.show ();
+        text_view.show ();
+        main_box.show ();
+
+        equation.notify["status"].connect ((pspec) => { status_changed_cb (); });
+        status_changed_cb ();
+
+        equation.notify["error-token-end"].connect ((pspec) => { error_status_changed_cb (); });
+    }
+
+    protected override bool key_press_event (Gdk.EventKey event)
+    {
+        return text_view.key_press_event (event);
+    }
+
+    private bool key_press_cb (Gdk.EventKey event)
+    {
+        /* Treat keypad keys as numbers even when numlock is off */
+        uint new_keyval = 0;
+        switch (event.keyval)
+        {
+        case Gdk.Key.KP_Insert:
+            new_keyval = Gdk Key  0;
+            break;
+        case Gdk.Key.KP_End:
+            new_keyval = Gdk Key  1;
+            break;
+        case Gdk.Key.KP_Down:
+            new_keyval = Gdk Key  2;
+            break;
+        case Gdk.Key.KP_Page_Down:
+            new_keyval = Gdk Key  3;
+            break;
+        case Gdk.Key.KP_Left:
+            new_keyval = Gdk Key  4;
+            break;
+        case Gdk.Key.KP_Begin: /* This is apparently what "5" does when numlock is off. */
+            new_keyval = Gdk Key  5;
+            break;
+        case Gdk.Key.KP_Right:
+            new_keyval = Gdk Key  6;
+            break;
+        case Gdk.Key.KP_Home:
+            new_keyval = Gdk Key  7;
+            break;
+        case Gdk.Key.KP_Up:
+            new_keyval = Gdk Key  8;
+            break;
+        case Gdk.Key.KP_Page_Up:
+            new_keyval = Gdk Key  9;
+            break;
+        }
+
+        if (new_keyval != 0)
+        {
+            var new_event = event; // FIXME: Does this copy?
+            new_event.keyval = new_keyval;
+            return key_press_event (new_event);
+        }
+
+        var state = event.state & (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.MOD1_MASK);
+        var c = Gdk.keyval_to_unicode (event.keyval);
+
+        /* Solve on enter */
+        if (event.keyval == Gdk.Key.Return || event.keyval == Gdk.Key.KP_Enter)
+        {
+            equation.solve ();
+            return true;
+        }
+
+        /* Clear on escape */
+        if ((event.keyval == Gdk.Key.Escape && state == 0) ||
+            (event.keyval == Gdk.Key.BackSpace && state == Gdk.ModifierType.CONTROL_MASK) ||
+            (event.keyval == Gdk.Key.Delete && state == Gdk.ModifierType.SHIFT_MASK))
+        {
+            equation.clear ();
+            return true;
+        }
+
+        /* Numeric keypad will often insert '.' regardless of locale */
+        if (event.keyval == Gdk.Key.KP_Decimal)
+        {
+            equation.insert_numeric_point ();
+            return true;
+        }
+
+        /* Substitute */
+        if (state == 0)
+        {
+            if (c == '*')
+            {
+                equation.insert ("Ã");
+                return true;
+            }
+            if (c == '/')
+            {
+                equation.insert ("Ã");
+                return true;
+            }
+            if (c == '-')
+            {
+                equation.insert_subtract ();
+                return true;
+            }
+        }
+
+        /* Shortcuts */
+        if (state == Gdk.ModifierType.CONTROL_MASK)
+        {
+            switch (event.keyval)
+            {
+            case Gdk.Key.bracketleft:
+                equation.insert ("â");
+                return true;
+            case Gdk.Key.bracketright:
+                equation.insert ("â");
+                return true;
+            case Gdk.Key.e:
+                equation.insert_exponent ();
+                return true;
+            case Gdk.Key.f:
+                equation.factorize ();
+                return true;
+            case Gdk.Key.i:
+                equation.insert ("âÂ");
+                return true;
+            case Gdk.Key.p:
+                equation.insert ("Ï");
+                return true;
+            case Gdk.Key.r:
+                equation.insert ("â");
+                return true;
+            case Gdk.Key.u:
+                equation.insert ("Â");
+                return true;
+            case Gdk.Key.minus:
+                 equation.insert ("â");
+                 return true;
+            case Gdk.Key.apostrophe:
+                 equation.insert ("Â");
+                 return true;
+            }
+        }
+        if (state == Gdk.ModifierType.MOD1_MASK)
+        {
+            switch (event.keyval)
+            {
+            case Gdk.Key.bracketleft:
+                equation.insert ("â");
+                return true;
+            case Gdk.Key.bracketright:
+                equation.insert ("â");
+                return true;
+            }
+        }
+
+        if (state == Gdk.ModifierType.CONTROL_MASK || equation.number_mode == NumberMode.SUPERSCRIPT)
+        {
+            switch (event.keyval)
+            {
+            case Gdk Key  0:
+            case Gdk.Key.KP_0:
+                equation.insert ("â");
+                return true;
+            case Gdk Key  1:
+            case Gdk.Key.KP_1:
+                equation.insert ("Â");
+                return true;
+            case Gdk Key  2:
+            case Gdk.Key.KP_2:
+                equation.insert ("Â");
+                return true;
+            case Gdk Key  3:
+            case Gdk.Key.KP_3:
+                equation.insert ("Â");
+                return true;
+            case Gdk Key  4:
+            case Gdk.Key.KP_4:
+                equation.insert ("â");
+                return true;
+            case Gdk Key  5:
+            case Gdk.Key.KP_5:
+                equation.insert ("â");
+                return true;
+            case Gdk Key  6:
+            case Gdk.Key.KP_6:
+                equation.insert ("â");
+                return true;
+            case Gdk Key  7:
+            case Gdk.Key.KP_7:
+                equation.insert ("â");
+                return true;
+            case Gdk Key  8:
+            case Gdk.Key.KP_8:
+                equation.insert ("â");
+                return true;
+            case Gdk Key  9:
+            case Gdk.Key.KP_9:
+                equation.insert ("â");
+                return true;
+            }
+        }
+        else if (state == Gdk.ModifierType.MOD1_MASK || equation.number_mode == NumberMode.SUBSCRIPT)
+        {
+            switch (event.keyval)
+            {
+            case Gdk Key  0:
+            case Gdk.Key.KP_0:
+                equation.insert ("â");
+                return true;
+            case Gdk Key  1:
+            case Gdk.Key.KP_1:
+                equation.insert ("â");
+                return true;
+            case Gdk Key  2:
+            case Gdk.Key.KP_2:
+                equation.insert ("â");
+                return true;
+            case Gdk Key  3:
+            case Gdk.Key.KP_3:
+                equation.insert ("â");
+                return true;
+            case Gdk Key  4:
+            case Gdk.Key.KP_4:
+                equation.insert ("â");
+                return true;
+            case Gdk Key  5:
+            case Gdk.Key.KP_5:
+                equation.insert ("â");
+                return true;
+            case Gdk Key  6:
+            case Gdk.Key.KP_6:
+                equation.insert ("â");
+                return true;
+            case Gdk Key  7:
+            case Gdk.Key.KP_7:
+                equation.insert ("â");
+                return true;
+            case Gdk Key  8:
+            case Gdk.Key.KP_8:
+                equation.insert ("â");
+                return true;
+            case Gdk Key  9:
+            case Gdk.Key.KP_9:
+                equation.insert ("â");
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private void status_changed_cb ()
+    {
+        info_buffer.set_text (equation.status, -1);
+        if (equation.in_solve && !spinner.get_visible ())
+        {
+            spinner.show ();
+            spinner.start ();
+        }
+        else if (!equation.in_solve && spinner.get_visible ())
+        {
+            spinner.hide ();
+            spinner.stop ();
+        }
+    }
+
+    private void error_status_changed_cb ()
+    {
+        /* If both start and end location of error token are the same, no need to select anything. */
+        if (equation.error_token_end - equation.error_token_start == 0)
+            return;
+
+        Gtk.TextIter start, end;
+        equation.get_start_iter (out start);
+        equation.get_start_iter (out end);
+
+        start.set_offset ((int) equation.error_token_start);
+        end.set_offset ((int) equation.error_token_end);
+
+        equation.select_range (start, end);
+    }
+}
diff --git a/src/math-equation.vala b/src/math-equation.vala
new file mode 100644
index 0000000..5163909
--- /dev/null
+++ b/src/math-equation.vala
@@ -0,0 +1,1251 @@
+/*
+ * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (C) 2008-2012 Robert Ancell
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+public enum NumberMode
+{
+    NORMAL,
+    SUPERSCRIPT,
+    SUBSCRIPT
+}
+
+/* Expression mode state */
+private class MathEquationState
+{
+    public Number ans;             /* Previously calculated answer */
+    public string expression;      /* Expression entered by user */
+    public int ans_start;          /* Start character for ans variable in expression */
+    public int ans_end;            /* End character for ans variable in expression */
+    public int cursor;             /* ??? */
+    public NumberMode number_mode; /* ??? */
+    public bool can_super_minus;   /* true if entering minus can generate a superscript minus */
+    public bool entered_multiply;  /* Last insert was a multiply character */
+    public string status;          /* Equation status */
+    public uint error_token_start; /* Start offset of error token */
+    public uint error_token_end;   /* End offset of error token */
+}
+
+private class SolveData
+{
+    public Number? number_result;
+    public string text_result;
+    public string error;
+    public uint error_start;
+    public uint error_end;
+}
+
+public class MathEquation : Gtk.TextBuffer
+{
+    private Gtk.TextTag ans_tag;
+
+    /* Word size in bits */   
+    private int _word_size;
+    public int word_size
+    {
+        get { return _word_size; }
+        set
+        {
+            if (_word_size == value)
+                return;
+            _word_size = value;
+            notify_property ("word-size");
+        }
+    }
+
+    private string _source_currency;
+    public string source_currency
+    {
+        owned get { return _source_currency; }
+        set
+        {
+            if (_source_currency == value)
+                return;
+            _source_currency = value;
+        }
+    }
+
+    private string _target_currency;
+    public string target_currency
+    {
+        owned get { return _target_currency; }
+        set
+        {
+            if (_target_currency == value)
+                return;
+            _target_currency = value;
+        }
+    }
+
+    private string _source_units;
+    public string source_units
+    {
+        owned get { return _source_units; }
+        set
+        {
+            if (_source_units == value)
+                return;
+            _source_units = value;
+        }
+    }
+
+    private string _target_units;
+    public string target_units
+    {
+        owned get { return _target_units; }
+        set
+        {
+            if (_target_units == value)
+                return;
+            _target_units = value;
+        }
+    }
+
+    public string display
+    {
+        owned get
+        {
+            Gtk.TextIter start, end;
+            get_bounds (out start, out end);
+            return get_text (start, end, false);
+        }
+    }
+
+    private AngleUnit _angle_units;  /* Units for trigonometric functions */
+    private NumberMode _number_mode;   /* ??? */
+    private bool can_super_minus; /* true if entering minus can generate a superscript minus */
+
+    private unichar digits[16];      /* Localized digits */
+
+    private Gtk.TextMark? ans_start_mark = null;
+    private Gtk.TextMark? ans_end_mark = null;
+
+    private MathEquationState state;  /* Equation state */
+    private List<MathEquationState> undo_stack; /* History of expression mode states */
+    private List<MathEquationState> redo_stack;
+    private bool in_undo_operation;
+
+    private bool in_reformat;
+
+    private bool in_delete;
+
+    private bool _in_solve;
+    public bool in_solve
+    {
+        get { return _in_solve; }
+    }
+
+    private MathVariables _variables;
+    public MathVariables variables
+    {
+        get { return _variables; }
+    }
+
+    private Serializer _serializer;
+    public Serializer serializer
+    {
+        get { return _serializer; }
+    }
+
+    private AsyncQueue<SolveData> queue;
+
+    public MathEquation ()
+    {
+        undo_stack = new List<MathEquationState> ();
+        redo_stack = new List<MathEquationState> ();
+
+        /* Default to using untranslated digits, this is because it doesn't make sense in most languages and we need to make this optional.
+         * See https://bugzilla.gnome.org/show_bug.cgi?id=632661 */
+        var use_default_digits = true;
+
+        const unichar default_digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+        /* Digits localized for the given language */
+        var ds = _("0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F").split (",", -1);
+        for (var i = 0; i < 16; i++)
+        {
+            if (use_default_digits || ds[i] == null)
+            {
+                use_default_digits = true;
+                digits[i] = default_digits[i];
+            }
+            else
+                digits[i] = ds[i].get_char (0);
+        }
+
+        _variables = new MathVariables ();
+
+        state = new MathEquationState ();
+        state.status = "";
+        word_size = 32;
+        _angle_units = AngleUnit.DEGREES;
+        // FIXME: Pick based on locale
+        source_currency = "";
+        target_currency = "";
+        source_units = "";
+        target_units = "";
+        _serializer = new Serializer (DisplayFormat.AUTOMATIC, 10, 9);
+        queue = new AsyncQueue<SolveData> ();
+
+        state.ans = new Number.integer (0);
+
+        ans_tag = create_tag (null, "weight", Pango.Weight.BOLD, null);
+    }
+
+    private void get_ans_offsets (out int start, out int end)
+    {
+        if (ans_start_mark == null)
+        {
+            start = -1;
+            end = -1;
+            return;
+        }
+
+        Gtk.TextIter iter;
+        get_iter_at_mark (out iter, ans_start_mark);
+        start = iter.get_offset ();
+        get_iter_at_mark (out iter, ans_end_mark);
+        end = iter.get_offset ();
+    }
+
+    private void reformat_ans ()
+    {
+        if (ans_start_mark == null)
+            return;
+
+        Gtk.TextIter ans_start, ans_end;
+        get_iter_at_mark (out ans_start, ans_start_mark);
+        get_iter_at_mark (out ans_end, ans_end_mark);
+
+        var orig_ans_text = get_text (ans_start, ans_end, false);
+        var ans_text = serializer.to_string (state.ans);
+        if (orig_ans_text != ans_text)
+        {
+            in_undo_operation = true;
+            in_reformat = true;
+
+            var start = ans_start.get_offset ();
+            @delete (ref ans_start, ref ans_end);
+            insert_with_tags (ans_end, ans_text, -1, ans_tag);
+
+            /* There seems to be a bug in the marks as they alternate being the correct and incorrect ways.  Reset them */
+            get_iter_at_offset (out ans_start, start);
+            get_iter_at_offset (out ans_end, start + ans_text.length);
+            move_mark (ans_start_mark, ans_start);
+            move_mark (ans_end_mark, ans_end);
+
+            in_reformat = false;
+            in_undo_operation = false;
+        }
+        get_iter_at_mark (out ans_start, ans_start_mark);
+        get_iter_at_mark (out ans_end, ans_end_mark);
+    }
+
+    private void reformat_separators ()
+    {
+        var in_number = false;
+        var in_radix = false;
+        var last_is_tsep = false;
+        var digit_offset = 0;
+
+        in_undo_operation = true;
+        in_reformat = true;
+
+        var text = display;
+        int ans_start, ans_end;
+        get_ans_offsets (out ans_start, out ans_end);
+        var offset = -1;
+        var index = 0;
+        unichar c;
+        while (text.get_next_char (ref index, out c))
+        {
+            offset++;
+
+            var expect_tsep = number_base == 10 &&
+                              serializer.get_show_thousands_separators () &&
+                              in_number && !in_radix && !last_is_tsep &&
+                              digit_offset > 0 && digit_offset % serializer.get_thousands_separator_count () == 0;
+            last_is_tsep = false;
+
+            /* Don't mess with ans */
+            if (offset >= ans_start && offset <= ans_end)
+            {
+                in_number = in_radix = false;
+                continue;
+            }
+            if (c.isdigit ())
+            {
+                if (!in_number)
+                    digit_offset = count_digits (text, index) + 1;
+                in_number = true;
+
+                /* Expected a thousands separator between these digits - insert it */
+                if (expect_tsep)
+                {
+                    Gtk.TextIter iter;
+                    get_iter_at_offset (out iter, offset);
+                    (this as Gtk.TextBuffer).insert (ref iter, serializer.get_thousands_separator ().to_string (), -1);
+                    offset++;
+                    last_is_tsep = true;
+                }
+
+                digit_offset--;
+            }
+            else if (c == serializer.get_radix ())
+            {
+                in_number = true;
+                in_radix = true;
+            }
+            else if (c == serializer.get_thousands_separator ())
+            {
+                /* Didn't expect thousands separator - delete it */
+                if (!expect_tsep && in_number)
+                {
+                    Gtk.TextIter start, end;
+                    get_iter_at_offset (out start, offset);
+                    get_iter_at_offset (out end, offset + 1);
+                    @delete (ref start, ref end);
+                    offset--;
+                }
+                else
+                    last_is_tsep = true;
+            }
+            else
+            {
+                in_number = false;
+                in_radix = false;
+            }
+        }
+
+        in_reformat = false;
+        in_undo_operation = false;
+    }
+
+    private int count_digits (string text, int index)
+    {
+        var count = 0;
+        var following_separator = false;
+        unichar c;
+        while (text.get_next_char (ref index, out c))
+        {
+            /* Allow a thousands separator between digits follow a digit */
+            if (c == serializer.get_thousands_separator ())
+            {
+                if (following_separator)
+                    return count;
+                following_separator = true;
+            }
+            else if (c.isdigit ())
+            {
+                following_separator = false;
+                count++;
+            }
+            else
+                return count;
+        }
+
+        return count;
+    }
+
+    private void reformat_display ()
+    {
+        /* Change ans */
+        reformat_ans ();
+
+        /* Add/remove thousands separators */
+        reformat_separators ();
+    }
+
+    private MathEquationState get_current_state ()
+    {
+        int ans_start = -1, ans_end = -1;
+
+        if (ans_start_mark != null)
+        {
+            Gtk.TextIter iter;
+            get_iter_at_mark (out iter, ans_start_mark);
+            ans_start = iter.get_offset ();
+            get_iter_at_mark (out iter, ans_end_mark);
+            ans_end = iter.get_offset ();
+        }
+
+        var s = new MathEquationState ();
+        s.ans = state.ans;
+        s.expression = display;
+        s.ans_start = ans_start;
+        s.ans_end = ans_end;
+        get ("cursor-position", out s.cursor, null);
+        s.number_mode = number_mode;
+        s.can_super_minus = can_super_minus;
+        s.entered_multiply = state.entered_multiply;
+        s.status = state.status;
+
+        return s;
+    }
+
+    private void push_undo_stack ()
+    {
+        if (in_undo_operation)
+            return;
+
+        status = "";
+
+        /* Can't redo anymore */
+        redo_stack = new List<MathEquationState> ();
+
+        state = get_current_state ();
+        undo_stack.prepend (state);
+    }
+
+    private void clear_ans (bool do_remove_tag)
+    {
+        if (ans_start_mark == null)
+            return;
+
+        if (do_remove_tag)
+        {
+            Gtk.TextIter start, end;
+            get_iter_at_mark (out start, ans_start_mark);
+            get_iter_at_mark (out end, ans_end_mark);
+            remove_tag (ans_tag, start, end);
+        }
+
+        delete_mark (ans_start_mark);
+        delete_mark (ans_end_mark);
+        ans_start_mark = null;
+        ans_end_mark = null;
+    }
+
+    private void apply_state (MathEquationState s)
+    {
+        /* Disable undo detection */
+        in_undo_operation = true;
+
+        state.ans = s.ans;
+        set_text (s.expression, -1);
+        Gtk.TextIter cursor;
+        get_iter_at_offset (out cursor, s.cursor);
+        place_cursor (cursor);
+        clear_ans (false);
+        if (s.ans_start >= 0)
+        {
+            Gtk.TextIter start;
+            get_iter_at_offset (out start, s.ans_start);
+            ans_start_mark = create_mark (null, start, false);
+            Gtk.TextIter end;
+            get_iter_at_offset (out end, s.ans_end);
+            ans_end_mark = create_mark (null, end, true);
+            apply_tag (ans_tag, start, end);
+        }
+
+        number_mode = s.number_mode;
+        can_super_minus = s.can_super_minus;
+        state.entered_multiply = s.entered_multiply;
+        status = s.status;
+
+        in_undo_operation = false;
+    }
+
+    public void copy ()
+    {
+        Gtk.TextIter start, end;
+        if (!get_selection_bounds (out start, out end))
+            get_bounds (out start, out end);
+
+        var text = get_text (start, end, false);
+        Gtk.Clipboard.get (Gdk.Atom.NONE).set_text (text, -1);
+    }
+
+    public void paste ()
+    {
+        Gtk.Clipboard.get (Gdk.Atom.NONE).request_text (on_paste);
+    }
+
+    private void on_paste (Gtk.Clipboard clipboard, string? text)
+    {
+        if (text != null)
+            insert (text);
+    }
+
+    public void undo ()
+    {
+        if (undo_stack == null)
+        {
+            /* Error shown when trying to undo with no undo history */
+            status = _("No undo history");
+            return;
+        }
+
+        state = undo_stack.nth_data (0);
+        undo_stack.remove (state);
+        redo_stack.prepend (get_current_state ());
+
+        apply_state (state);
+    }
+
+    public void redo ()
+    {
+        if (redo_stack == null)
+        {
+            /* Error shown when trying to redo with no redo history */
+            status = _("No redo history");
+            return;
+        }
+
+        state = redo_stack.nth_data (0);
+        redo_stack.remove (state);
+        undo_stack.prepend (get_current_state ());
+
+        apply_state (state);
+    }
+
+    public unichar get_digit_text (uint digit)
+    {
+        if (digit >= 16)
+            return '?';
+        return digits[digit];
+    }
+
+    public int accuracy
+    {
+        get { return serializer.get_trailing_digits (); }
+        set
+        {
+            if (serializer.get_trailing_digits () == value)
+                return;
+            serializer.set_trailing_digits (value);
+            reformat_display ();
+            notify_property ("accuracy");
+        }
+    }
+
+    public bool show_thousands_separators
+    {
+        get { return serializer.get_show_thousands_separators (); }
+        set
+        {
+            if (serializer.get_show_thousands_separators () == value)
+                return;
+
+            serializer.set_show_thousands_separators (value);
+            reformat_display ();
+            notify_property ("show-thousands-separators");
+        }
+    }
+
+    public bool show_trailing_zeroes
+    {
+        get { return serializer.get_show_trailing_zeroes (); }
+        set
+        {
+            if (serializer.get_show_trailing_zeroes () == value)
+                return;
+
+            serializer.set_show_trailing_zeroes (value);
+            reformat_display ();
+            notify_property ("show-trailing-zeroes");
+        }
+    }
+
+    public DisplayFormat number_format
+    {
+        get { return serializer.get_number_format (); }
+        set
+        {
+            if (serializer.get_number_format () == value)
+                return;
+
+            serializer.set_number_format (value);
+            reformat_display ();
+            notify_property ("number-format");
+        }
+    }
+
+    public int number_base
+    {
+        get { return serializer.get_base (); }
+        set
+        {
+            if (serializer.get_base () == value)
+                return;
+
+            serializer.set_base (value);
+            reformat_display ();
+            notify_property ("number-base");
+        }
+    }
+
+    public AngleUnit angle_units
+    {
+        get { return _angle_units; }
+        set
+        {
+            if (_angle_units == value)
+                return;
+
+            _angle_units = value;
+            notify_property ("angle-units");
+        }
+    }
+
+    public string status
+    {
+        owned get { return state.status; }
+        set
+        {
+            if (state.status == value)
+                return;
+
+            state.status = value;
+            notify_property ("status");
+        }
+    }
+
+    public uint error_token_start
+    {
+        get
+        {
+            /* Check if the previous answer is before start of error token.
+             * If so, subtract 3 (the length of string "ans") and add actual answer length (ans_end - ans_start) into it. */
+            int ans_start, ans_end;
+            get_ans_offsets (out ans_start, out ans_end);
+            if (ans_start != -1 && ans_start < state.error_token_start)
+                return state.error_token_start + ans_end - ans_start - 3;
+
+            return state.error_token_start;
+        }
+    }
+
+    public uint error_token_end
+    {
+        get
+        {
+            /* Check if the previous answer is before end of error token.
+             * If so, subtract 3 (the length of string "ans") and add actual answer length (ans_end - ans_start) into it. */
+            int ans_start, ans_end;
+            get_ans_offsets (out ans_start, out ans_end);
+            if (ans_start != -1 && ans_start < state.error_token_end)
+                return state.error_token_end + ans_end - ans_start - 3;
+
+            return state.error_token_end;
+        }
+    }
+
+    public bool is_empty
+    {
+        get { return get_char_count () == 0; }
+    }
+
+    public bool is_result
+    {
+        get { return equation == "ans"; }
+    }
+
+    public string equation
+    {
+        owned get
+        {
+            var text = display;
+            var eq_text = "";
+
+            var ans_start = -1, ans_end = -1;
+            if (ans_start_mark != null)
+                get_ans_offsets (out ans_start, out ans_end);
+            if (ans_start >= 0)
+                text = text.splice (text.index_of_nth_char (ans_start), text.index_of_nth_char (ans_end), "ans");
+
+            var last_is_digit = false;
+            var index = 0;
+            unichar c;
+            while (text.get_next_char (ref index, out c))
+            {
+                var is_digit = c.isdigit ();
+                var next_is_digit = false;
+                unichar next_char;
+                var i = index;
+                if (text.get_next_char (ref i, out next_char))
+                    next_is_digit = next_char.isdigit ();
+
+                /* Ignore thousands separators */
+                if (c == serializer.get_thousands_separator () && last_is_digit && next_is_digit)
+                    ;
+                /* Substitute radix character */
+                else if (c == serializer.get_radix () && (last_is_digit || next_is_digit))
+                    eq_text += ".";
+                else
+                    eq_text += c.to_string ();
+
+                last_is_digit = is_digit;
+            }
+
+            return eq_text;
+        }
+    }
+
+    public Number? number
+    {
+        owned get
+        {
+            if (is_result)
+                return answer;
+            else
+                return serializer.from_string (equation);
+        }
+    }
+
+    public NumberMode number_mode
+    {
+        get { return _number_mode; }
+        set
+        {
+            if (_number_mode == value)
+                return;
+
+            can_super_minus = value == NumberMode.SUPERSCRIPT;
+
+            _number_mode = value;
+            notify_property ("number-mode");
+        }
+    }
+
+    public Number answer
+    {
+        get { return state.ans; }
+    }
+
+    public void store (string name)
+    {
+        var t = number;
+        if (t == null)
+            status = _("No sane value to store");
+        else
+            variables.set (name, t);
+    }
+
+    public void recall (string name)
+    {
+        insert (name);
+    }
+
+    public new void set (string text)
+    {
+        set_text (text, -1);
+        clear_ans (false);
+    }
+
+    public void set_number (Number x)
+    {
+        /* Show the number in the user chosen format */
+        var text = serializer.to_string (x);
+        set_text (text, -1);
+        state.ans = x;
+
+        /* Mark this text as the answer variable */
+        Gtk.TextIter start, end;
+        get_bounds (out start, out end);
+        clear_ans (false);
+        ans_start_mark = create_mark (null, start, false);
+        ans_end_mark = create_mark (null, end, true);
+        apply_tag (ans_tag, start, end);
+    }
+
+    public new void insert (string text)
+    {
+        /* Replace ** with ^ (not on all keyboards) */
+        if (!has_selection && text == "Ã" && state.entered_multiply)
+        {
+            Gtk.TextIter iter;
+            get_iter_at_mark (out iter, get_insert ());
+            (this as Gtk.TextBuffer).backspace (iter, true, true);
+            insert_at_cursor ("^", -1);
+            return;
+        }
+
+        /* Can't enter superscript minus after entering digits */
+        if ("âÂÂÂââââââ".index_of (text) >= 0 || text == "â")
+            can_super_minus = false;
+
+        /* Disable super/subscript mode when finished entering */
+        if ("ââÂÂÂââââââââââââââââ".index_of (text) < 0)
+            number_mode = NumberMode.NORMAL;
+
+        delete_selection (false, false);
+        insert_at_cursor (text, -1);
+    }
+
+    public void insert_digit (uint digit)
+    {
+        const unichar subscript_digits[] = {'â', 'â', 'â', 'â', 'â', 'â', 'â', 'â', 'â', 'â'};
+        const unichar superscript_digits[] = {'â', 'Â', 'Â', 'Â', 'â', 'â', 'â', 'â', 'â', 'â'};
+
+        if (digit >= 16)
+            return;
+
+        if (number_mode == NumberMode.NORMAL || digit >= 10)
+            insert (get_digit_text (digit).to_string ());
+        else if (number_mode == NumberMode.SUPERSCRIPT)
+            insert (superscript_digits[digit].to_string ());
+        else if (number_mode == NumberMode.SUBSCRIPT)
+            insert (subscript_digits[digit].to_string ());
+    }
+
+    public void insert_numeric_point ()
+    {
+        insert (serializer.get_radix ().to_string ());
+    }
+
+    public void insert_number (Number x)
+    {
+        insert (serializer.to_string (x));
+    }
+
+    public void insert_exponent ()
+    {
+        insert ("Ã10");
+        number_mode = NumberMode.SUPERSCRIPT;
+    }
+
+    public void insert_subtract ()
+    {
+        if (number_mode == NumberMode.SUPERSCRIPT && can_super_minus)
+        {
+            insert ("â");
+            can_super_minus = false;
+        }
+        else
+        {
+            insert ("â");
+            number_mode = NumberMode.NORMAL;
+        }
+    }
+
+    private Number? parse (string text, out ErrorCode error_code = null, out string error_token = null, out uint error_start, out uint error_end)
+    {
+        var equation = new MEquation (this, text);
+        equation.base = serializer.get_base ();
+        equation.wordlen = word_size;
+        equation.angle_units = angle_units;
+
+        return equation.parse (out error_code, out error_token, out error_start, out error_end);
+    }
+
+    /*
+     * Executed in separate thread. It is thus not a good idea to write to anything
+     * in MathEquation but the async queue from here.
+     */
+    private void* solve_real ()
+    {
+        var solvedata = new SolveData ();
+
+        var text = equation;
+        /* Count the number of brackets and automatically add missing closing brackets */
+        var n_brackets = 0;
+        for (var i = 0; text[i] != '\0'; i++)
+        {
+            if (text[i] == '(')
+                n_brackets++;
+            else if (text[i] == ')')
+                n_brackets--;
+        }
+        while (n_brackets > 0)
+        {
+            text += ")";
+            n_brackets--;
+        }
+
+        ErrorCode error_code;
+        string error_token;
+        uint error_start, error_end;
+        var z = parse (text, out error_code, out error_token, out error_start, out error_end);
+        switch (error_code)
+        {
+            case ErrorCode.NONE:
+                solvedata.number_result = z;
+                break;
+
+            case ErrorCode.OVERFLOW:
+                solvedata.error = /* 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 ErrorCode.UNKNOWN_VARIABLE:
+                solvedata.error = /* Error displayed to user when they an unknown variable is entered */
+                                  _("Unknown variable '%s'").printf (error_token);
+                solvedata.error_start = error_start;
+                solvedata.error_end = error_end;
+                break;
+
+            case ErrorCode.UNKNOWN_FUNCTION:
+                solvedata.error = /* Error displayed to user when an unknown function is entered */
+                                  _("Function '%s' is not defined").printf (error_token);
+                solvedata.error_start = error_start;
+                solvedata.error_end = error_end;
+                break;
+
+            case ErrorCode.UNKNOWN_CONVERSION:
+                solvedata.error = /* Error displayed to user when an conversion with unknown units is attempted */
+                                  _("Unknown conversion");
+                break;
+
+            case ErrorCode.MP:
+                if (mp_get_error () != null)
+                    solvedata.error = mp_get_error ();
+                else if (error_token != null) /* Uncategorized error. Show error token to user */
+                {
+                    solvedata.error = _("Malformed expression at token '%s'").printf (error_token);
+                    solvedata.error_start = error_start;
+                    solvedata.error_end = error_end;
+                }
+                else /* Unknown error. */
+                    solvedata.error = _("Malformed expression");
+                break;
+
+            default:
+                solvedata.error = /* Error displayed to user when they enter an invalid calculation */
+                                  _("Malformed expression");
+                break;
+        }
+        queue.push (solvedata);
+
+        return null;
+    }
+
+    private bool show_in_progress ()
+    {
+        if (in_solve)
+            status = _("Calculating");
+        return false;
+    }
+
+    private bool look_for_answer ()
+    {
+        var result = queue.try_pop ();
+
+        if (result == null)
+            return true;
+
+        _in_solve = false;
+
+        if (result.error == null)
+            status = "";
+
+        if (result.error != null)
+        {
+            status = result.error;
+            state.error_token_start = result.error_start;
+            state.error_token_end = result.error_end;
+
+            /* Fix thousand separator offsets in the start and end offsets of error token. */
+            error_token_fix_thousands_separator ();
+
+            /* Notify the GUI about the change in error token locations. */
+            notify_property ("error-token-end");
+        }
+        else if (result.number_result != null)
+            set_number (result.number_result);
+        else if (result.text_result != null)
+            set (result.text_result);
+
+        return false;
+    }
+
+    public void solve ()
+    {
+        // FIXME: should replace calculation or give error message
+        if (in_solve)
+            return;
+
+        if (is_empty)
+            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 (is_result)
+        {
+            undo ();
+            return;
+        }
+
+        _in_solve = true;
+
+        number_mode = NumberMode.NORMAL;
+
+        new Thread<void*> ("", solve_real);
+
+        Timeout.add (50, look_for_answer);
+        Timeout.add (100, show_in_progress);
+    }
+
+    /* Fix the offsets to consider thousand separators inserted by the gui. */
+    private void error_token_fix_thousands_separator ()
+    {
+        Gtk.TextIter start;
+        get_start_iter (out start);
+        var temp = start;
+        var end = start;
+
+        start.set_offset ((int) error_token_start);
+        end.set_offset ((int) error_token_end);
+
+        var str = serializer.get_thousands_separator ().to_string ();
+        var length = str.char_count ();
+
+        /* Move both start and end offsets for each thousand separator till the start of error token. */
+        while (temp.forward_search (str, Gtk.TextSearchFlags.TEXT_ONLY, null, out temp, start))
+        {
+            state.error_token_start += length;
+            state.error_token_end += length;
+            start.forward_chars (length);
+            start.forward_chars (length);
+        }
+
+        /* Starting from start, move only end offset for each thousand separator till the end of error token. */
+        temp = start;
+        while (temp.forward_search (str, Gtk.TextSearchFlags.TEXT_ONLY, null, out temp, end))
+        {
+            state.error_token_end += length;
+            end.forward_chars (length);
+        }
+    }
+
+    private void* factorize_real ()
+    {
+        var x = number;
+        var factors = x.factorize ();
+
+        var text = "";
+        var i = 0;
+        foreach (var factor in factors)
+        {
+            if (i != 0)
+                text += "Ã";
+            text += serializer.to_string (factor);
+            i++;
+        }
+
+        var result = new SolveData ();
+        result.text_result = text;
+        queue.push (result);
+
+        return null;
+    }
+
+    public void factorize ()
+    {
+        // FIXME: should replace calculation or give error message
+        if (in_solve)
+            return;
+
+        var x = number;
+        if (x == null || !x.is_integer ())
+        {
+            /* Error displayed when trying to factorize a non-integer value */
+            status = _("Need an integer to factorize");
+            return;
+        }
+
+        _in_solve = true;
+
+        new Thread<void*> ("", factorize_real);
+
+        Timeout.add (50, look_for_answer);
+        Timeout.add (100, show_in_progress);
+    }
+
+    public void delete_next ()
+    {
+        int cursor;
+        get ("cursor-position", out cursor, null);
+        if (cursor >= get_char_count ())
+            return;
+
+        Gtk.TextIter start, end;
+        get_iter_at_offset (out start, cursor);
+        get_iter_at_offset (out end, cursor+1);
+        @delete (ref start, ref end);
+    }
+
+    public new void backspace ()
+    {
+        /* Can't delete empty display */
+        if (is_empty)
+            return;
+
+        if (has_selection)
+            delete_selection (false, false);
+        else
+        {
+            Gtk.TextIter iter;
+            get_iter_at_mark (out iter, get_insert ());
+            (this as Gtk.TextBuffer).backspace (iter, true, true);
+        }
+    }
+
+    public void clear ()
+    {
+        number_mode = NumberMode.NORMAL;
+        set_text ("", -1);
+        clear_ans (false);
+    }
+
+    public void shift (int count)
+    {
+        var z = number;
+        if (z == null)
+        {
+            /* This message is displayed in the status bar when a bit shift operation is performed and the display does not contain a number */        
+            status = _("No sane value to bitwise shift");
+            return;
+        }
+
+        set_number (z.shift (count));
+    }
+
+    public void toggle_bit (uint bit)
+    {
+        var x = number;
+        var max = new Number.unsigned_integer (uint64.MAX);
+        if (x == null || x.is_negative () || x.compare (max) > 0)
+        {
+            /* Message displayed when cannot toggle bit in display */
+            status = _("Displayed value not an integer");
+            return;
+        }
+
+        var bits = x.to_unsigned_integer ();
+        bits ^= (1LL << (63 - bit));
+        x = new Number.unsigned_integer (bits);
+
+        // FIXME: Only do this if in ans format, otherwise set text in same format as previous number
+        set_number (x);
+    }
+
+    protected override void insert_text (ref Gtk.TextIter location, string text, int len)
+    {
+        if (in_reformat)
+        {
+            base.insert_text (ref location, text, len);
+            return;
+        }
+
+        /* If following a delete then have already pushed undo stack (Gtk.TextBuffer doesn't indicate replace operations so we have to infer them) */
+        if (!in_delete)
+            push_undo_stack ();
+
+        /* Clear result on next digit entered if cursor at end of line */
+        var c = text.get_char (0);
+        int cursor;
+        get ("cursor-position", out cursor, null);
+        if ((c.isdigit () || c == serializer.get_radix ()) && is_result && cursor >= get_char_count ())
+        {
+            set_text ("", -1);
+            clear_ans (false);
+            get_end_iter (out location);
+        }
+
+        if (ans_start_mark != null)
+        {
+            var offset = location.get_offset ();
+            int ans_start, ans_end;
+            get_ans_offsets (out ans_start, out ans_end);
+
+            /* Inserted inside ans */
+            if (offset > ans_start && offset < ans_end)
+                clear_ans (true);
+        }
+
+        base.insert_text (ref location, text, len);
+
+        state.entered_multiply = text == "Ã";
+
+        /* Update thousands separators */
+        reformat_separators ();
+
+        notify_property ("display");
+    }
+
+    protected override void delete_range (Gtk.TextIter start, Gtk.TextIter end)
+    {
+        if (in_reformat)
+        {
+            base.delete_range (start, end);
+            return;
+        }
+
+        push_undo_stack ();
+
+        in_delete = true;
+        Idle.add (() => { in_delete = false; return false; });
+
+        if (ans_start_mark != null)
+        {
+            var start_offset = start.get_offset ();
+            var end_offset = end.get_offset ();
+            int ans_start, ans_end;
+            get_ans_offsets (out ans_start, out ans_end);
+
+            /* Deleted part of ans */
+            if (start_offset < ans_end && end_offset > ans_start)
+                clear_ans (true);
+        }
+
+        base.delete_range (start, end);
+
+        state.entered_multiply = false;
+
+        /* Update thousands separators */
+        reformat_separators ();
+
+        // FIXME: A replace will emit this both for delete-range and insert-text, can it be avoided?
+        notify_property ("display");
+    }
+}
+
+private class MEquation : Equation
+{
+    private MathEquation m_equation;
+
+    public MEquation (MathEquation m_equation, string equation)
+    {
+        base (equation);
+        this.m_equation = m_equation;
+    }
+
+    public override bool variable_is_defined (string name)
+    {
+        var lower_name = name.down ();
+
+        if (lower_name == "rand" || lower_name == "ans")
+            return true;
+
+        return m_equation.variables.get (name) != null;
+    }
+
+    public override Number? get_variable (string name)
+    {
+        var lower_name = name.down ();
+
+        if (lower_name == "rand")
+            return new Number.random ();
+        else if (lower_name == "ans")
+            return m_equation.answer;
+        else
+            return m_equation.variables.get (name);
+    }
+
+    public override void set_variable (string name, Number x)
+    {
+        /* FIXME: Don't allow writing to built-in variables, e.g. ans, rand, sin, ... */
+        m_equation.variables.set (name, x);
+    }
+
+    public override Number? convert (Number x, string x_units, string z_units)
+    {
+        return UnitManager.get_default ().convert_by_symbol (x, x_units, z_units);
+    }
+}
\ No newline at end of file
diff --git a/src/math-preferences.vala b/src/math-preferences.vala
new file mode 100644
index 0000000..51b8582
--- /dev/null
+++ b/src/math-preferences.vala
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2008-2012 Robert Ancell
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+public class MathPreferencesDialog : Gtk.Dialog
+{
+    private MathEquation equation;
+    private Gtk.ComboBox angle_unit_combo;
+    private Gtk.ComboBox number_format_combo;
+    private Gtk.ComboBox word_size_combo;
+    private Gtk.SpinButton decimal_places_spin;
+    private Gtk.CheckButton thousands_separator_check;
+    private Gtk.CheckButton trailing_zeroes_check;
+
+    public MathPreferencesDialog (MathEquation equation)
+    {
+        this.equation = equation;
+
+        set_title (/* Title of preferences dialog */
+                   _("Preferences"));
+        border_width = 8;
+        add_button (/* Label on close button in preferences dialog */
+                    _("_Close"), 0);
+
+        var grid = new Gtk.Grid ();
+        grid.show ();
+        grid.border_width = 5;
+        grid.column_spacing = 6;
+        grid.row_spacing = 12;
+        get_content_area ().pack_start (grid, true, true, 0);
+
+        var label = new Gtk.Label.with_mnemonic (/* Preferences dialog: Label for number format combo box */
+                                                 _("Number _Format:"));
+        label.show ();
+        label.xalign = 0;
+        grid.attach (label, 0, 0, 1, 1);
+
+        number_format_combo = new Gtk.ComboBox ();
+        label.mnemonic_widget = number_format_combo;
+        number_format_combo.show ();
+        number_format_combo.changed.connect (number_format_combo_changed_cb);
+        grid.attach (number_format_combo, 1, 0, 1, 1);
+
+        var model = new Gtk.ListStore (2, typeof (string), typeof (int));
+        number_format_combo.model = model;
+        Gtk.TreeIter iter;
+        model.append (out iter);
+        model.set (iter, 0,
+                   /* Number display mode combo: Automatic, e.g. 1234 (or scientific for large number 1.234Ã10^99) */
+                   _("Automatic"), 1, DisplayFormat.AUTOMATIC, -1);
+        model.append (out iter);
+        model.set (iter, 0,
+                   /* Number display mode combo: Fixed, e.g. 1234 */
+                   _("Fixed"), 1, DisplayFormat.FIXED, -1);
+        model.append (out iter);
+        model.set (iter, 0,
+                   /* Number display mode combo: Scientific, e.g. 1.234Ã10^3 */
+                   _("Scientific"), 1, DisplayFormat.SCIENTIFIC, -1);
+        model.append (out iter);
+        model.set (iter, 0,
+                   /* Number display mode combo: Engineering, e.g. 1.234k */
+                   _("Engineering"), 1, DisplayFormat.ENGINEERING, -1);
+        var renderer = new Gtk.CellRendererText ();
+        number_format_combo.pack_start (renderer, true);
+        number_format_combo.add_attribute (renderer, "text", 0);
+
+        var alignment = new Gtk.Alignment (0.5f, 0.5f, 1.0f, 1.0f);
+        alignment.bottom_padding = 6;
+        alignment.left_padding = 12;
+        alignment.show ();
+        grid.attach (alignment, 0, 1, 2, 1);
+
+        var format_options_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
+        format_options_box.show ();
+        alignment.add (format_options_box);
+
+        var places_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
+        places_box.show ();
+        format_options_box.pack_start (places_box, false, false, 0);
+
+        /* Label used in preferences dialog.  The %d is replaced by a spinbutton */
+        var string = _("Show %d decimal _places");
+        var tokens = string.split ("%d", 2);
+
+        var decimal_places_adjustment = new Gtk.Adjustment (0.0, 0.0, 9.0, 1.0, 1.0, 0.0);
+        decimal_places_spin = new Gtk.SpinButton (decimal_places_adjustment, 0.0, 0);
+
+        if (tokens.length > 0)
+        {
+            label = new Gtk.Label.with_mnemonic (tokens[0].strip ());
+            label.mnemonic_widget = decimal_places_spin;
+            label.show ();
+            places_box.pack_start (label, false, false, 0);
+        }
+
+        decimal_places_spin.show ();
+        decimal_places_spin.value_changed.connect (() => { equation.accuracy = decimal_places_spin.get_value_as_int (); });
+        places_box.pack_start (decimal_places_spin, false, false, 0);
+
+        if (tokens.length == 2)
+        {
+            label = new Gtk.Label.with_mnemonic (tokens[1].strip ());
+            label.mnemonic_widget = decimal_places_spin;
+            label.show ();
+            places_box.pack_start (label, false, false, 0);
+        }
+
+        trailing_zeroes_check = new Gtk.CheckButton.with_mnemonic (/* Preferences dialog: label for show trailing zeroes check button */
+                                                                   _("Show trailing _zeroes"));
+        trailing_zeroes_check.show ();
+        trailing_zeroes_check.toggled.connect (() => { equation.show_trailing_zeroes = trailing_zeroes_check.get_active (); });
+        format_options_box.pack_start (trailing_zeroes_check, false, false, 0);
+
+        thousands_separator_check = new Gtk.CheckButton.with_mnemonic (/* Preferences dialog: label for show show thousands separator check button */
+                                                                       _("Show _thousands separators"));
+        thousands_separator_check.show ();
+        thousands_separator_check.toggled.connect (() => { equation.show_thousands_separators = thousands_separator_check.get_active (); });
+        format_options_box.pack_start (thousands_separator_check, false, false, 0);
+
+        label = new Gtk.Label.with_mnemonic (/* Preferences dialog: Label for angle unit combo box */
+                                             _("_Angle units:"));
+        label.show ();
+        label.xalign = 0;
+        grid.attach (label, 0, 2, 1, 1);
+
+        angle_unit_combo = new Gtk.ComboBox ();
+        label.mnemonic_widget = angle_unit_combo;
+        angle_unit_combo.show ();
+        angle_unit_combo.changed.connect (angle_unit_combo_changed_cb);
+        grid.attach (angle_unit_combo, 1, 2, 1, 1);
+
+        model = new Gtk.ListStore (2, typeof (string), typeof (int));
+        angle_unit_combo.model = model;
+        model.append (out iter);
+        model.set (iter, 0,
+                   /* Preferences dialog: Angle unit combo box: Use degrees for trigonometric calculations */
+                   _("Degrees"), 1, AngleUnit.DEGREES, -1);
+        model.append (out iter);
+        model.set (iter, 0,
+                   /* Preferences dialog: Angle unit combo box: Use radians for trigonometric calculations */
+                   _("Radians"), 1, AngleUnit.RADIANS, -1);
+        model.append (out iter);
+        model.set (iter, 0,
+                   /* Preferences dialog: Angle unit combo box: Use gradians for trigonometric calculations */
+                   _("Gradians"), 1, AngleUnit.GRADIANS, -1);
+        renderer = new Gtk.CellRendererText ();
+        angle_unit_combo.pack_start (renderer, true);
+        angle_unit_combo.add_attribute (renderer, "text", 0);
+
+        label = new Gtk.Label.with_mnemonic (/* Preferences dialog: Label for word size combo box */
+                                             _("Word _size:"));
+        label.show ();
+        label.xalign = 0;
+        grid.attach (label, 0, 3, 1, 1);
+
+        word_size_combo = new Gtk.ComboBox ();
+        label.mnemonic_widget = word_size_combo;
+        word_size_combo.show ();
+        word_size_combo.changed.connect (word_size_combo_changed_cb);
+        grid.attach (word_size_combo, 1, 3, 1, 1);
+
+        model = new Gtk.ListStore (2, typeof (string), typeof (int));
+        word_size_combo.model = model;
+        model.append (out iter);
+        model.set (iter, 0, /* Word size combo: 8 bits */ _("8 bits"), 1, 8);
+        model.append (out iter);
+        model.set (iter, 0, /* Word size combo: 16 bits */ _("16 bits"), 1, 16);
+        model.append (out iter);
+        model.set (iter, 0, /* Word size combo: 32 bits */ _("32 bits"), 1, 32);
+        model.append (out iter);
+        model.set (iter, 0, /* Word size combo: 64 bits */ _("64 bits"), 1, 64);
+        renderer = new Gtk.CellRendererText ();
+        word_size_combo.pack_start (renderer, true);
+        word_size_combo.add_attribute (renderer, "text", 0);
+
+        decimal_places_spin.set_value (equation.accuracy);
+        equation.notify["accuracy"].connect ((pspec) => { decimal_places_spin.set_value (equation.accuracy); });
+
+        thousands_separator_check.set_active (equation.show_thousands_separators);
+        equation.notify["show-thousands-separators"].connect (() => { thousands_separator_check.set_active (equation.show_thousands_separators); });
+
+        trailing_zeroes_check.set_active (equation.show_trailing_zeroes);
+        equation.notify["show-trailing_zeroes"].connect (() => { trailing_zeroes_check.set_active (equation.show_trailing_zeroes); });
+
+        set_combo_box_from_int (number_format_combo, equation.number_format);
+        equation.notify["number-format"].connect ((pspec) => { set_combo_box_from_int (number_format_combo, equation.number_format); });
+
+        set_combo_box_from_int (word_size_combo, equation.word_size);
+        equation.notify["word-size"].connect ((pspec) => { set_combo_box_from_int (word_size_combo, equation.word_size); });
+
+        set_combo_box_from_int (angle_unit_combo, equation.angle_units);
+        equation.notify["angle-units"].connect ((pspec) => { set_combo_box_from_int (angle_unit_combo, equation.angle_units); });
+    }
+
+    protected override void response (int id)
+    {
+        hide ();
+    }
+
+    protected override bool delete_event (Gdk.EventAny event)
+    {
+        hide ();
+        return true;
+    }
+
+    private void number_format_combo_changed_cb (Gtk.ComboBox combo)
+    {
+        Gtk.TreeIter iter;
+        combo.get_active_iter (out iter);
+        DisplayFormat value;
+        combo.model.get (iter, 1, out value, -1);
+        equation.number_format = value;
+    }
+
+    private void angle_unit_combo_changed_cb (Gtk.ComboBox combo)
+    {
+        Gtk.TreeIter iter;
+        combo.get_active_iter (out iter);
+        AngleUnit value;
+        combo.model.get (iter, 1, out value, -1);
+        equation.angle_units = value;
+    }
+
+    private void word_size_combo_changed_cb (Gtk.ComboBox combo)
+    {
+        Gtk.TreeIter iter;
+        combo.get_active_iter (out iter);
+        int value;
+        combo.model.get (iter, 1, out value, -1);
+        equation.word_size = value;
+    }
+
+    private void set_combo_box_from_int (Gtk.ComboBox combo, int value)
+    {
+        Gtk.TreeIter iter;
+        var valid = combo.model.get_iter_first (out iter);
+        while (valid)
+        {
+            int v;
+
+            combo.model.get (iter, 1, out v, -1);
+            if (v == value)
+                break;
+            valid = combo.model.iter_next (ref iter);
+        }
+        if (!valid)
+            valid = combo.model.get_iter_first (out iter);
+
+        combo.set_active_iter (iter);
+    }
+}
diff --git a/src/math-variable-popup.vala b/src/math-variable-popup.vala
new file mode 100644
index 0000000..05849a1
--- /dev/null
+++ b/src/math-variable-popup.vala
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2008-2012 Robert Ancell
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+public class MathVariablePopup : Gtk.Window
+{
+    private MathEquation equation;
+
+    private Gtk.Box vbox;
+    private Gtk.Entry variable_name_entry;
+    private Gtk.Button add_variable_button;
+
+    public MathVariablePopup (MathEquation equation)
+    {
+        this.equation = equation;
+
+        decorated = false;
+        skip_taskbar_hint = true;
+        border_width = 6;
+
+        /* Destroy this window when it loses focus */
+        focus_out_event.connect ((event) => { destroy (); return false; });
+
+        vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
+        vbox.homogeneous = true;
+        add (vbox);
+        vbox.show ();
+
+        var names = equation.variables.get_names ();
+        for (var i = 0; names[i] != null; i++)
+        {
+            var value = equation.variables.get (names[i]);
+            var entry = make_variable_entry (names[i], value, true);
+            entry.show ();
+            vbox.pack_start (entry, false, true, 0);
+        }
+
+        var entry = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
+        entry.show ();
+
+        // TODO: Show greyed "variable name" text to give user a hint how to use
+        variable_name_entry = new Gtk.Entry ();
+        variable_name_entry.key_press_event.connect (variable_name_key_press_cb);
+        variable_name_entry.changed.connect (variable_name_changed_cb);
+        variable_name_entry.activate.connect (add_variable_cb);
+        entry.pack_start (variable_name_entry, true, true, 0);
+        variable_name_entry.show ();
+
+        add_variable_button = new Gtk.Button ();
+        add_variable_button.sensitive = false;
+        add_variable_button.clicked.connect (add_variable_cb);
+        var image = new Gtk.Image.from_stock (Gtk.Stock.ADD, Gtk.IconSize.BUTTON);
+        add_variable_button.add (image);
+        entry.pack_start (add_variable_button, false, true, 0);
+        image.show ();
+        add_variable_button.show ();
+        vbox.pack_end (entry, false, true, 0);
+
+        entry = make_variable_entry ("rand", null, false);
+        entry.show ();
+        vbox.pack_end (entry, false, true, 0);
+
+        entry = make_variable_entry ("ans", equation.answer, false);
+        entry.show ();
+        vbox.pack_end (entry, false, true, 0);
+    }
+
+    private void insert_variable_cb (Gtk.Widget widget)
+    {
+        var name = widget.get_data<string> ("variable_name");
+        equation.insert (name);
+
+        widget.get_toplevel ().destroy ();
+    }
+
+    private bool variable_name_key_press_cb (Gtk.Widget widget, Gdk.EventKey event)
+    {
+        /* Can't have whitespace in names, so replace with underscores */
+        if (event.keyval == Gdk.Key.space || event.keyval == Gdk.Key.KP_Space)
+            event.keyval = Gdk.Key.underscore;
+
+        return false;
+    }
+
+    private void variable_name_changed_cb ()
+    {
+        add_variable_button.sensitive = variable_name_entry.get_text () != "";
+    }
+
+    private void add_variable_cb (Gtk.Widget widget)
+    {
+        var name = variable_name_entry.get_text ();
+        if (name == "")
+            return;
+
+        var z = equation.number;
+        if (z != null)
+            equation.variables.set (name, z);
+        else if (equation.is_result)
+            equation.variables.set (name, equation.answer);
+        else
+            warning ("Can't add variable %s, the display is not a number", name);
+
+        widget.get_toplevel ().destroy ();
+    }
+
+    private void save_variable_cb (Gtk.Widget widget)
+    {
+        var name = widget.get_data<string> ("variable_name");
+        var z = equation.number;
+        if (z != null)
+            equation.variables.set (name, z);
+        else if (equation.is_result)
+            equation.variables.set (name, equation.answer);
+        else
+            warning ("Can't save variable %s, the display is not a number", name);
+
+        widget.get_toplevel ().destroy ();
+    }
+
+    private void delete_variable_cb (Gtk.Widget widget)
+    {
+        var name = widget.get_data<string> ("variable_name");
+        equation.variables.delete (name);
+
+        widget.get_toplevel ().destroy ();
+    }
+
+    private Gtk.Box make_variable_entry (string name, Number? value, bool writable)
+    {
+        var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
+
+        string text;
+        if (value != null)
+        {
+            var value_text = equation.serializer.to_string (value);
+            text = "<b>%s</b> = %s".printf (name, value_text);
+        }
+        else
+            text = "<b>%s</b>".printf (name);
+
+        var button = new Gtk.Button ();
+        button.set_data<string> ("variable_name", name);
+        button.clicked.connect (insert_variable_cb);
+        button.set_relief (Gtk.ReliefStyle.NONE);
+        hbox.pack_start (button, true, true, 0);
+        button.show ();
+
+        var label = new Gtk.Label (text);
+        label.set_use_markup (true);
+        label.set_alignment (0.0f, 0.5f);
+        button.add (label);
+        label.show ();
+
+        if (writable)
+        {
+            button = new Gtk.Button ();
+            button.set_data<string> ("variable_name", name);
+            var image = new Gtk.Image.from_stock (Gtk.Stock.SAVE, Gtk.IconSize.BUTTON);
+            button.add (image);
+            hbox.pack_start (button, false, true, 0);
+            button.clicked.connect (save_variable_cb);
+            image.show ();
+            button.show ();
+
+            button = new Gtk.Button ();
+            button.set_data<string> ("variable_name", name);
+            image = new Gtk.Image.from_stock (Gtk.Stock.DELETE, Gtk.IconSize.BUTTON);
+            button.add (image);
+            hbox.pack_start (button, false, true, 0);
+            button.clicked.connect (delete_variable_cb);
+            image.show ();
+            button.show ();
+        }
+
+        return hbox;
+    }
+}
diff --git a/src/math-variables.vala b/src/math-variables.vala
new file mode 100644
index 0000000..f326f1f
--- /dev/null
+++ b/src/math-variables.vala
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2008-2012 Robert Ancell
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+public class MathVariables : Object
+{
+    private string file_name;
+    private HashTable<string, Number?> registers;
+    private Serializer serializer;
+
+    public MathVariables ()
+    {
+        registers = new HashTable <string, Number?> (str_hash, str_equal);
+        file_name = Path.build_filename (Environment.get_user_data_dir (), "gcalctool", "registers");
+        serializer = new Serializer (DisplayFormat.SCIENTIFIC, 10, 50);
+        serializer.set_radix ('.');
+        registers_load ();
+    }
+
+    private void registers_load ()
+    {
+        string data;
+        try
+        {
+            FileUtils.get_contents (file_name, out data);
+        }
+        catch (FileError e)
+        {
+            return;
+        }
+
+        registers.remove_all ();
+
+        var lines = data.split ("\n");
+        foreach (var line in lines)
+        {
+            var i = line.index_of_char ('=');
+            if (i < 0)
+                continue;
+
+            var name = line.substring (0, i).strip ();
+            var value = line.substring (i+1).strip ();
+
+            var t = mp_set_from_string (value);
+            if (t != null)
+                registers.insert (name, t);
+        }
+    }
+
+    private void save ()
+    {
+        var data = "";
+        var iter = HashTableIter<string, Number?> (registers);
+        string name;
+        Number? value;
+        while (iter.next (out name, out value))
+        {
+            var number = serializer.to_string (value);
+            data += "%s=%s\n".printf (name, number);
+        }
+
+        var dir = Path.get_dirname (file_name);
+        DirUtils.create_with_parents (dir, 0700);
+        try
+        {
+            FileUtils.set_contents (file_name, data);
+        }
+        catch (FileError e)
+        {
+        }
+    }
+
+    // FIXME: Sort
+    public string[] get_names ()
+    {
+        var names = new string[registers.size () + 1];
+
+        var iter = HashTableIter<string, Number?> (registers);
+        var i = 0;
+        string name;
+        Number? value;
+        while (iter.next (out name, out value))
+        {
+            names[i] = name;
+            i++;
+        }
+        names[i] = null;
+
+        return names;
+    }
+
+    public new void set (string name, Number value)
+    {
+        registers.insert (name, value);
+        save ();
+    }
+
+    public new Number? get (string name)
+    {
+        return registers.lookup (name);
+    }
+
+    public void delete (string name)
+    {
+        registers.remove (name);
+        save ();
+    }
+}
diff --git a/src/math-window.vala b/src/math-window.vala
new file mode 100644
index 0000000..1c3a614
--- /dev/null
+++ b/src/math-window.vala
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (C) 2008-2012 Robert Ancell.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+public class MathWindow : Gtk.ApplicationWindow
+{
+    private MathEquation _equation;
+    public MathEquation equation { get { return _equation; } }
+
+    private MathDisplay _display;
+    public MathDisplay display { get { return _display; } }
+
+    private MathButtons _buttons;
+    public MathButtons buttons { get { return _buttons; } }
+    private bool right_aligned;
+
+    public MathWindow (Gtk.Application app, MathEquation equation)
+    {
+        Object (application: app);
+        _equation = equation;
+        set_title (/* Title of main window */
+                   _("Calculator"));
+        role = "gcalctool";
+        resizable = false;
+
+        var main_vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
+        add (main_vbox);
+        main_vbox.show ();
+
+        var vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
+        vbox.border_width = 6;
+        main_vbox.pack_start (vbox, true, true, 0);  
+        vbox.show ();
+
+        var scrolled_window = new Gtk.ScrolledWindow (null, null);
+        scrolled_window.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.NEVER);
+        scrolled_window.set_shadow_type (Gtk.ShadowType.IN);
+        vbox.pack_start (scrolled_window, false, false, 0);
+        scrolled_window.get_hadjustment ().changed.connect (scroll_changed_cb);
+        scrolled_window.get_hadjustment ().value_changed.connect (scroll_value_changed_cb);
+        right_aligned = true;
+        scrolled_window.show ();
+
+        _display = new MathDisplay (equation);
+        scrolled_window.add (display);
+        display.show ();
+
+        _buttons = new MathButtons (equation);
+        vbox.pack_start (buttons, true, true, 0);
+        buttons.show ();
+    }
+
+    public void critical_error (string title, string contents)
+    {
+        var dialog = new Gtk.MessageDialog (null, 0,
+                                            Gtk.MessageType.ERROR,
+                                            Gtk.ButtonsType.NONE,
+                                            "%s", title);
+        dialog.format_secondary_text ("%s", contents);
+        dialog.add_buttons (Gtk.Stock.QUIT, Gtk.ResponseType.ACCEPT);
+
+        dialog.run ();
+
+        destroy ();
+    }
+
+    protected override bool key_press_event (Gdk.EventKey event)
+    {
+        var result = base.key_press_event (event);
+
+        if (buttons.mode == ButtonMode.PROGRAMMING && (event.state & Gdk.ModifierType.CONTROL_MASK) == Gdk.ModifierType.CONTROL_MASK)
+        {
+            switch (event.keyval)
+            {
+            /* Binary */
+            case Gdk.Key.b:
+                equation.number_base = 2;
+                return true;
+            /* Octal */
+            case Gdk.Key.o:
+                equation.number_base = 8;
+                return true;
+            /* Decimal */
+            case Gdk.Key.d:
+                equation.number_base = 10;
+                return true;
+            /* Hexdecimal */
+            case Gdk.Key.h:
+                equation.number_base = 16;
+                return true;
+            }
+        }
+
+        return result;
+    }
+
+    private void scroll_changed_cb (Gtk.Adjustment adjustment)
+    {
+        if (right_aligned)
+            adjustment.set_value (adjustment.get_upper () - adjustment.get_page_size ());
+    }
+
+    private void scroll_value_changed_cb (Gtk.Adjustment adjustment)
+    {
+        if (adjustment.get_value () == adjustment.get_upper () - adjustment.get_page_size ())
+            right_aligned = true;
+        else
+            right_aligned = false;
+    }
+}
\ No newline at end of file
diff --git a/src/number.vala b/src/number.vala
new file mode 100644
index 0000000..b8ad8aa
--- /dev/null
+++ b/src/number.vala
@@ -0,0 +1,3272 @@
+/*
+ * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (C) 2008-2012 Robert Ancell
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+/*  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.
+ */
+
+/* Size of the multiple precision values */
+private const int SIZE = 1000;
+
+/* Base for numbers */
+private const int BASE = 10000;
+
+//2E0 BELOW ENSURES AT LEAST ONE GUARD DIGIT
+//MP.t = (int) ((float) (accuracy) * Math.log ((float)10.) / Math.log ((float) BASE) + (float) 2.0);
+//if (MP.t > SIZE)
+//{
+//    mperr ("SIZE TOO SMALL IN CALL TO MPSET, INCREASE SIZE AND DIMENSIONS OF MP ARRAYS TO AT LEAST %d ***", MP.t);
+//    MP.t = SIZE;
+//}
+private const int T = 100;
+
+private delegate int BitwiseFunc (int v1, int v2);
+
+public enum AngleUnit
+{
+    RADIANS,
+    DEGREES,
+    GRADIANS
+}
+
+/* Object for a high precision floating point number representation
+ *
+ * x = sign * (BASE^(exponent-1) + BASE^(exponent-2) + ...)
+ */
+public class Number
+{
+    /* Sign (+1, -1) or 0 for the value zero */
+    public int re_sign;
+    public int im_sign;
+
+    /* Exponent (to base BASE) */
+    public int re_exponent;
+    public int im_exponent;
+
+    /* Normalized fraction */
+    public int re_fraction[1000]; // SIZE
+    public int im_fraction[1000]; // SIZE
+
+    public Number.integer (int64 value)
+    {
+        if (value < 0)
+        {
+            value = -value;
+            re_sign = -1;
+        }
+        else if (value > 0)
+            re_sign = 1;
+        else
+            re_sign = 0;
+
+        while (value != 0)
+        {
+            re_fraction[re_exponent] = (int) (value % BASE);
+            re_exponent++;
+            value /= BASE;
+        }
+        for (var i = 0; i < re_exponent / 2; i++)
+        {
+            int t = re_fraction[i];
+            re_fraction[i] = re_fraction[re_exponent - i - 1];
+            re_fraction[re_exponent - i - 1] = t;
+        }
+    }
+
+    public Number.unsigned_integer (uint64 x)
+    {
+        if (x == 0)
+            re_sign = 0;
+        else
+            re_sign = 1;
+
+        while (x != 0)
+        {
+            re_fraction[re_exponent] = (int) (x % BASE);
+            x = x / BASE;
+            re_exponent++;
+        }
+        for (var i = 0; i < re_exponent / 2; i++)
+        {
+            int t = re_fraction[i];
+            re_fraction[i] = re_fraction[re_exponent - i - 1];
+            re_fraction[re_exponent - i - 1] = t;
+        }
+    }
+
+    public Number.fraction (int64 numerator, int64 denominator)
+    {
+        mp_gcd (ref numerator, ref denominator);
+
+        if (denominator < 0)
+        {
+            numerator = -numerator;
+            denominator = -denominator;
+        }
+
+        Number.integer (numerator);
+        if (denominator != 1)
+        {
+            var z = divide_integer (denominator);
+            re_sign = z.re_sign;
+            im_sign = z.im_sign;
+            re_exponent = z.re_exponent;
+            im_exponent = z.im_exponent;
+            for (var i = 0; i < z.re_fraction.length; i++)
+            {
+                re_fraction[i] = z.re_fraction[i];
+                im_fraction[i] = z.im_fraction[i];
+            }
+        }
+    }
+
+    public Number.float (float value)
+    {
+        var z = new Number.integer (0);
+
+        if (value != 0)
+        {
+            /* CHECK SIGN */
+            var rj = 0f;
+            if (value < 0.0f)
+            {
+                z.re_sign = -1;
+                rj = -value;
+            }
+            else if (value > 0.0f)
+            {
+                z.re_sign = 1;
+                rj = value;
+            }
+
+            /* INCREASE IE AND DIVIDE RJ BY 16. */
+            var 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 re_exponent TO 0
+             */
+            z.re_exponent = 0;
+
+            /* CONVERSION LOOP (ASSUME SINGLE-PRECISION OPS. EXACT) */
+            for (var i = 0; i < T + 4; i++)
+            {
+                rj *= BASE;
+                z.re_fraction[i] = (int) rj;
+                rj -= z.re_fraction[i];
+            }
+
+            /* NORMALIZE RESULT */
+            mp_normalize (ref z);
+
+            /* Computing MAX */
+            var ib = int.max (BASE * 7 * BASE, 32767) / 16;
+            var tp = 1;
+
+            /* NOW MULTIPLY BY 16**IE */
+            if (ie < 0)
+            {
+                var k = -ie;
+                for (var i = 1; i <= k; i++)
+                {
+                    tp <<= 4;
+                    if (tp <= ib && tp != BASE && i < k)
+                        continue;
+                    z = z.divide_integer (tp);
+                    tp = 1;
+                }
+            }
+            else if (ie > 0)
+            {
+                for (var i = 1; i <= ie; i++)
+                {
+                    tp <<= 4;
+                    if (tp <= ib && tp != BASE && i < ie)
+                        continue;
+                    z = z.multiply_integer (tp);
+                    tp = 1;
+                }
+            }
+        }
+
+        re_sign = z.re_sign;
+        im_sign = z.im_sign;
+        re_exponent = z.re_exponent;
+        im_exponent = z.im_exponent;
+        for (var i = 0; i < z.re_fraction.length; i++)
+        {
+            re_fraction[i] = z.re_fraction[i];
+            im_fraction[i] = z.im_fraction[i];
+        }
+    }
+    
+    public Number.double (double value)
+    {
+        var z = new Number.integer (0);
+
+        if (value != 0)
+        {
+            /* CHECK SIGN */
+            var dj = 0.0;
+            if (value < 0.0)
+            {
+                z.re_sign = -1;
+                dj = -value;
+            }
+            else if (value > 0.0)
+            {
+                z.re_sign = 1;
+                dj = value;
+            }
+    
+            /* INCREASE IE AND DIVIDE DJ BY 16. */
+            var ie = 0;
+            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 re_exponent TO 0
+             */
+            z.re_exponent = 0;
+    
+            /* CONVERSION LOOP (ASSUME DOUBLE-PRECISION OPS. EXACT) */
+            for (var i = 0; i < T + 4; i++)
+            {
+                dj *= (double) BASE;
+                z.re_fraction[i] = (int) dj;
+                dj -= (double) z.re_fraction[i];
+            }
+    
+            /* NORMALIZE RESULT */
+            mp_normalize (ref z);
+    
+            /* Computing MAX */
+            var ib = int.max (BASE * 7 * BASE, 32767) / 16;
+            var tp = 1;
+    
+            /* NOW MULTIPLY BY 16**IE */
+            if (ie < 0)
+            {
+                var k = -ie;
+                for (var i = 1; i <= k; ++i)
+                {
+                    tp <<= 4;
+                    if (tp <= ib && tp != BASE && i < k)
+                        continue;
+                    z = z.divide_integer (tp);
+                    tp = 1;
+                }
+            }
+            else if (ie > 0)
+            {
+                for (var i = 1; i <= ie; ++i)
+                {
+                    tp <<= 4;
+                    if (tp <= ib && tp != BASE && i < ie)
+                        continue;
+                    z = z.multiply_integer (tp);
+                    tp = 1;
+                }
+            }
+        }
+    
+        re_sign = z.re_sign;
+        im_sign = z.im_sign;
+        re_exponent = z.re_exponent;
+        im_exponent = z.im_exponent;
+        for (var i = 0; i < z.re_fraction.length; i++)
+        {
+            re_fraction[i] = z.re_fraction[i];
+            im_fraction[i] = z.im_fraction[i];
+        }
+    }
+
+    public Number.complex (Number x, Number y)
+    {
+        re_sign = x.re_sign;
+        re_exponent = x.re_exponent;
+        for (var i = 0; i < im_fraction.length; i++)
+            re_fraction[i] = x.re_fraction[i];
+
+        im_sign = y.re_sign;
+        im_exponent = y.re_exponent;
+        for (var i = 0; i < im_fraction.length; i++)
+            im_fraction[i] = y.re_fraction[i];
+    }
+
+    public Number.polar (Number r, Number theta, AngleUnit unit = AngleUnit.RADIANS)
+    {
+        var x = theta.cos (unit);
+        var y = theta.sin (unit);
+        Number.complex (x.multiply (r), y.multiply (r));
+    }
+
+    public Number.eulers ()
+    {        
+        var z = new Number.integer (1).epowy ();
+        re_sign = z.re_sign;
+        im_sign = z.im_sign;
+        re_exponent = z.re_exponent;
+        im_exponent = z.im_exponent;
+        for (var i = 0; i < z.re_fraction.length; i++)
+        {
+            re_fraction[i] = z.re_fraction[i];
+            im_fraction[i] = z.im_fraction[i];
+        }
+    }
+
+    public Number.i ()
+    {
+        im_sign = 1;
+        im_exponent = 1;
+        im_fraction[0] = 1;
+    }
+
+    public Number.pi ()
+    {
+        // FIXME: Should generate PI to required accuracy
+        Number.double (Math.PI);
+    }
+
+    /* Sets z to be a uniform random number in the range [0, 1] */
+    public Number.random ()
+    {
+        Number.double (Random.next_double ());
+    }
+
+    public int64 to_integer ()
+    {
+        int64 z = 0;
+
+        /* |x| <= 1 */
+        if (re_sign == 0 || re_exponent <= 0)
+            return 0;
+
+        /* Multiply digits together */
+        for (var i = 0; i < re_exponent; i++)
+        {
+            var t = z;
+            z = z * BASE + re_fraction[i];
+
+            /* Check for overflow */
+            if (z <= t)
+                return 0;
+        }
+
+        /* Validate result */
+        var v = z;
+        for (var i = re_exponent - 1; i >= 0; i--)
+        {
+            /* Get last digit */
+            var digit = v - (v / BASE) * BASE;
+            if (re_fraction[i] != digit)
+                return 0;
+
+            v /= BASE;
+        }
+        if (v != 0)
+            return 0;
+
+        return re_sign * z;
+    }
+
+    public uint64 to_unsigned_integer ()
+    {
+        /* x <= 1 */
+        if (re_sign <= 0 || re_exponent <= 0)
+            return 0;
+
+        /* Multiply digits together */
+        uint64 z = 0;
+        for (var i = 0; i < re_exponent; i++)
+        {
+            var t = z;
+            z = z * BASE + re_fraction[i];
+
+            /* Check for overflow */
+            if (z <= t)
+                return 0;
+        }
+
+        /* Validate result */
+        var v = z;
+        for (var i = re_exponent - 1; i >= 0; i--)
+        {
+            /* Get last digit */
+            var digit = (uint64) v - (v / BASE) * BASE;
+            if (re_fraction[i] != digit)
+                return 0;
+
+            v /= BASE;
+        }
+        if (v != 0)
+            return 0;
+
+        return z;
+    }
+
+    public float to_float ()
+    {
+        if (is_zero ())
+            return 0f;
+
+        var z = 0f;
+        for (var i = 0; i < T; i++)
+        {
+            if (re_fraction[i] != 0)
+                z += re_fraction[i] * Math.powf (BASE, re_exponent - i - 1);
+        }
+
+        if (re_sign < 0)
+            return -z;
+        else
+            return z;
+    }
+
+    public double to_double ()
+    {
+        if (is_zero ())
+            return 0d;
+
+        var z = 0d;
+        for (var i = 0; i < T; i++)
+        {
+            if (re_fraction[i] != 0)
+                z += re_fraction[i] * Math.pow (BASE, re_exponent - i - 1);
+        }
+
+        if (re_sign < 0)
+            return -z;
+        else
+            return z;
+    }
+
+    /* Return true if the value is x == 0 */
+    public bool is_zero ()
+    {
+        return re_sign == 0 && im_sign == 0;
+    }
+
+    /* Return true if x < 0 */
+    public bool is_negative ()
+    {
+        return re_sign < 0;
+    }
+
+    /* Return true if x is integer */
+    public bool is_integer ()
+    {
+        if (is_complex ())
+            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
+         * floor () routine in mp.c, when the re_exponent is less than 1.
+         */
+        var t3 = new Number.integer (10000);
+        var t1 = multiply (t3);
+        t1 = t1.divide (t3);
+        var t2 = t1.floor ();
+        return t1.equals (t2);
+
+        /* Correct way to check for integer */
+        /*
+
+        // Zero is an integer
+        if (is_zero ())
+            return true;
+
+        // fractional
+        if (re_exponent <= 0)
+            return false;
+
+        // Look for fractional components
+        for (var i = re_exponent; i < SIZE; i++)
+        {
+            if (re_fraction[i] != 0)
+                return false;
+        }
+
+        return true;*/
+    }
+
+    /* Return true if x is a positive integer */
+    public bool is_positive_integer ()
+    {
+        if (is_complex ())
+            return false;
+        else
+            return re_sign >= 0 && is_integer ();
+    }
+
+    /* Return true if x is a natural number (an integer â 0) */
+    public bool is_natural ()
+    {
+        if (is_complex ())
+            return false;
+        else
+            return re_sign > 0 && is_integer ();
+    }
+
+    /* Return true if x has an imaginary component */
+    public bool is_complex ()
+    {
+        return im_sign != 0;
+    }
+
+    /* Return true if x == y */
+    public bool equals (Number y)
+    {
+        return compare (y) == 0;
+    }
+
+    /* Returns:
+     *  0 if x == y
+     * <0 if x < y
+     * >0 if x > y
+     */
+    public int compare (Number y)
+    {
+        if (re_sign != y.re_sign)
+        {
+            if (re_sign > y.re_sign)
+                return 1;
+            else
+                return -1;
+        }
+
+        /* x = y = 0 */
+        if (is_zero ())
+            return 0;
+
+        /* See if numbers are of different magnitude */
+        if (re_exponent != y.re_exponent)
+        {
+            if (re_exponent > y.re_exponent)
+                return re_sign;
+            else
+                return -re_sign;
+        }
+
+        /* Compare fractions */
+        for (var i = 0; i < SIZE; i++)
+        {
+            if (re_fraction[i] == y.re_fraction[i])
+                continue;
+
+            if (re_fraction[i] > y.re_fraction[i])
+                return re_sign;
+            else
+                return -re_sign;
+        }
+
+        /* x = y */
+        return 0;
+    }
+
+    /* Sets z = sgn (x) */
+    public Number sgn ()
+    {
+        if (is_zero ())
+            return new Number.integer (0);
+        else if (is_negative ())
+            return new Number.integer (-1);
+        else
+            return new Number.integer (1);
+    }
+
+    /* Sets z = âx */
+    public Number invert_sign ()
+    {
+        var z = copy ();
+
+        z.re_sign = -z.re_sign;
+        z.im_sign = -z.im_sign;
+
+        return z;
+    }
+
+    /* Sets z = |x| */
+    public Number abs ()
+    {
+        if (is_complex ())
+        {
+            var x_real = real_component ();
+            var x_im = imaginary_component ();
+
+            x_real = x_real.multiply (x_real);
+            x_im = x_im.multiply (x_im);
+            var z = x_real.add (x_im);
+            return z.root (2);
+        }
+        else
+        {
+            var z = copy ();
+            if (z.re_sign < 0)
+                z.re_sign = -z.re_sign;
+            return z;
+        }
+    }
+
+    /* Sets z = Arg (x) */
+    public Number arg (AngleUnit unit = AngleUnit.RADIANS)
+    {
+        if (is_zero ())
+        {
+            /* Translators: Error display when attempting to take argument of zero */
+            mperr (_("Argument not defined for zero"));
+            return new Number.integer (0);
+        }
+
+        var x_real = real_component ();
+        var x_im = imaginary_component ();
+        var pi = new Number.pi ();
+
+        Number z;
+        if (x_im.is_zero ())
+        {
+            if (x_real.is_negative ())
+                z = pi;
+            else
+                return new Number.integer (0);
+        }
+        else if (x_real.is_zero ())
+        {
+            if (x_im.is_negative ())
+                z = pi.divide_integer (-2);
+            else
+                z = pi.divide_integer (2);
+        }
+        else if (x_real.is_negative ())
+        {
+            z = x_im.divide (x_real);
+            z = z.atan (AngleUnit.RADIANS);
+            if (x_im.is_negative ())
+                z = z.subtract (pi);
+            else
+                z = z.add (pi);
+        }
+        else
+        {
+            z = x_im.divide (x_real);
+            z = z.atan (AngleUnit.RADIANS);
+        }
+
+        return z.from_radians (unit);
+    }
+
+    /* Sets z = âÌx */
+    public Number conjugate ()
+    {
+        var z = copy ();
+        z.im_sign = -z.im_sign;
+        return z;
+    }
+
+    /* Sets z = Re (x) */
+    public Number real_component ()
+    {
+        var z = copy ();
+
+        /* Clear imaginary component */
+        z.im_sign = 0;
+        z.im_exponent = 0;
+        for (var i = 0; i < z.im_fraction.length; i++)
+            z.im_fraction[i] = 0;
+
+        return z;
+    }
+
+    /* Sets z = Im (x) */
+    public Number imaginary_component ()
+    {
+        /* Copy imaginary component to real component */
+        var z = new Number ();
+        z.re_sign = im_sign;
+        z.re_exponent = im_exponent;
+
+        for (var i = 0; i < z.im_fraction.length; i++)
+            z.re_fraction[i] = im_fraction[i];
+
+        /* Clear (old) imaginary component */
+        z.im_sign = 0;
+        z.im_exponent = 0;
+        for (var i = 0; i < z.im_fraction.length; i++)
+            z.im_fraction[i] = 0;
+
+        return z;
+    }
+
+    public Number integer_component ()
+    {
+        /* Clear re_fraction */
+        var z = copy ();
+        for (var i = z.re_exponent; i < SIZE; i++)
+            z.re_fraction[i] = 0;
+        z.im_sign = 0;
+        z.im_exponent = 0;
+        for (var i = 0; i < z.im_fraction.length; i++)
+            z.im_fraction[i] = 0;
+
+        return z;
+    }
+
+    /* Sets z = x mod 1 */
+    public Number fractional_component ()
+    {
+        /* fractional component of zero is 0 */
+        if (is_zero ())
+            return new Number.integer (0);
+
+        /* All fractional */
+        if (re_exponent <= 0)
+            return this;
+
+        /* Shift fractional component */
+        var shift = re_exponent;
+        for (var i = shift; i < SIZE && re_fraction[i] == 0; i++)
+            shift++;
+        var z = new Number.integer (0);
+        z.re_sign = re_sign;
+        z.re_exponent = re_exponent - shift;
+        for (var i = 0; i < SIZE; i++)
+        {
+            if (i + shift >= SIZE)
+                z.re_fraction[i] = 0;
+            else
+                z.re_fraction[i] = re_fraction[i + shift];
+        }
+        if (z.re_fraction[0] == 0)
+            z.re_sign = 0;
+
+        return z;
+    }
+
+    /* Sets z = {x} */
+    public Number fractional_part ()
+    {
+        return subtract (floor ());
+    }
+
+    /* Sets z = âxâ */
+    public Number floor ()
+    {
+        /* Integer component of zero = 0 */
+        if (is_zero ())
+            return this;
+
+        /* If all fractional then no integer component */
+        if (re_exponent <= 0)
+        {
+            if (is_negative ())
+                return new Number.integer (-1);
+            else
+                return new Number.integer (0);
+        }
+
+        /* Clear fractional component */
+        var z = copy ();
+        var have_fraction = false;
+        for (var i = z.re_exponent; i < SIZE; i++)
+        {
+            if (z.re_fraction[i] != 0)
+                have_fraction = true;
+            z.re_fraction[i] = 0;
+        }
+        z.im_sign = 0;
+        z.im_exponent = 0;
+        for (var i = 0; i < z.im_fraction.length; i++)
+            z.im_fraction[i] = 0;
+
+        if (have_fraction && is_negative ())
+            z = z.add (new Number.integer (-1));
+
+        return z;
+    }
+
+    /* Sets z = âxâ */
+    public Number ceiling ()
+    {
+        var z = floor ();
+        var f = fractional_component ();
+        if (f.is_zero ())
+            return z;
+        return z.add (new Number.integer (1));
+    }
+
+    /* Sets z = [x] */
+    public Number round ()
+    {
+        var do_floor = !is_negative ();
+
+        var f = fractional_component ();
+        f = f.multiply_integer (2);
+        f = f.abs ();
+        if (f.compare (new Number.integer (1)) >= 0)
+            do_floor = !do_floor;
+
+        if (do_floor)
+            return floor ();
+        else
+            return ceiling ();
+    }
+
+    /* Sets z = 1 Ã x */
+    public Number reciprocal ()
+    {
+        if (is_complex ())
+        {
+            var real_x = real_component ();
+            var im_x = imaginary_component ();
+
+            /* 1/(a+bi) = (a-bi)/(a+bi)(a-bi) = (a-bi)/(aÂ+bÂ) */
+            var t1 = real_x.multiply (real_x);
+            var t2 = im_x.multiply (im_x);
+            t1 = t1.add (t2);
+            var z = t1.reciprocal_real ();
+            return conjugate ().multiply (z);
+        }
+        else
+            return reciprocal_real ();
+    }
+
+    /* Sets z = e^x */
+    public Number epowy ()
+    {
+        /* e^0 = 1 */
+        if (is_zero ())
+            return new Number.integer (1);
+
+        if (is_complex ())
+        {
+            var x_real = real_component ();
+            var theta = imaginary_component ();
+
+            var r = x_real.epowy_real ();
+            return new Number.polar (r, theta);
+        }
+        else
+            return epowy_real ();
+    }
+
+    /* Sets z = x^y */
+    public Number xpowy (Number y)
+    {
+        if (y.is_integer ())
+            return xpowy_integer (y.to_integer ());
+        else
+        {
+            var reciprocal = y.reciprocal ();
+            if (reciprocal.is_integer ())
+                return root (reciprocal.to_integer ());
+            else
+                return pwr (y);
+        }
+    }
+
+    /* Sets z = x^y */
+    public Number xpowy_integer (int64 n)
+    {
+        /* 0^-n invalid */
+        if (is_zero () && n < 0)
+        {
+            /* Translators: Error displayed when attempted to raise 0 to a negative re_exponent */
+            mperr (_("The power of zero is undefined for a negative exponent"));
+            return new Number.integer (0);
+        }
+
+        /* x^0 = 1 */
+        if (n == 0)
+            return new Number.integer (1);
+
+        /* 0^n = 0 */
+        if (is_zero ())
+            return new Number.integer (0);
+
+        /* x^1 = x */
+        if (n == 1)
+            return this;
+
+        Number t;
+        if (n < 0)
+        {
+            t = reciprocal ();
+            n = -n;
+        }
+        else
+            t = this;
+
+        /* Multply x n times */
+        // FIXME: Can do z = z.multiply (z) until close to answer (each call doubles number of multiples) */
+        var z = new Number.integer (1);
+        for (var i = 0; i < n; i++)
+            z = z.multiply (t);
+        return z;
+    }
+
+    /* Sets z = nâx */
+    public Number root (int64 n)
+    {
+        if (!is_complex () && is_negative () && n % 2 == 1)
+        {
+            var z = abs ();
+            z = z.root_real (n);
+            z = z.invert_sign ();
+            return z;
+        }
+        else if (is_complex () || is_negative ())
+        {
+            var r = abs ();
+            var theta = arg ();
+
+            r = r.root_real (n);
+            theta = theta.divide_integer (n);
+            return new Number.polar (r, theta);
+        }
+        else
+            return root_real (n);
+    }
+
+    /* Sets z = âx */
+    public Number sqrt ()
+    {
+        if (is_zero ())
+            return this;
+
+        /* FIXME: Make complex numbers optional */
+        /*if (re_sign < 0)
+        {
+            mperr (_("Square root is undefined for negative values"));
+            return new Number.integer (0);
+        }*/
+        else
+        {
+            var t = root (-2);
+            var z = multiply (t);
+            return z.ext (t.re_fraction[0], z.re_fraction[0]);
+        }
+    }
+
+    /* Sets z = ln x */
+    public Number ln ()
+    {
+        /* ln (0) undefined */
+        if (is_zero ())
+        {
+            /* Translators: Error displayed when attempting to take logarithm of zero */
+            mperr (_("Logarithm of zero is undefined"));
+            return new Number.integer (0);
+        }
+
+        /* ln (-x) complex */
+        /* FIXME: Make complex numbers optional */
+        /*if (is_negative ())
+        {
+            // Translators: Error displayed attempted to take logarithm of negative value
+            mperr (_("Logarithm of negative values is undefined"));
+            return new Number.integer (0);
+        }*/
+
+        if (is_complex () || is_negative ())
+        {
+            /* ln (re^iÎ) = e^(ln (r)+iÎ) */
+            var r = abs ();
+            var theta = arg ();
+            var z_real = r.ln_real ();
+
+            return new Number.complex (z_real, theta);
+        }
+        else
+            return ln_real ();
+    }
+
+    /* Sets z = log_n x */
+    public Number logarithm (int64 n)
+    {
+        /* log (0) undefined */
+        if (is_zero ())
+        {
+            /* Translators: Error displayed when attempting to take logarithm of zero */
+            mperr (_("Logarithm of zero is undefined"));
+            return new Number.integer (0);
+        }
+
+        /* logn (x) = ln (x) / ln (n) */
+        var t1 = new Number.integer (n);
+        return ln ().divide (t1.ln ());
+    }
+
+    /* Sets z = x! */
+    public Number factorial ()
+    {
+        /* 0! == 1 */
+        if (is_zero ())
+            return new Number.integer (1);
+        if (!is_natural ())
+        {
+            /* Translators: Error displayed when attempted take the factorial of a fractional number */
+            mperr (_("Factorial is only defined for natural numbers"));
+            return new Number.integer (0);
+        }
+
+        /* Convert to integer - if couldn't be converted then the factorial would be too big anyway */
+        var value = to_integer ();
+        var z = this;
+        for (var i = 2; i < value; i++)
+            z = z.multiply_integer (i);
+
+        return z;
+    }
+
+    /* Sets z = x + y */
+    public Number add (Number y)
+    {
+        return add_with_sign (1, y);
+    }
+
+    /* Sets z = x â y */
+    public Number subtract (Number y)
+    {
+        return add_with_sign (-1, y);
+    }
+
+    /* Sets z = x à y */
+    public Number multiply (Number y)
+    {
+        /* x*0 = 0*y = 0 */
+        if (is_zero () || y.is_zero ())
+            return new Number.integer (0);
+
+        /* (a+bi)(c+di) = (ac-bd)+(ad+bc)i */
+        if (is_complex () || y.is_complex ())
+        {
+            Number t1, t2, real_z, im_z;
+
+            var real_x = real_component ();
+            var im_x = imaginary_component ();
+            var real_y = y.real_component ();
+            var im_y = y.imaginary_component ();
+
+            t1 = real_x.multiply_real (real_y);
+            t2 = im_x.multiply_real (im_y);
+            real_z = t1.subtract (t2);
+
+            t1 = real_x.multiply_real (im_y);
+            t2 = im_x.multiply_real (real_y);
+            im_z = t1.add (t2);
+
+            return new Number.complex (real_z, im_z);
+        }
+        else
+            return multiply_real (y);
+    }
+
+    /* Sets z = x à y */
+    public Number multiply_integer (int64 y)
+    {
+        if (is_complex ())
+        {
+            var re_z = real_component ().multiply_integer_real (y);;
+            var im_z = imaginary_component ().multiply_integer_real (y);
+            return new Number.complex (re_z, im_z);
+        }
+        else
+            return multiply_integer_real (y);
+    }
+
+    /* Sets z = x à y */
+    public Number divide (Number y)
+    {
+        /* x/0 */
+        if (y.is_zero ())
+        {
+            /* Translators: Error displayed attempted to divide by zero */
+            mperr (_("Division by zero is undefined"));
+            return new Number.integer (0);
+        }
+
+        /* 0/y = 0 */
+        if (is_zero ())
+            return this;
+
+        /* z = x à yâ */
+        /* FIXME: Set re_exponent to zero to avoid overflow in multiply??? */
+        var t = y.reciprocal ();
+        var ie = t.re_exponent;
+        t.re_exponent = 0;
+        var i = t.re_fraction[0];
+        var z = multiply (t);
+        z = z.ext (i, z.re_fraction[0]);
+        z.re_exponent += ie;
+
+        return z;
+    }
+
+    /* Sets z = x à y */
+    public Number divide_integer (int64 y)
+    {
+        if (is_complex ())
+        {
+            var re_z = real_component ().divide_integer_real (y);
+            var im_z = imaginary_component ().divide_integer_real (y);
+            return new Number.complex (re_z, im_z);
+        }
+        else
+            return divide_integer_real (y);
+    }
+
+    /* Sets z = x mod y */
+    public Number modulus_divide (Number y)
+    {
+        if (!is_integer () || !y.is_integer ())
+        {
+            /* Translators: Error displayed when attemping to do a modulus division on non-integer numbers */
+            mperr (_("Modulus division is only defined for integers"));
+            return new Number.integer (0);
+        }
+
+        var t1 = divide (y).floor ();
+        var t2 = t1.multiply (y);
+        var z = subtract (t2);
+
+        t1 = new Number.integer (0);
+        if ((y.compare (t1) < 0 && z.compare (t1) > 0) || z.compare (t1) < 0)
+            z = z.add (y);
+            
+        return z;
+    }
+
+    /* Sets z = sin x */
+    public Number sin (AngleUnit unit = AngleUnit.RADIANS)
+    {
+        if (is_complex ())
+        {
+            var x_real = real_component ();
+            var x_im = imaginary_component ();
+
+            var z_real = x_real.sin_real (unit);
+            var t = x_im.cosh ();
+            z_real = z_real.multiply (t);
+
+            var z_im = x_real.cos_real (unit);
+            t = x_im.sinh ();
+            z_im = z_im.multiply (t);
+
+            return new Number.complex (z_real, z_im);
+        }
+        else
+            return sin_real (unit);
+    }
+
+    /* Sets z = cos x */
+    public Number cos (AngleUnit unit = AngleUnit.RADIANS)
+    {
+        if (is_complex ())
+        {
+            var x_real = real_component ();
+            var x_im = imaginary_component ();
+
+            var z_real = x_real.cos_real (unit);
+            var t = x_im.cosh ();
+            z_real = z_real.multiply (t);
+
+            var z_im = x_real.sin_real (unit);
+            t = x_im.sinh ();
+            z_im = z_im.multiply (t);
+            z_im = z_im.invert_sign ();
+
+            return new Number.complex (z_real, z_im);
+        }
+        else
+            return cos_real (unit);
+    }
+
+    /* Sets z = tan x */
+    public Number tan (AngleUnit unit = AngleUnit.RADIANS)
+    {
+        /* Check for undefined values */
+        var cos_x = cos (unit);
+        if (cos_x.is_zero ())
+        {
+            /* Translators: Error displayed when tangent value is undefined */
+            mperr (_("Tangent is undefined for angles that are multiples of Ï (180Â) from Ïâ2 (90Â)"));
+            return new Number.integer (0);
+        }
+
+        /* tan (x) = sin (x) / cos (x) */
+        return sin (unit).divide (cos_x);
+    }
+
+    /* Sets z = sinâ x */
+    public Number asin (AngleUnit unit = AngleUnit.RADIANS)
+    {
+        /* asinâÂ(0) = 0 */
+        if (is_zero ())
+            return new Number.integer (0);
+
+        /* sinâÂ(x) = tanâÂ(x / â(1 - xÂ)), |x| < 1 */
+        if (re_exponent <= 0)
+        {
+            var t1 = new Number.integer (1);
+            var t2 = t1;
+            t1 = t1.subtract (this);
+            t2 = t2.add (this);
+            t2 = t1.multiply (t2);
+            t2 = t2.root (-2);
+            var z = multiply (t2);
+            z = z.atan (unit);
+            return z;
+        }
+
+        /* sinâÂ(1) = Ï/2, sinâÂ(-1) = -Ï/2 */
+        var t2 = new Number.integer (re_sign);
+        if (equals (t2))
+        {
+            var z = new Number.pi ().divide_integer (2 * t2.re_sign);
+            return z.from_radians (unit);
+        }
+
+        /* Translators: Error displayed when inverse sine value is undefined */
+        mperr (_("Inverse sine is undefined for values outside [-1, 1]"));
+        return new Number.integer (0);
+    }
+
+    /* Sets z = cosâ x */
+    public Number acos (AngleUnit unit = AngleUnit.RADIANS)
+    {
+        var pi = new Number.pi ();
+        var t1 = new Number.integer (1);
+        var n1 = new Number.integer (-1);
+
+        Number z;
+        if (compare (t1) > 0 || compare (n1) < 0)
+        {
+            /* Translators: Error displayed when inverse cosine value is undefined */
+            mperr (_("Inverse cosine is undefined for values outside [-1, 1]"));
+            z = new Number.integer (0);
+        }
+        else if (is_zero ())
+            z = pi.divide_integer (2);
+        else if (equals (t1))
+            z = new Number.integer (0);
+        else if (equals (n1))
+            z = pi;
+        else
+        {
+            /* cosâÂ(x) = tanâÂ(â(1 - xÂ) / x) */
+            Number y;
+            var t2 = multiply (this);
+            t2 = t1.subtract (t2);
+            t2 = t2.sqrt ();
+            t2 = t2.divide (this);
+            y = t2.atan (AngleUnit.RADIANS);
+            if (re_sign > 0)
+                z = y;
+            else
+                z = y.add (pi);
+        }
+
+        return z.from_radians (unit);
+    }
+
+    /* Sets z = tanâ x */
+    public Number atan (AngleUnit unit = AngleUnit.RADIANS)
+    {
+        if (is_zero ())
+            return new Number.integer (0);
+
+        var t2 = this;
+        var rx = 0f;
+        if (re_exponent.abs () <= 2)
+            rx = to_float ();
+
+        /* REDUCE ARGUMENT IF NECESSARY BEFORE USING SERIES */
+        var q = 1;
+        Number z;
+        while (t2.re_exponent >= 0)
+        {
+            if (t2.re_exponent == 0 && 2 * (t2.re_fraction[0] + 1) <= BASE)
+                break;
+
+            q *= 2;
+
+            /* t = t / (â(t + 1) + 1) */
+            z = t2.multiply (t2);
+            z = z.add (new Number.integer (1));
+            z = z.sqrt ();
+            z = z.add (new Number.integer (1));
+            t2 = t2.divide (z);
+        }
+
+        /* USE POWER SERIES NOW ARGUMENT IN (-0.5, 0.5) */
+        z = t2;
+        var t1 = t2.multiply (t2);
+
+        /* SERIES LOOP.  REDUCE T IF POSSIBLE. */
+        for (var i = 1; ; i += 2)
+        {
+            if (T + 2 + t2.re_exponent <= 1)
+                break;
+
+            t2 = t2.multiply (t1).multiply_integer (-i).divide_integer (i + 2);
+
+            z = z.add (t2);
+            if (t2.is_zero ())
+                break;
+        }
+
+        /* CORRECT FOR ARGUMENT REDUCTION */
+        z = z.multiply_integer (q);
+
+        /*  CHECK THAT RELATIVE ERROR LESS THAN 0.01 UNLESS re_exponent
+         *  OF X IS LARGE (WHEN ATAN MIGHT NOT WORK)
+         */
+        if (re_exponent.abs () <= 2)
+        {
+            float ry = z.to_float ();
+            /* THE FOLLOWING MESSAGE MAY INDICATE THAT B**(T-1) IS TOO SMALL. */
+            if (Math.fabs (ry - Math.atan (rx)) >= Math.fabs (ry) * 0.01)
+                mperr ("*** ERROR OCCURRED IN ATAN, RESULT INCORRECT ***");
+        }
+
+        return z.from_radians (unit);
+    }
+
+    /* Sets z = sinh x */
+    public Number sinh ()
+    {
+        /* sinh (0) = 0 */
+        if (is_zero ())
+            return new Number.integer (0);
+
+        /* WORK WITH ABS (X) */
+        var abs_x = abs ();
+
+        /* If |x| < 1 USE EXP TO AVOID CANCELLATION, otherwise IF TOO LARGE EPOWY GIVES ERROR MESSAGE */
+        Number z;
+        if (abs_x.re_exponent <= 0)
+        {
+            /* ((e^|x| + 1) * (e^|x| - 1)) / e^|x| */
+            // FIXME: Solves to e^|x| - e^-|x|, why not lower branch always? */
+            var exp_x = abs_x.epowy ();
+            var a = exp_x.add (new Number.integer (1));
+            var b = exp_x.add (new Number.integer (-1));
+            z = a.multiply (b);
+            z = z.divide (exp_x);
+        }
+        else
+        {
+            /* e^|x| - e^-|x| */
+            var exp_x = abs_x.epowy ();
+            z = exp_x.reciprocal ();
+            z = exp_x.subtract (z);
+        }
+
+        /* DIVIDE BY TWO AND RESTORE re_sign */
+        z = z.divide_integer (2);
+        return z.multiply_integer (re_sign);
+    }
+
+    /* Sets z = cosh x */
+    public Number cosh ()
+    {
+        /* cosh (0) = 1 */
+        if (is_zero ())
+            return new Number.integer (1);
+
+        /* cosh (x) = (e^x + e^-x) / 2 */
+        var t = abs ();
+        t = t.epowy ();
+        var z = t.reciprocal ();
+        z = t.add (z);
+        return z.divide_integer (2);
+    }
+
+    /* Sets z = tanh x */
+    public Number tanh ()
+    {
+        /* tanh (0) = 0 */
+        if (is_zero ())
+            return new Number.integer (0);
+
+        var t = abs ();
+
+        /* SEE IF ABS (X) SO LARGE THAT RESULT IS +-1 */
+        var r__1 = (float) T * 0.5 * Math.log ((float) BASE);
+        var z = new Number.double (r__1);
+        if (t.compare (z) > 0)
+            return new Number.integer (re_sign);
+
+        /* If |x| >= 1/2 use ?, otherwise use ? to avoid cancellation */
+        /* |tanh (x)| = (e^|2x| - 1) / (e^|2x| + 1) */
+        t = t.multiply_integer (2);
+        if (t.re_exponent > 0)
+        {
+            t = t.epowy ();
+            z = t.add (new Number.integer (-1));
+            t = t.add (new Number.integer (1));
+            z = z.divide (t);
+        }
+        else
+        {
+            t = t.epowy ();
+            z = t.add (new Number.integer (1));
+            t = t.add (new Number.integer (-1));
+            z = t.divide (z);
+        }
+
+        /* Restore re_sign */
+        z.re_sign = re_sign * z.re_sign;
+        return z;
+    }
+
+    /* Sets z = sinhâ x */
+    public Number asinh ()
+    {
+        /* sinhâÂ(x) = ln (x + â(x + 1)) */
+        var t = multiply (this);
+        t = t.add (new Number.integer (1));
+        t = t.sqrt ();
+        t = add (t);
+        return t.ln ();
+    }
+
+    /* Sets z = coshâ x */
+    public Number acosh ()
+    {
+        /* Check x >= 1 */
+        var t = new Number.integer (1);
+        if (compare (t) < 0)
+        {
+            /* Translators: Error displayed when inverse hyperbolic cosine value is undefined */
+            mperr (_("Inverse hyperbolic cosine is undefined for values less than one"));
+            return new Number.integer (0);
+        }
+
+        /* coshâÂ(x) = ln (x + â(x - 1)) */
+        t = multiply (this);
+        t = t.add (new Number.integer (-1));
+        t = t.sqrt ();
+        t = add (t);
+        return t.ln ();
+    }
+
+    /* Sets z = tanhâ x */
+    public Number atanh ()
+    {
+        /* Check -1 <= x <= 1 */
+        if (compare (new Number.integer (1)) >= 0 || compare (new Number.integer (-1)) <= 0)
+        {
+            /* Translators: Error displayed when inverse hyperbolic tangent value is undefined */
+            mperr (_("Inverse hyperbolic tangent is undefined for values outside [-1, 1]"));
+            return new Number.integer (0);
+        }
+
+        /* atanh (x) = 0.5 * ln ((1 + x) / (1 - x)) */
+        var n = add (new Number.integer (1));
+        var d = invert_sign ();
+        d = d.add (new Number.integer (1));
+        var z = n.divide (d);
+        z = z.ln ();
+        return z.divide_integer (2);
+    }
+
+    /* Sets z = boolean AND for each bit in x and z */
+    public Number and (Number y)
+    {
+        if (!is_positive_integer () || !y.is_positive_integer ())
+        {
+            /* Translators: Error displayed when boolean AND attempted on non-integer values */
+            mperr (_("Boolean AND is only defined for positive integers"));
+        }
+
+        return bitwise (y, (v1, v2) => { return v1 & v2; }, 0);
+    }
+
+    /* Sets z = boolean OR for each bit in x and z */
+    public Number or (Number y)
+    {
+        if (!is_positive_integer () || !y.is_positive_integer ())
+        {
+            /* Translators: Error displayed when boolean OR attempted on non-integer values */
+            mperr (_("Boolean OR is only defined for positive integers"));
+        }
+
+        return bitwise (y, (v1, v2) => { return v1 | v2; }, 0);
+    }
+
+    /* Sets z = boolean XOR for each bit in x and z */
+    public Number xor (Number y)
+    {
+        if (!is_positive_integer () || !y.is_positive_integer ())
+        {
+            /* Translators: Error displayed when boolean XOR attempted on non-integer values */
+            mperr (_("Boolean XOR is only defined for positive integers"));
+        }
+
+        return bitwise (y, (v1, v2) => { return v1 ^ v2; }, 0);
+    }
+
+    /* Sets z = boolean NOT for each bit in x and z for word of length 'wordlen' */
+    public Number not (int wordlen)
+    {
+        if (!is_positive_integer ())
+        {
+            /* Translators: Error displayed when boolean XOR attempted on non-integer values */
+            mperr (_("Boolean NOT is only defined for positive integers"));
+        }
+
+        return bitwise (new Number.integer (0), (v1, v2) => { return v1 ^ 0xF; }, wordlen);
+    }
+
+    /* Sets z = x masked to 'wordlen' bits */
+    public Number mask (Number x, int wordlen)
+    {
+        /* Convert to a hexadecimal string and use last characters */
+        var text = x.to_hex_string ();
+        var len = text.length;
+        var offset = wordlen / 4;
+        offset = len > offset ? (int) len - offset: 0;
+        return mp_set_from_string (text.substring (offset), 16);
+    }
+
+    /* Sets z = x shifted by 'count' bits.  Positive shift increases the value, negative decreases */
+    public Number shift (int count)
+    {
+        if (!is_integer ())
+        {
+            /* Translators: Error displayed when bit shift attempted on non-integer values */
+            mperr (_("Shift is only possible on integer values"));
+            return new Number.integer (0);
+        }
+
+        if (count >= 0)
+        {
+            var multiplier = 1;
+            for (var i = 0; i < count; i++)
+                multiplier *= 2;
+            return multiply_integer (multiplier);
+        }
+        else
+        {
+            var multiplier = 1;
+            for (var i = 0; i < -count; i++)
+                multiplier *= 2;
+            return divide_integer (multiplier).floor ();
+        }
+    }
+
+    /* Sets z to be the ones complement of x for word of length 'wordlen' */
+    public Number ones_complement (int wordlen)
+    {
+        return bitwise (new Number.integer (0), (v1, v2) => { return v1 ^ v2; }, wordlen).not (wordlen);
+    }
+
+    /* Sets z to be the twos complement of x for word of length 'wordlen' */
+    public Number twos_complement (int wordlen)
+    {
+        return ones_complement (wordlen).add (new Number.integer (1));
+    }
+
+    /* Returns a list of all prime factors in x as Numbers */
+    public List<Number?> factorize ()
+    {
+        var factors = new List<Number?> ();
+
+        var value = abs ();
+
+        if (value.is_zero ())
+        {
+            factors.append (value);
+            return factors;
+        }
+
+        if (value.equals (new Number.integer (1)))
+        {
+            factors.append (this);
+            return factors;
+        }
+
+        var divisor = new Number.integer (2);
+        while (true)
+        {
+            var tmp = value.divide (divisor);
+            if (tmp.is_integer ())
+            {
+                value = tmp;
+                factors.append (divisor);
+            }
+            else
+                break;
+        }
+
+        divisor = new Number.integer (3);
+        var root = value.sqrt ();
+        while (divisor.compare (root) <= 0)
+        {
+            var tmp = value.divide (divisor);
+            if (tmp.is_integer ())
+            {
+                value = tmp;
+                root = value.sqrt ();
+                factors.append (divisor);
+            }
+            else
+            {
+                tmp = divisor.add (new Number.integer (2));
+                divisor = tmp;
+            }
+        }
+
+        if (value.compare (new Number.integer (1)) > 0)
+            factors.append (value);
+
+        if (is_negative ())
+            factors.data = factors.data.invert_sign ();
+
+        return factors;
+    }
+
+    private Number copy ()
+    {
+        var z = new Number ();
+        z.re_sign = re_sign;
+        z.im_sign = im_sign;
+        z.re_exponent = re_exponent;
+        z.im_exponent = im_exponent;
+        for (var i = 0; i < re_fraction.length; i++)
+        {
+            z.re_fraction[i] = re_fraction[i];
+            z.im_fraction[i] = im_fraction[i];
+        }
+
+        return z;
+    }
+
+    private Number add_with_sign (int y_sign, Number y)
+    {
+        if (is_complex () || y.is_complex ())
+        {
+            Number real_z, im_z;
+
+            var real_x = real_component ();
+            var im_x = imaginary_component ();
+            var real_y = y.real_component ();
+            var im_y = y.imaginary_component ();
+
+            real_z = real_x.add_real (y_sign * y.re_sign, real_y);
+            im_z = im_x.add_real (y_sign * y.im_sign, im_y);
+
+            return new Number.complex (real_z, im_z);
+        }
+        else
+            return add_real (y_sign * y.re_sign, y);
+    }
+
+    private Number epowy_real ()
+    {
+        /* e^0 = 1 */
+        if (is_zero ())
+            return new Number.integer (1);
+
+        /* If |x| < 1 use exp */
+        if (re_exponent <= 0)
+            return exp ();
+
+        /* NOW SAFE TO CONVERT X TO REAL */
+        var rx = to_double ();
+
+        /* SAVE re_sign AND WORK WITH ABS (X) */
+        var xs = re_sign;
+        var t2 = abs ();
+
+        /* GET fractional AND INTEGER PARTS OF ABS (X) */
+        var ix = t2.to_integer ();
+        t2 = t2.fractional_component ();
+
+        /* ATTACH re_sign TO fractional PART AND COMPUTE EXP OF IT */
+        t2.re_sign *= xs;
+        var z = t2.exp ();
+
+        /*  COMPUTE E-2 OR 1/E USING TWO EXTRA DIGITS IN CASE ABS (X) LARGE
+         *  (BUT ONLY ONE EXTRA DIGIT IF T < 4)
+         */
+        var tss = 0;
+        if (T < 4)
+            tss = T + 1;
+        else
+            tss = T + 2;
+
+        /* LOOP FOR E COMPUTATION. DECREASE T IF POSSIBLE. */
+        /* Computing MIN */
+        var t1 = new Number.integer (xs);
+
+        t2.re_sign = 0;
+        for (var i = 2 ; ; i++)
+        {
+            if (int.min (tss, tss + 2 + t1.re_exponent) <= 2)
+                break;
+
+            t1 = t1.divide_integer (i * xs);
+            t2 = t2.add (t1);
+            if (t1.is_zero ())
+                break;
+        }
+
+        /* RAISE E OR 1/E TO POWER IX */
+        if (xs > 0)
+            t2 = t2.add (new Number.integer (2));
+        t2 = t2.xpowy_integer (ix);
+
+        /* MULTIPLY EXPS OF INTEGER AND fractional PARTS */
+        z = z.multiply (t2);
+
+        /*  CHECK THAT RELATIVE ERROR LESS THAN 0.01 UNLESS ABS (X) LARGE
+         *  (WHEN EXP MIGHT OVERFLOW OR UNDERFLOW)
+         */
+        if (Math.fabs (rx) > 10.0f)
+            return z;
+
+        var rz = z.to_double ();
+        var r__1 = rz - Math.exp (rx);
+        if (Math.fabs (r__1) < rz * 0.01f)
+            return z;
+
+        /*  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 EPOWY, RESULT INCORRECT ***");
+        return z;
+    }
+
+    /*  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.
+     */
+    private Number exp ()
+    {
+        /* e^0 = 1 */
+        if (is_zero ())
+            return new Number.integer (1);
+
+        /* Only defined for |x| < 1 */
+        if (re_exponent > 0)
+        {
+            mperr ("*** ABS (X) NOT LESS THAN 1 IN CALL TO MP_EXP ***");
+            return new Number.integer (0);
+        }
+
+        var t1 = this;
+        var rlb = Math.log (BASE);
+
+        /* Compute approximately optimal q (and divide x by 2^q) */
+        var q = (int) (Math.sqrt (T * 0.48 * rlb) + re_exponent * 1.44 * rlb);
+
+        /* HALVE Q TIMES */
+        if (q > 0)
+        {
+            var ib = BASE << 2;
+            var ic = 1;
+            for (var i = 1; i <= q; i++)
+            {
+                ic *= 2;
+                if (ic < ib && ic != BASE && i < q)
+                    continue;
+                t1 = t1.divide_integer (ic);
+                ic = 1;
+            }
+        }
+
+        if (t1.is_zero ())
+            return new Number.integer (0);
+
+        /* Sum series, reducing t where possible */
+        var z = t1.copy ();
+        var t2 = t1;
+        for (var i = 2; T + t2.re_exponent - z.re_exponent > 0; i++)
+        {
+            t2 = t1.multiply (t2);
+            t2 = t2.divide_integer (i);
+            z = t2.add (z);
+            if (t2.is_zero ())
+                break;
+        }
+
+        /* Apply (x+1)^2 - 1 = x (2 + x) for q iterations */
+        for (var i = 1; i <= q; i++)
+        {
+            t1 = z.add (new Number.integer (2));
+            z = t1.multiply (z);
+        }
+
+        return z.add (new Number.integer (1));    
+    }
+
+    private Number pwr (Number y)
+    {
+        /* (-x)^y imaginary */
+        /* FIXME: Make complex numbers optional */
+        /*if (re_sign < 0)
+        {
+            mperr (_("The power of negative numbers is only defined for integer exponents"));
+            return new Number.integer (0);
+        }*/
+
+        /* 0^y = 0, 0^-y undefined */
+        if (is_zero ())
+        {
+            if (y.re_sign < 0)
+                mperr (_("The power of zero is undefined for a negative exponent"));
+            return new Number.integer (0);
+        }
+
+        /* x^0 = 1 */
+        if (y.is_zero ())
+            return new Number.integer (1);
+
+        return y.multiply (ln ()).epowy ();
+    }
+
+    private Number root_real (int64 n)
+    {
+        /* x^(1/1) = x */
+        if (n == 1)
+            return this;
+
+        /* x^(1/0) invalid */
+        if (n == 0)
+        {
+            mperr (_("Root must be non-zero"));
+            return new Number.integer (0);
+        }
+
+        var np = n.abs ();
+
+        /* LOSS OF ACCURACY IF NP LARGE, SO ONLY ALLOW NP <= MAX (B, 64) */
+        if (np > int.max (BASE, 64))
+        {
+            mperr ("*** ABS (N) TOO LARGE IN CALL TO ROOT ***");
+            return new Number.integer (0);
+        }
+
+        /* 0^(1/n) = 0 for positive n */
+        if (is_zero ())
+        {
+            if (n <= 0)
+                mperr (_("Negative root of zero is undefined"));
+            return new Number.integer (0);
+        }
+
+        // FIXME: Imaginary root
+        if (re_sign < 0 && np % 2 == 0)
+        {
+            mperr (_("nth root of negative number is undefined for even n"));
+            return new Number.integer (0);
+        }
+
+        /* DIVIDE re_exponent BY NP */
+        var ex = re_exponent / np;
+
+        /* Get initial approximation */
+        var t1 = copy ();
+        t1.re_exponent = 0;
+        var approximation = Math.exp (((np * ex - re_exponent) * Math.log (BASE) - Math.log (Math.fabs (t1.to_float ()))) / np);
+        t1 = new Number.double (approximation);
+        t1.re_sign = re_sign;
+        t1.re_exponent -= (int) ex;
+
+        /* MAIN ITERATION LOOP */
+        var it0 = 3;
+        var t = it0;
+        Number t2;
+        while (true)
+        {
+            /* t1 = t1 - ((t1 * ((x * t1^np) - 1)) / np) */
+            t2 = t1.xpowy_integer (np);
+            t2 = multiply (t2);
+            t2 = t2.add (new Number.integer (-1));
+            t2 = t1.multiply (t2);
+            t2 = t2.divide_integer (np);
+            t1 = t1.subtract (t2);
+
+            /*  FOLLOWING LOOP ALMOST DOUBLES T (POSSIBLE BECAUSE
+             *  NEWTONS METHOD HAS 2ND ORDER CONVERGENCE).
+             */
+            if (t >= T)
+                break;
+
+            var ts3 = t;
+            var ts2 = t;
+            t = T;
+            do
+            {
+                ts2 = t;
+                t = (t + it0) / 2;
+            } while (t > ts3);
+            t = int.min (ts2, T);
+        }
+
+        /*  NOW r (I2) IS X**(-1/NP)
+         *  CHECK THAT NEWTON ITERATION WAS CONVERGING
+         */
+        if (t2.re_sign != 0 && (t1.re_exponent - t2.re_exponent) << 1 < 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 ROOT, NEWTON ITERATION NOT CONVERGING PROPERLY ***");
+        }
+
+        if (n >= 0)
+        {
+            t1 = t1.xpowy_integer (n - 1);
+            return multiply (t1);
+        }
+
+        return t1;
+    }
+
+    /*  ROUTINE CALLED BY DIVIDE AND 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.
+     */
+    private Number ext (int i, int j)
+    {
+        if (is_zero () || T <= 2 || i == 0)
+            return this;
+
+        /* COMPUTE MAXIMUM POSSIBLE ERROR IN THE LAST PLACE */
+        var q = (j + 1) / i + 1;
+        var s = BASE * re_fraction[T - 2] + re_fraction[T - 1];
+
+        /* SET LAST TWO DIGITS TO ZERO */
+        var z = copy ();
+        if (s <= q)
+        {
+            z.re_fraction[T - 2] = 0;
+            z.re_fraction[T - 1] = 0;
+            return z;
+        }
+
+        if (s + q < BASE * BASE)
+            return z;
+
+        /* ROUND UP HERE */
+        z.re_fraction[T - 2] = BASE - 1;
+        z.re_fraction[T - 1] = BASE;
+
+        /* NORMALIZE X (LAST DIGIT B IS OK IN MULTIPLY_INTEGER) */
+        return z.multiply_integer (1);
+    }
+
+    private Number ln_real ()
+    {
+        /* LOOP TO GET APPROXIMATE Ln (X) USING SINGLE-PRECISION */
+        var t1 = copy ();
+        var z = new Number.integer (0);
+        for (var k = 0; k < 10; k++)
+        {
+            /* COMPUTE FINAL CORRECTION ACCURATELY USING LNS */
+            var t2 = t1.add (new Number.integer (-1));
+            if (t2.is_zero () || t2.re_exponent + 1 <= 0)
+                return z.add (t2.lns ());
+
+            /* REMOVE EXPONENT TO AVOID FLOATING-POINT OVERFLOW */
+            var e = t1.re_exponent;
+            t1.re_exponent = 0;
+            var rx = t1.to_float_old ();
+            t1.re_exponent = e;
+            var rlx = (float) (Math.log (rx) + e * Math.log (BASE));
+            t2 = new Number.float (-(float)rlx);
+
+            /* UPDATE Z AND COMPUTE ACCURATE EXP OF APPROXIMATE LOG */
+            z = z.subtract (t2);
+            t2 = t2.epowy ();
+
+            /* COMPUTE RESIDUAL WHOSE LOG IS STILL TO BE FOUND */
+            t1 = t1.multiply (t2);
+        }
+
+        mperr ("*** ERROR IN LN, ITERATION NOT CONVERGING ***");
+        return z;
+    }
+
+    // FIXME: This is here becase ln e breaks if we use the symmetric to_float
+    private float to_float_old ()
+    {
+        if (is_zero ())
+            return 0f;
+
+        var z = 0f;
+        var i = 0;
+        for (; i < T; i++)
+        {
+            z = BASE * z + re_fraction[i];
+
+            /* CHECK IF FULL SINGLE-PRECISION ACCURACY ATTAINED */
+            if (z + 1.0f <= z)
+                break;
+        }
+
+        /* NOW ALLOW FOR EXPONENT */
+        z = (float) (z * mppow_ri (BASE, re_exponent - i - 1));
+
+        if (re_sign < 0)
+            return -z;
+        else
+            return z;
+    }
+
+    private double mppow_ri (float ap, int bp)
+    {
+        if (bp == 0)
+            return 1.0f;
+
+        if (bp < 0)
+        {
+            if (ap == 0)
+                return 1.0f;
+            bp = -bp;
+            ap = 1 / ap;
+        }
+
+        var pow = 1.0;
+        while (true)
+        {
+            if ((bp & 01) != 0)
+                pow *= ap;
+            if ((bp >>= 1) != 0)
+                ap *= ap;
+            else
+                break;
+        }
+
+        return pow;
+    }
+
+    /*  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 re_sign OF Y.
+     */
+    private Number lns ()
+    {
+        /* ln (1+0) = 0 */
+        if (is_zero ())
+            return this;
+
+        /* Get starting approximation -ln (1+x) ~= -x + x^2/2 - x^3/3 + x^4/4 */
+        var t2 = copy ();
+        var t1 = divide_integer (4);
+        t1 = t1.add (new Number.fraction (-1, 3));
+        t1 = multiply (t1);
+        t1 = t1.add (new Number.fraction (1, 2));
+        t1 = multiply (t1);
+        t1 = t1.add (new Number.integer (-1));
+        var z = multiply (t1);
+
+        /* Solve using Newtons method */
+        var it0 = 5;
+        var t = it0;
+        Number t3;
+        while (true)
+        {
+            /* t3 = (e^t3 - 1) */
+            /* z = z - (t2 + t3 + (t2 * t3)) */
+            t3 = z.epowy ();
+            t3 = t3.add (new Number.integer (-1));
+            t1 = t2.multiply (t3);
+            t3 = t3.add (t1);
+            t3 = t2.add (t3);
+            z = z.subtract (t3);
+            if (t >= 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.
+             */
+            var ts3 = t;
+            var ts2 = t;
+            t = T;
+            do
+            {
+                ts2 = t;
+                t = (t + it0) / 2;
+            } while (t > ts3);
+            t = ts2;
+        }
+
+        /* CHECK THAT NEWTON ITERATION WAS CONVERGING AS EXPECTED */
+        if (t3.re_sign != 0 && t3.re_exponent << 1 > it0 - T)
+            mperr ("*** ERROR OCCURRED IN LNS, NEWTON ITERATION NOT CONVERGING PROPERLY ***");
+
+        z.re_sign = -z.re_sign;
+
+        return z;
+    }
+
+    private Number add_real (int y_sign, Number y)
+    {
+        var re_sign_prod = y_sign * re_sign;
+
+        /* 0 + y = y */
+        if (is_zero ())
+        {
+            if (y_sign != y.re_sign)
+                return y.invert_sign ();
+            else
+                return y;
+        }
+        /* x + 0 = x */
+        else if (y.is_zero ())
+            return this;
+
+        var exp_diff = re_exponent - y.re_exponent;
+        var med = exp_diff.abs ();
+        var x_largest = false;
+        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 (re_sign_prod < 0)
+            {
+                /* signs are not equal.  find out which mantissa is larger. */
+                int j;
+                for (j = 0; j < T; j++)
+                {
+                    int i = re_fraction[j] - y.re_fraction[j];
+                    if (i != 0)
+                    {
+                        if (i < 0)
+                            x_largest = false;
+                        else if (i > 0)
+                            x_largest = true;
+                        break;
+                    }
+                }
+
+                /* Both mantissas equal, so result is zero. */
+                if (j >= T)
+                    return new Number.integer (0);
+            }
+        }
+
+        var z = new Number.integer (0);
+
+        int[] big_fraction, small_fraction;
+        if (x_largest)
+        {
+            z.re_sign = re_sign;
+            z.re_exponent = re_exponent;
+            big_fraction = re_fraction;
+            small_fraction = y.re_fraction;
+        }
+        else
+        {
+            z.re_sign = y_sign;
+            z.re_exponent = y.re_exponent;
+            big_fraction = y.re_fraction;
+            small_fraction = re_fraction;
+        }
+
+        /* CLEAR GUARD DIGITS TO RIGHT OF X DIGITS */
+        for (var i = 3; i >= med; i--)
+            z.re_fraction[T + i] = 0;
+
+        if (re_sign_prod >= 0)
+        {
+            /* This is probably insufficient overflow detection, but it makes us not crash at least. */
+            if (T + 3 < med)
+            {
+                mperr (_("Overflow: the result couldn't be calculated"));
+                return new Number.integer (0);
+            }
+
+            /* HERE DO ADDITION, re_exponent (Y) >= re_exponent (X) */
+            var i = 0;
+            for (i = T + 3; i >= T; i--)
+                z.re_fraction[i] = small_fraction[i - med];
+
+            var c = 0;
+            for (; i >= med; i--)
+            {
+                c = big_fraction[i] + small_fraction[i - med] + c;
+
+                if (c < BASE)
+                {
+                    /* NO CARRY GENERATED HERE */
+                    z.re_fraction[i] = c;
+                    c = 0;
+                }
+                else
+                {
+                    /* CARRY GENERATED HERE */
+                    z.re_fraction[i] = c - BASE;
+                    c = 1;
+                }
+            }
+
+            for (; i >= 0; i--)
+            {
+                c = big_fraction[i] + c;
+                if (c < BASE)
+                {
+                    z.re_fraction[i] = c;
+                    i--;
+
+                    /* NO CARRY POSSIBLE HERE */
+                    for (; i >= 0; i--)
+                        z.re_fraction[i] = big_fraction[i];
+
+                    c = 0;
+                    break;
+                }
+
+                z.re_fraction[i] = 0;
+                c = 1;
+            }
+
+            /* MUST SHIFT RIGHT HERE AS CARRY OFF END */
+            if (c != 0)
+            {
+                for (var j = T + 3; j > 0; j--)
+                    z.re_fraction[j] = z.re_fraction[j - 1];
+                z.re_fraction[0] = 1;
+                z.re_exponent++;
+            }
+        }
+        else
+        {
+            var c = 0;
+            var i = 0;
+            for (i = T + med - 1; i >= T; i--)
+            {
+                /* HERE DO SUBTRACTION, ABS (Y) > ABS (X) */
+                z.re_fraction[i] = c - small_fraction[i - med];
+                c = 0;
+
+                /* BORROW GENERATED HERE */
+                if (z.re_fraction[i] < 0)
+                {
+                    c = -1;
+                    z.re_fraction[i] += BASE;
+                }
+            }
+
+            for (; i >= med; i--)
+            {
+                c = big_fraction[i] + c - small_fraction[i - med];
+                if (c >= 0)
+                {
+                    /* NO BORROW GENERATED HERE */
+                    z.re_fraction[i] = c;
+                    c = 0;
+                }
+                else
+                {
+                    /* BORROW GENERATED HERE */
+                    z.re_fraction[i] = c + BASE;
+                    c = -1;
+                }
+            }
+
+            for (; i >= 0; i--)
+            {
+                c = big_fraction[i] + c;
+
+                if (c >= 0)
+                {
+                    z.re_fraction[i] = c;
+                    i--;
+
+                    /* NO CARRY POSSIBLE HERE */
+                    for (; i >= 0; i--)
+                        z.re_fraction[i] = big_fraction[i];
+
+                    break;
+                }
+
+                z.re_fraction[i] = c + BASE;
+                c = -1;
+            }
+        }
+
+        mp_normalize (ref z);
+
+        return z;
+    }
+
+    private Number multiply_real (Number y)
+    {
+        /* x*0 = 0*y = 0 */
+        if (re_sign == 0 || y.re_sign == 0)
+            return new Number.integer (0);
+
+        var z = new Number.integer (0);
+        z.re_sign = re_sign * y.re_sign;
+        z.re_exponent = re_exponent + y.re_exponent;
+
+        var r = new Number.integer (0);
+
+        /* PERFORM MULTIPLICATION */
+        var c = 8;
+        for (var i = 0; i < T; i++)
+        {
+            var xi = re_fraction[i];
+
+            /* FOR SPEED, PUT THE NUMBER WITH MANY ZEROS FIRST */
+            if (xi == 0)
+                continue;
+
+            /* Computing MIN */
+            for (var j = 0; j < int.min (T, T + 3 - i); j++)
+                r.re_fraction[i+j+1] += xi * y.re_fraction[j];
+            c--;
+            if (c > 0)
+                continue;
+
+            /* CHECK FOR LEGAL BASE B DIGIT */
+            if (xi < 0 || xi >= BASE)
+            {
+                mperr ("*** ILLEGAL BASE B DIGIT IN CALL TO MULTIPLY, POSSIBLE OVERWRITING PROBLEM ***");
+                return new Number.integer (0);
+            }
+
+            /*  PROPAGATE CARRIES AT END AND EVERY EIGHTH TIME,
+             *  FASTER THAN DOING IT EVERY TIME.
+             */
+            for (var j = T + 3; j >= 0; j--)
+            {
+                int ri = r.re_fraction[j] + c;
+                if (ri < 0)
+                {
+                    mperr ("*** INTEGER OVERFLOW IN MULTIPLY, B TOO LARGE ***");
+                    return new Number.integer (0);
+                }
+                c = ri / BASE;
+                r.re_fraction[j] = ri - BASE * c;
+            }
+            if (c != 0)
+            {
+                mperr ("*** ILLEGAL BASE B DIGIT IN CALL TO MULTIPLY, POSSIBLE OVERWRITING PROBLEM ***");
+                return new Number.integer (0);
+            }
+            c = 8;
+        }
+
+        if (c != 8)
+        {
+            c = 0;
+            for (var i = T + 3; i >= 0; i--)
+            {
+                int ri = r.re_fraction[i] + c;
+                if (ri < 0)
+                {
+                    mperr ("*** INTEGER OVERFLOW IN MULTIPLY, B TOO LARGE ***");
+                    return new Number.integer (0);
+                }
+                c = ri / BASE;
+                r.re_fraction[i] = ri - BASE * c;
+            }
+
+            if (c != 0)
+            {
+                mperr ("*** ILLEGAL BASE B DIGIT IN CALL TO MULTIPLY, POSSIBLE OVERWRITING PROBLEM ***");
+                return new Number.integer (0);
+            }
+        }
+
+        /* Clear complex part */
+        z.im_sign = 0;
+        z.im_exponent = 0;
+        for (var i = 0; i < z.im_fraction.length; i++)
+            z.im_fraction[i] = 0;
+
+        /* NORMALIZE AND ROUND RESULT */
+        // FIXME: Use stack variable because of mp_normalize brokeness
+        for (var i = 0; i < SIZE; i++)
+            z.re_fraction[i] = r.re_fraction[i];
+        mp_normalize (ref z);
+
+        return z;
+    }
+
+    private Number multiply_integer_real (int64 y)
+    {
+        /* x*0 = 0*y = 0 */
+        if (is_zero () || y == 0)
+            return new Number.integer (0);
+
+        /* 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)
+                z = invert_sign ();
+            else
+                z = this;
+            return z;
+        }*/
+
+        /* Copy x as z may also refer to x */
+        var z = new Number.integer (0);
+
+        if (y < 0)
+        {
+            y = -y;
+            z.re_sign = -re_sign;
+        }
+        else
+            z.re_sign = re_sign;
+        z.re_exponent = re_exponent + 4;
+
+        /* FORM PRODUCT IN ACCUMULATOR */
+        int64 c = 0;
+
+        /*  IF y*B NOT REPRESENTABLE AS AN INTEGER WE HAVE TO SIMULATE
+         *  DOUBLE-PRECISION MULTIPLICATION.
+         */
+
+        /* Computing MAX */
+        if (y >= int.max (BASE << 3, 32767 / BASE))
+        {
+            /* HERE J IS TOO LARGE FOR SINGLE-PRECISION MULTIPLICATION */
+            var j1 = y / BASE;
+            var j2 = y - j1 * BASE;
+
+            /* FORM PRODUCT */
+            for (var i = T + 3; i >= 0; i--)
+            {
+                var c1 = c / BASE;
+                var c2 = c - BASE * c1;
+                var ix = 0;
+                if (i > 3)
+                    ix = re_fraction[i - 4];
+
+                var t = j2 * ix + c2;
+                var is = t / BASE;
+                c = j1 * ix + c1 + is;
+                z.re_fraction[i] = (int) (t - BASE * is);
+            }
+        }
+        else
+        {
+            int64 ri = 0;
+            for (var i = T + 3; i >= 4; i--)
+            {
+                ri = y * re_fraction[i - 4] + c;
+                c = ri / BASE;
+                z.re_fraction[i] = (int) (ri - BASE * c);
+            }
+
+            /* CHECK FOR INTEGER OVERFLOW */
+            if (ri < 0)
+            {
+                mperr ("*** INTEGER OVERFLOW IN multiply_integer, B TOO LARGE ***");
+                return new Number.integer (0);
+            }
+
+            /* HAVE TO TREAT FIRST FOUR WORDS OF R SEPARATELY */
+            for (var i = 3; i >= 0; i--)
+            {
+                var t = c;
+                c = t / BASE;
+                z.re_fraction[i] = (int) (t - BASE * c);
+            }
+        }
+
+        /* HAVE TO SHIFT RIGHT HERE AS CARRY OFF END */
+        while (c != 0)
+        {
+            for (var i = T + 3; i >= 1; i--)
+                z.re_fraction[i] = z.re_fraction[i - 1];
+            var t = c;
+            c = t / BASE;
+            z.re_fraction[0] = (int) (t - BASE * c);
+            z.re_exponent++;
+        }
+
+        if (c < 0)
+        {
+            mperr ("*** INTEGER OVERFLOW IN multiply_integer, B TOO LARGE ***");
+            return new Number.integer (0);
+        }
+
+        z.im_sign = 0;
+        z.im_exponent = 0;
+        for (var i = 0; i < z.im_fraction.length; i++)
+            z.im_fraction[i] = 0;
+        mp_normalize (ref z);
+
+        return z;
+    }
+
+    private Number reciprocal_real ()
+    {
+        /* 1/0 invalid */
+        if (is_zero ())
+        {
+            mperr (_("Reciprocal of zero is undefined"));
+            return new Number.integer (0);
+        }
+
+        /* Start by approximating value using floating point */
+        var t1 = copy ();
+        t1.re_exponent = 0;
+        t1 = new Number.double (1.0 / t1.to_double ());
+        t1.re_exponent -= re_exponent;
+
+        var t = 3;
+        var it0 = t;
+        Number t2;
+        while (true)
+        {
+            /* t1 = t1 - (t1 * ((x * t1) - 1))   (2*t1 - t1^2*x) */
+            t2 = multiply (t1);
+            t2 = t2.add (new Number.integer (-1));
+            t2 = t1.multiply (t2);
+            t1 = t1.subtract (t2);
+            if (t >= T)
+                break;
+
+            /*  FOLLOWING LOOP ALMOST DOUBLES T (POSSIBLE
+             *  BECAUSE NEWTONS METHOD HAS 2ND ORDER CONVERGENCE).
+             */
+            var ts3 = t;
+            var ts2 = 0;
+            t = T;
+            do
+            {
+                ts2 = t;
+                t = (t + it0) / 2;
+            } while (t > ts3);
+            t = int.min (ts2, T);
+        }
+
+        /* RETURN IF NEWTON ITERATION WAS CONVERGING */
+        if (t2.re_sign != 0 && (t1.re_exponent - t2.re_exponent) << 1 < 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 RECIPROCAL, NEWTON ITERATION NOT CONVERGING PROPERLY ***");
+        }
+
+        return t1;
+    }
+
+    private Number divide_integer_real (int64 y)
+    {
+        /* x/0 */
+        if (y == 0)
+        {
+            /* Translators: Error displayed attempted to divide by zero */
+            mperr (_("Division by zero is undefined"));
+            return new Number.integer (0);
+        }
+
+        /* 0/y = 0 */
+        if (is_zero ())
+            return new Number.integer (0);
+
+        /* Division by -1 or 1 just changes re_sign */
+        if (y == 1 || y == -1)
+        {
+            if (y < 0)
+                return invert_sign ();
+            else
+                return this;
+        }
+
+        var z = new Number.integer (0);
+        if (y < 0)
+        {
+            y = -y;
+            z.re_sign = -re_sign;
+        }
+        else
+            z.re_sign = re_sign;
+        z.re_exponent = re_exponent;
+
+        int64 c = 0;
+        int64 i = 0;
+
+        /*  IF y*B NOT REPRESENTABLE AS AN INTEGER HAVE TO SIMULATE
+         *  LONG DIVISION.  ASSUME AT LEAST 16-BIT WORD.
+         */
+
+        /* Computing MAX */
+        var b2 = int.max (BASE << 3, 32767 / BASE);
+        if (y < b2)
+        {
+            /* LOOK FOR FIRST NONZERO DIGIT IN QUOTIENT */
+            int64 r1 = 0;
+            do
+            {
+                c = BASE * c;
+                if (i < T)
+                    c += re_fraction[i];
+                i++;
+                r1 = c / y;
+                if (r1 < 0)
+                {
+                    mperr ("*** INTEGER OVERFLOW IN DIVIDE_INTEGER, B TOO LARGE ***");
+                    return new Number.integer (0);
+                }
+            } while (r1 == 0);
+
+            /* ADJUST re_exponent AND GET T+4 DIGITS IN QUOTIENT */
+            z.re_exponent += (int) (1 - i);
+            z.re_fraction[0] = (int) r1;
+            c = BASE * (c - y * r1);
+            int64 kh = 1;
+            if (i < T)
+            {
+                kh = T + 1 - i;
+                for (var k = 1; k < kh; k++)
+                {
+                    c += re_fraction[i];
+                    z.re_fraction[k] = (int) (c / y);
+                    c = BASE * (c - y * z.re_fraction[k]);
+                    i++;
+                }
+                if (c < 0)
+                {
+                    mperr ("*** INTEGER OVERFLOW IN DIVIDE_INTEGER, B TOO LARGE ***");
+                    return new Number.integer (0);
+                }
+            }
+
+            for (var k = kh; k < T + 4; k++)
+            {
+                z.re_fraction[k] = (int) (c / y);
+                c = BASE * (c - y * z.re_fraction[k]);
+            }
+            if (c < 0)
+            {
+                mperr ("*** INTEGER OVERFLOW IN DIVIDE_INTEGER, B TOO LARGE ***");
+                return new Number.integer (0);
+            }
+
+            mp_normalize (ref z);
+            return z;
+        }
+
+        /* HERE NEED SIMULATED DOUBLE-PRECISION DIVISION */
+        var j1 = y / BASE;
+        var j2 = y - j1 * BASE;
+
+        /* LOOK FOR FIRST NONZERO DIGIT */
+        var c2 = 0;
+        do
+        {
+            c = BASE * c + c2;
+            c2 = i < T ? re_fraction[i] : 0;
+            i++;
+        } while (c < j1 || (c == j1 && c2 < j2));
+
+        /* COMPUTE T+4 QUOTIENT DIGITS */
+        z.re_exponent += (int) (1 - i);
+        i--;
+
+        /* MAIN LOOP FOR LARGE ABS (y) CASE */
+        for (var k = 1; k <= T + 4; k++)
+        {
+            /* GET APPROXIMATE QUOTIENT FIRST */
+            var ir = c / (j1 + 1);
+
+            /* NOW REDUCE SO OVERFLOW DOES NOT OCCUR */
+            var iq = c - ir * j1;
+            if (iq >= b2)
+            {
+                /* HERE IQ*B WOULD POSSIBLY OVERFLOW SO INCREASE IR */
+                ir++;
+                iq -= j1;
+            }
+
+            iq = iq * BASE - ir * j2;
+            if (iq < 0)
+            {
+                /* HERE IQ NEGATIVE SO IR WAS TOO LARGE */
+                ir--;
+                iq += y;
+            }
+
+            if (i < T)
+                iq += re_fraction[i];
+            i++;
+            var iqj = iq / y;
+
+            /* r (K) = QUOTIENT, C = REMAINDER */
+            z.re_fraction[k - 1] = (int) (iqj + ir);
+            c = iq - y * iqj;
+
+            if (c < 0)
+            {
+                /* CARRY NEGATIVE SO OVERFLOW MUST HAVE OCCURRED */
+                mperr ("*** INTEGER OVERFLOW IN DIVIDE_INTEGER, B TOO LARGE ***");
+                return new Number.integer (0);
+            }
+        }
+
+        mp_normalize (ref z);
+
+        /* CARRY NEGATIVE SO OVERFLOW MUST HAVE OCCURRED */
+        mperr ("*** INTEGER OVERFLOW IN DIVIDE_INTEGER, B TOO LARGE ***");
+        return new Number.integer (0);
+    }
+
+    private Number from_radians (AngleUnit unit)
+    {
+        switch (unit)
+        {
+        default:
+        case AngleUnit.RADIANS:
+            return this;
+
+        case AngleUnit.DEGREES:
+            return multiply_integer (180).divide (new Number.pi ());
+
+        case AngleUnit.GRADIANS:
+            return multiply_integer (200).divide (new Number.pi ());
+        }
+    }
+
+    /* Convert x to radians */
+    private Number to_radians (AngleUnit unit)
+    {
+        switch (unit)
+        {
+        default:
+        case AngleUnit.RADIANS:
+            return this;
+
+        case AngleUnit.DEGREES:
+            return multiply (new Number.pi ()).divide_integer (180);
+
+        case AngleUnit.GRADIANS:
+            return multiply (new Number.pi ()).divide_integer (200);
+        }
+    }
+
+    /* z = sin (x) -1 >= x >= 1, do_sin = 1
+     * z = cos (x) -1 >= x >= 1, do_sin = 0
+     */
+    private Number sin1 (bool do_sin)
+    {
+        /* sin (0) = 0, cos (0) = 1 */
+        if (is_zero ())
+        {
+            if (do_sin)
+                return new Number.integer (0);
+            else
+                return new Number.integer (1);
+        }
+
+        var t2 = multiply (this);
+        if (t2.compare (new Number.integer (1)) > 0)
+            mperr ("*** ABS (X) > 1 IN CALL TO SIN1 ***");
+
+        Number t1;
+        int i;
+        Number z;
+        if (do_sin)
+        {
+            t1 = this;
+            z = t1;
+            i = 2;
+        }
+        else
+        {
+            t1 = new Number.integer (1);
+            z = new Number.integer (0);
+            i = 1;
+        }
+
+        /* Taylor series */
+        /* POWER SERIES LOOP.  REDUCE T IF POSSIBLE */
+        var b2 = 2 * int.max (BASE, 64);
+        do
+        {
+            if (T + t1.re_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.
+             */
+            t1 = t2.multiply (t1);
+            if (i > b2)
+            {
+                t1 = t1.divide_integer (-i);
+                t1 = t1.divide_integer (i + 1);
+            }
+            else
+                t1 = t1.divide_integer (-i * (i + 1));
+            z = t1.add (z);
+
+            i += 2;
+        } while (t1.re_sign != 0);
+
+        if (!do_sin)
+            z = z.add (new Number.integer (1));
+
+        return z;
+    }
+
+    private Number sin_real (AngleUnit unit)
+    {
+        /* sin (0) = 0 */
+        if (is_zero ())
+            return new Number.integer (0);
+
+        var x_radians = to_radians (unit);
+
+        var xs = x_radians.re_sign;
+        x_radians = x_radians.abs ();
+
+        /* USE SIN1 IF ABS (X) <= 1 */
+        Number z;
+        if (x_radians.compare (new Number.integer (1)) <= 0)
+            z = x_radians.sin1 (true);
+        /* FIND ABS (X) MODULO 2PI */
+        else
+        {
+            z = new Number.pi ().divide_integer (4);
+            x_radians = x_radians.divide (z);
+            x_radians = x_radians.divide_integer (8);
+            x_radians = x_radians.fractional_component ();
+
+            /* SUBTRACT 1/2, SAVE re_sign AND TAKE ABS */
+            x_radians = x_radians.add (new Number.fraction (-1, 2));
+            xs = -xs * x_radians.re_sign;
+            if (xs == 0)
+                return new Number.integer (0);
+
+            x_radians.re_sign = 1;
+            x_radians = x_radians.multiply_integer (4);
+
+            /* IF NOT LESS THAN 1, SUBTRACT FROM 2 */
+            if (x_radians.re_exponent > 0)
+                x_radians = x_radians.add (new Number.integer (-2));
+
+            if (x_radians.is_zero ())
+                return new Number.integer (0);
+
+            x_radians.re_sign = 1;
+            x_radians = x_radians.multiply_integer (2);
+
+            /*  NOW REDUCED TO FIRST QUADRANT, IF LESS THAN PI/4 USE
+             *  POWER SERIES, ELSE COMPUTE COS OF COMPLEMENT
+             */
+            if (x_radians.re_exponent > 0)
+            {
+                x_radians = x_radians.add (new Number.integer (-2));
+                x_radians = x_radians.multiply (z);
+                z = x_radians.sin1 (false);
+            }
+            else
+            {
+                x_radians = x_radians.multiply (z);
+                z = x_radians.sin1 (true);
+            }
+        }
+
+        z.re_sign = xs;
+        return z;
+    }
+
+    private Number cos_real (AngleUnit unit)
+    {
+        /* cos (0) = 1 */
+        if (is_zero ())
+            return new Number.integer (1);
+
+        /* Use power series if |x| <= 1 */
+        var z = to_radians (unit).abs ();
+        if (z.compare (new Number.integer (1)) <= 0)
+            return z.sin1 (false);
+        else
+            /* cos (x) = sin (Ï/2 - |x|) */
+            return new Number.pi ().divide_integer (2).subtract (z).sin (AngleUnit.RADIANS);
+    }
+
+    private Number bitwise (Number y, BitwiseFunc bitwise_operator, int wordlen)
+    {
+        var text1 = to_hex_string ();
+        var text2 = y.to_hex_string ();
+        var offset1 = text1.length - 1;
+        var offset2 = text2.length - 1;
+        var 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 new Number.integer (0);
+        }
+
+        var text_out = new char[offset_out + 1];
+
+        /* 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;
+            const char digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+            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)];
+        }
+
+        return mp_set_from_string ((string) text_out, 16);
+    }
+
+    private 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;
+    }
+
+    private string to_hex_string ()
+    {
+        var serializer = new Serializer (DisplayFormat.FIXED, 16, 0);
+        return serializer.to_string (this);
+    }
+}
+
+// FIXME: Should all be in the class
+
+// FIXME: Re-add overflow and underflow detection
+
+static string? mp_error = null;
+
+/*  THIS ROUTINE IS CALLED WHEN AN ERROR CONDITION IS ENCOUNTERED, AND
+ *  AFTER A MESSAGE HAS BEEN WRITTEN TO STDERR.
+ */
+public void mperr (string text)
+{
+    mp_error = text;
+}
+
+/* Returns error string or null if no error */
+// FIXME: Global variable
+public string mp_get_error ()
+{
+    return mp_error;
+}
+
+/* Clear any current error */
+public void mp_clear_error ()
+{
+    mp_error = null;
+}
+
+/* Sets z from a string representation in 'text'. */
+public Number? mp_set_from_string (string str, int default_base = 10)
+{
+    if (str.index_of_char ('Â') >= 0)
+        return set_from_sexagesimal (str);
+
+    /* Find the base */
+    const unichar base_digits[] = {'â', 'â', 'â', 'â', 'â', 'â', 'â', 'â', 'â', 'â'};
+    var index = 0;
+    unichar c;
+    while (str.get_next_char (ref index, out c));
+    var end = index;
+    var number_base = 0;
+    var base_multiplier = 1;
+    while (str.get_prev_char (ref index, out c))
+    {
+        var value = -1;
+        for (var i = 0; i < base_digits.length; i++)
+        {
+            if (c == base_digits[i])
+            {
+                value = i;
+                break;
+            }
+        }
+        if (value < 0)
+            break;
+
+        end = index;
+        number_base += value * base_multiplier;
+        base_multiplier *= 10;
+    }
+    if (base_multiplier == 1)
+        number_base = default_base;
+
+    /* Check if this has a sign */
+    var negate = false;
+    index = 0;
+    str.get_next_char (ref index, out c);
+    if (c == '+')
+        negate = false;
+    else if (c == '-' || c == 'â')
+        negate = true;
+    else
+        str.get_prev_char (ref index, out c);
+
+    /* Convert integer part */
+    var z = new Number.integer (0);
+    while (str.get_next_char (ref index, out c))
+    {
+        var i = char_val (c, number_base);
+        if (i > number_base)
+            return null;
+        if (i < 0)
+        {
+            str.get_prev_char (ref index, out c);
+            break;
+        }
+
+        z = z.multiply_integer (number_base).add (new Number.integer (i));
+    }
+
+    /* Look for fraction characters, e.g. â */
+    const unichar fractions[] = {'Â', 'â', 'â', 'Â', 'Â', 'â', 'â', 'â', 'â', 'â', 'â', 'â', 'â', 'â', 'â'};
+    const int numerators[]    = { 1,   1,   2,   1,   3,   1,   2,   3,   4,   1,   5,   1,   3,   5,   7};
+    const int denominators[]  = { 2,   3,   3,   4,   4,   5,   5,   5,   5,   6,   6,   8,   8,   8,   8};
+    var has_fraction = false;
+    if (str.get_next_char (ref index, out c))
+    {
+        for (var i = 0; i < fractions.length; i++)
+        {
+            if (c == fractions[i])
+            {
+                var fraction = new Number.fraction (numerators[i], denominators[i]);
+                z = z.add (fraction);
+
+                /* Must end with fraction */
+                if (!str.get_next_char (ref index, out c))
+                    return z;
+                else
+                    return null;
+            }
+        }
+
+        /* Check for decimal point */
+        if (c == '.')
+            has_fraction = true;
+        else
+            str.get_prev_char (ref index, out c);
+    }
+
+    /* Convert fractional part */
+    if (has_fraction)
+    {
+        var numerator = new Number.integer (0);
+        var denominator = new Number.integer (1);
+
+        while (str.get_next_char (ref index, out c))
+        {
+            var i = char_val (c, number_base);
+            if (i < 0)
+            {
+                str.get_prev_char (ref index, out c);
+                break;
+            }
+
+            denominator = denominator.multiply_integer (number_base);
+            numerator = numerator.multiply_integer (number_base);
+            numerator = numerator.add (new Number.integer (i));
+        }
+
+        numerator = numerator.divide (denominator);
+        z = z.add (numerator);
+    }
+
+    if (index != end)
+        return null;
+
+    if (negate)
+        z = z.invert_sign ();
+
+    return z;
+}
+
+private int char_val (unichar c, int number_base)
+{
+    if (!c.isxdigit ())
+        return -1;
+
+    var value = c.xdigit_value ();
+
+    if (value >= number_base)
+        return -1;
+
+    return value;
+}
+
+private Number? set_from_sexagesimal (string str)
+{
+    var degree_index = str.index_of_char ('Â');
+    if (degree_index < 0)
+        return null;
+    var degrees = mp_set_from_string (str.substring (0, degree_index));
+    if (degrees == null)
+        return null;
+    var minute_start = degree_index;
+    unichar c;
+    str.get_next_char (ref minute_start, out c);
+
+    if (str[minute_start] == '\0')
+        return degrees;
+    var minute_index = str.index_of_char ('\'', minute_start);
+    if (minute_index < 0)
+        return null;
+    var minutes = mp_set_from_string (str.substring (minute_start, minute_index - minute_start));
+    if (minutes == null)
+        return null;
+    degrees = degrees.add (minutes.divide_integer (60));
+    var second_start = minute_index;
+    str.get_next_char (ref second_start, out c);
+
+    if (str[second_start] == '\0')
+        return degrees;
+    var second_index = str.index_of_char ('"', second_start);
+    if (second_index < 0)
+        return null;
+    var seconds = mp_set_from_string (str.substring (second_start, second_index - second_start));
+    if (seconds == null)
+        return null;
+    degrees = degrees.add (seconds.divide_integer (3600));
+    str.get_next_char (ref second_index, out c);
+
+    /* Skip over second marker and expect no more characters */
+    if (str[second_index] == '\0')
+        return degrees;
+    else
+        return null;
+}
+
+/*  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
+ */
+public void mp_gcd (ref int64 k, ref int64 l)
+{
+    var i = k.abs ();
+    var j = l.abs ();
+    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;
+}
+
+// FIXME: Is r.re_fraction large enough?  It seems to be in practise but it may be T+4 instead of 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)
+public void mp_normalize (ref Number x)
+{
+    int start_index;
+
+    /* Find first non-zero digit */
+    for (start_index = 0; start_index < SIZE && x.re_fraction[start_index] == 0; start_index++);
+
+    /* Mark as zero */
+    if (start_index >= SIZE)
+    {
+        x.re_sign = 0;
+        x.re_exponent = 0;
+        return;
+    }
+
+    /* Shift left so first digit is non-zero */
+    if (start_index > 0)
+    {
+        x.re_exponent -= start_index;
+        var i = 0;
+        for (; (i + start_index) < SIZE; i++)
+            x.re_fraction[i] = x.re_fraction[i + start_index];
+        for (; i < SIZE; i++)
+            x.re_fraction[i] = 0;
+    }
+}
+
+/* Returns true if x is cannot be represented in a binary word of length 'wordlen' */
+public bool mp_is_overflow (Number x, int wordlen)
+{
+    var t2 = new Number.integer (2).xpowy_integer (wordlen);
+    return t2.compare (x) > 0;
+}
diff --git a/src/serializer.vala b/src/serializer.vala
new file mode 100644
index 0000000..fc389df
--- /dev/null
+++ b/src/serializer.vala
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2010 Robin Sonefors
+ * Copyright (C) 2008-2012 Robert Ancell.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+public enum DisplayFormat
+{
+    AUTOMATIC,
+    FIXED,
+    SCIENTIFIC,
+    ENGINEERING
+}
+
+public class Serializer : Object
+{
+    private int leading_digits;      /* Number of digits to show before radix */
+    private int trailing_digits;     /* Number of digits to show after radix */
+    private DisplayFormat format;    /* Number display mode. */
+    private bool show_tsep;          /* Set if the thousands separator should be shown. */
+    private bool show_zeroes;        /* Set if trailing zeroes should be shown. */
+
+    private int number_base;         /* Numeric base */
+
+    private unichar radix;           /* Locale specific radix string. */
+    private unichar tsep;            /* Locale specific thousands separator. */
+    private int tsep_count;          /* Number of digits between separator. */
+
+    public Serializer (DisplayFormat format, int number_base, int trailing_digits)
+    {
+        var radix_string = nl_langinfo (NLItem.RADIXCHAR);
+        if (radix_string != null && radix_string != "")
+            radix = radix_string.locale_to_utf8 (-1, null, null).get_char (0);
+        else
+            radix = '.';
+        var tsep_string = nl_langinfo (NLItem.THOUSEP);
+        if (tsep_string != null && tsep_string != "")
+            tsep = tsep_string.locale_to_utf8 (-1, null, null).get_char (0);
+        else
+            tsep = ' ';
+        tsep_count = 3;
+
+        this.number_base = number_base;
+        leading_digits = 12;
+        this.trailing_digits = trailing_digits;
+        show_zeroes = false;
+        show_tsep = false;
+        this.format = format;
+    }
+
+    public string to_string (Number x)
+    {
+        switch (format)
+        {
+        default:
+        case DisplayFormat.AUTOMATIC:
+            int n_digits = 0;
+            var s0 = cast_to_string (x, ref n_digits);
+            if (n_digits <= leading_digits)
+                return s0;
+            else
+                return cast_to_exponential_string (x, false, ref n_digits);
+        case DisplayFormat.FIXED:
+            int n_digits = 0;
+            return cast_to_string (x, ref n_digits);
+        case DisplayFormat.SCIENTIFIC:
+            int n_digits = 0;
+            return cast_to_exponential_string (x, false, ref n_digits);
+        case DisplayFormat.ENGINEERING:
+            int n_digits = 0;
+            return cast_to_exponential_string (x, true, ref n_digits);
+        }
+    }
+
+    public Number? from_string (string str)
+    {
+        // FIXME: Move mp_set_from_string into here
+        return mp_set_from_string (str, number_base);
+    }
+
+    public void set_base (int number_base)
+    {
+        this.number_base = number_base;
+    }
+
+    public int get_base ()
+    {
+        return number_base;
+    }
+
+    public void set_radix (unichar radix)
+    {
+        this.radix = radix;
+    }
+
+    public unichar get_radix ()
+    {
+        return radix;
+    }
+
+    public void set_thousands_separator (unichar separator)
+    {
+        tsep = separator;
+    }
+
+    public unichar get_thousands_separator ()
+    {
+        return tsep;
+    }
+
+    public int get_thousands_separator_count ()
+    {
+        return tsep_count;
+    }
+
+    public void set_show_thousands_separators (bool visible)
+    {
+        show_tsep = visible;
+    }
+
+    public bool get_show_thousands_separators ()
+    {
+        return show_tsep;
+    }
+
+    public void set_show_trailing_zeroes (bool visible)
+    {
+        show_zeroes = visible;
+    }
+
+    public bool get_show_trailing_zeroes ()
+    {
+        return show_zeroes;
+    }
+
+    public int get_leading_digits ()
+    {
+        return leading_digits;
+    }
+
+    public void set_leading_digits (int leading_digits)
+    {
+        this.leading_digits = leading_digits;
+    }
+
+    public int get_trailing_digits ()
+    {
+        return trailing_digits;
+    }
+
+    public void set_trailing_digits (int trailing_digits)
+    {
+        this.trailing_digits = trailing_digits;
+    }
+
+    public DisplayFormat get_number_format ()
+    {
+        return format;
+    }
+
+    public void set_number_format (DisplayFormat format)
+    {
+        this.format = format;
+    }
+
+    private string cast_to_string (Number x, ref int n_digits)
+    {
+        var string = new StringBuilder.sized (1024);
+
+        var x_real = x.real_component ();
+        cast_to_string_real (x_real, number_base, false, ref n_digits, string);
+        if (x.is_complex ())
+        {
+            var x_im = x.imaginary_component ();
+
+            var force_sign = true;
+            if (string.str == "0")
+            {
+                string.assign ("");
+                force_sign = false;
+            }
+
+            var s = new StringBuilder.sized (1024);
+            int n_complex_digits = 0;
+            cast_to_string_real (x_im, 10, force_sign, ref n_complex_digits, s);
+            if (n_complex_digits > n_digits)
+                n_digits = n_complex_digits;
+            if (s.str == "0" || s.str == "+0" || s.str == "â0")
+            {
+                /* Ignore */
+            }
+            else if (s.str == "1")
+            {
+                string.append ("i");
+            }
+            else if (s.str == "+1")
+            {
+                string.append ("+i");
+            }
+            else if (s.str == "â1")
+            {
+                string.append ("âi");
+            }
+            else
+            {
+                if (s.str == "+0")
+                    string.append ("+");
+                else if (s.str != "0")
+                    string.append (s.str);
+
+                string.append ("i");
+            }
+        }
+
+        return string.str;
+    }
+
+    private void cast_to_string_real (Number x, int number_base, bool force_sign, ref int n_digits, StringBuilder string)
+    {
+        const char digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+        var number = x;
+        if (number.is_negative ())
+            number = number.abs ();
+
+        /* Add rounding factor */
+        var temp = new Number.integer (number_base);
+        temp = temp.xpowy_integer (-(trailing_digits+1));
+        temp = temp.multiply_integer (number_base);
+        temp = temp.divide_integer (2);
+        var rounded_number = number.add (temp);
+
+        /* Write out the integer component least significant digit to most */
+        temp = rounded_number.floor ();
+        var i = 0;
+        do
+        {
+            if (this.number_base == 10 && show_tsep && i == tsep_count)
+            {
+                string.prepend_unichar (tsep);
+                i = 0;
+            }
+            i++;
+
+            var t = temp.divide_integer (number_base);
+            t = t.floor ();
+            var t2 = t.multiply_integer (number_base);
+
+            var t3 = temp.subtract (t2);
+
+            var d = t3.to_integer ();
+            string.prepend_c (d < 16 ? digits[d] : '?');
+            n_digits++;
+
+            temp = t;
+        } while (!temp.is_zero ());
+
+        var last_non_zero = string.len;
+
+        string.append_unichar (radix);
+
+        /* Write out the fractional component */
+        temp = rounded_number.fractional_component ();
+        for (i = 0; i < trailing_digits; i++)
+        {
+            if (temp.is_zero ())
+                break;
+
+            temp = temp.multiply_integer (number_base);
+            var digit = temp.floor ();
+            var d = digit.to_integer ();
+
+            string.append_c (digits[d]);
+
+            if (d != 0)
+                last_non_zero = string.len;
+            temp = temp.subtract (digit);
+        }
+
+        /* Strip trailing zeroes */
+        if (!show_zeroes || trailing_digits == 0)
+            string.truncate (last_non_zero);
+
+        /* Add sign on non-zero values */
+        if (string.str != "0" || force_sign)
+        {
+            if (x.is_negative ())
+                string.prepend ("â");
+            else if (force_sign)
+                string.prepend ("+");
+        }
+
+        /* Append base suffix if not in default base */
+        if (number_base != this.number_base)
+        {
+            const string sub_digits[] = {"â", "â", "â", "â", "â", "â", "â", "â", "â", "â"};
+            int multiplier = 1;
+            int b = number_base;
+
+            while (number_base / multiplier != 0)
+                multiplier *= 10;
+            while (multiplier != 1)
+            {
+                int d;
+                multiplier /= 10;
+                d = b / multiplier;
+                string.append (sub_digits[d]);
+                b -= d * multiplier;
+            }
+        }
+    }
+
+    private int cast_to_exponential_string_real (Number x, StringBuilder string, bool eng_format, ref int n_digits)
+    {
+        if (x.is_negative ())
+            string.append ("â");
+
+        var mantissa = x.abs ();
+
+        var base_ = new Number.integer (number_base);
+        var base3 = base_.xpowy_integer (3);
+        var base10 = base_.xpowy_integer (10);
+        var t = new Number.integer (1);
+        var base10inv = t.divide (base10);
+
+        var exponent = 0;
+        if (!mantissa.is_zero ())
+        {
+            while (!eng_format && mantissa.compare (base10) >= 0)
+            {
+                exponent += 10;
+                mantissa = mantissa.multiply (base10inv);
+            }
+
+            while ((!eng_format && mantissa.compare (base_) >= 0) ||
+                    (eng_format && (mantissa.compare (base3) >= 0 || exponent % 3 != 0)))
+            {
+                exponent += 1;
+                mantissa = mantissa.divide (base_);
+            }
+
+            while (!eng_format && mantissa.compare (base10inv) < 0)
+            {
+                exponent -= 10;
+                mantissa = mantissa.multiply (base10);
+            }
+
+            t = new Number.integer (1);
+            while (mantissa.compare (t) < 0 || (eng_format && exponent % 3 != 0))
+            {
+                exponent -= 1;
+                mantissa = mantissa.multiply (base_);
+            }
+        }
+
+        string.append (cast_to_string (mantissa, ref n_digits));
+
+        return exponent;
+    }
+
+    private string cast_to_exponential_string (Number x, bool eng_format, ref int n_digits)
+    {
+        var string = new StringBuilder.sized (1024);
+
+        var x_real = x.real_component ();
+        var exponent = cast_to_exponential_string_real (x_real, string, eng_format, ref n_digits);
+        append_exponent (string, exponent);
+
+        if (x.is_complex ())
+        {
+            var x_im = x.imaginary_component ();
+
+            if (string.str == "0")
+                string.assign ("");
+
+            var s = new StringBuilder.sized (1024);
+            int n_complex_digits = 0;
+            exponent = cast_to_exponential_string_real (x_im, s, eng_format, ref n_complex_digits);
+            if (n_complex_digits > n_digits)
+                n_digits = n_complex_digits;
+            if (s.str == "0" || s.str == "+0" || s.str == "â0")
+            {
+                /* Ignore */
+            }
+            else if (s.str == "1")
+            {
+                string.append ("i");
+            }
+            else if (s.str == "+1")
+            {
+                string.append ("+i");
+            }
+            else if (s.str == "â1")
+            {
+                string.append ("âi");
+            }
+            else
+            {
+                if (s.str == "+0")
+                    string.append ("+");
+                else if (s.str != "0")
+                    string.append (s.str);
+
+                string.append ("i");
+            }
+            append_exponent (string, exponent);
+        }
+
+        return string.str;
+    }
+
+    private void append_exponent (StringBuilder string, int exponent)
+    {
+        const unichar super_digits[] = {'â', 'Â', 'Â', 'Â', 'â', 'â', 'â', 'â', 'â', 'â'};
+
+        if (exponent == 0)
+            return;
+
+        string.append ("Ã10"); // FIXME: Use the current base
+        if (exponent < 0)
+        {
+            exponent = -exponent;
+            string.append_unichar ('â');
+        }
+
+        var super_value = "%d".printf (exponent);
+        for (var i = 0; i < super_value.length; i++)
+            string.append_unichar (super_digits[super_value[i] - '0']);
+    }
+}
diff --git a/src/test-equation.vala b/src/test-equation.vala
new file mode 100644
index 0000000..f723e41
--- /dev/null
+++ b/src/test-equation.vala
@@ -0,0 +1,601 @@
+/*
+ * Copyright (C) 2008-2012 Robert Ancell.
+ * 
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+ 
+private int number_base = 10;
+private int wordlen = 32;
+private AngleUnit angle_units = AngleUnit.DEGREES;
+private bool enable_conversions = false;
+private bool enable_variables = false;
+
+private int fail_count = 0;
+private int pass_count = 0;
+
+private string error_code_to_string (ErrorCode error)
+{
+    if (error == ErrorCode.MP)
+        return "ErrorCode.MP(\"%s\")".printf (mp_get_error ());
+
+    return mp_error_code_to_string (error);
+}
+
+private void test (string expression, string expected, ErrorCode expected_error)
+{
+    var equation = new TestEquation (expression, enable_variables, enable_conversions);
+    equation.base = number_base;
+    equation.wordlen = wordlen;
+    equation.angle_units = angle_units;
+
+    ErrorCode error;
+    var result = equation.parse (out error, null);
+
+    if (result == null)
+    {
+        if (error == expected_error)
+        {
+            /*stdout.printf ("PASS: '%s' -> error %s\n", expression, error_code_to_string (error));*/
+            pass_count++;
+        }
+        else if (expected_error == ErrorCode.NONE)
+        {
+            stdout.printf ("*FAIL: '%s' -> error %s, expected result %s\n", expression, error_code_to_string (error), expected);
+            fail_count++;
+        }
+        else
+        {
+            stdout.printf ("*FAIL: '%s' -> error %s, expected error %s\n", expression, error_code_to_string (error), error_code_to_string (expected_error));
+            fail_count++;
+        }
+    }
+    else
+    {
+        var serializer = new Serializer (DisplayFormat.FIXED, number_base, 9);
+        var result_str = serializer.to_string (result);
+
+        if (expected_error != ErrorCode.NONE)
+        {
+            stdout.printf ("*FAIL: '%s' -> %s, expected error %s\n", expression, result_str, error_code_to_string (expected_error));
+            fail_count++;
+        }
+        else if (result_str != expected)
+        {
+            stdout.printf ("*FAIL: '%s' -> '%s', expected '%s'\n", expression, result_str, expected);
+            fail_count++;
+        }
+        else
+        {
+            /*stdout.printf ("PASS: '%s' -> '%s'\n", expression, result_str);*/
+            pass_count++;
+        }
+    }
+}
+
+private class TestEquation : Equation
+{
+    private bool enable_variables;
+    private bool enable_conversions;
+
+    public TestEquation (string equation, bool enable_variables, bool enable_conversions)
+    {
+        base (equation);
+        this.enable_variables = enable_variables;
+        this.enable_conversions = enable_conversions;
+    }
+
+    public override bool variable_is_defined (string name)
+    {
+        if (!enable_variables)
+            return false;
+
+        return name == "x" || name == "y";
+    }
+
+    public override Number? get_variable (string name)
+    {
+        if (!enable_variables)
+            return null;
+
+        if (name == "x")
+            return new Number.integer (2);
+        if (name == "y")
+            return new Number.integer (3);
+
+        return null;
+    }
+
+    public override Number? convert (Number x, string x_units, string z_units)
+    {
+        if (!enable_conversions)
+            return null;
+        
+        return UnitManager.get_default ().convert_by_symbol (x, x_units, z_units);
+    }
+}
+
+private void test_conversions ()
+{
+    number_base = 10;
+    wordlen = 32;
+    angle_units = AngleUnit.DEGREES;
+    enable_conversions = true;
+    enable_variables = false;
+
+    /* Angle units */
+    //test ("Ï radians in degrees", "180", 0);
+    test ("100 gradians in degrees", "90", 0);
+
+    /* 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);
+  
+    /* Duration */
+    test ("1 minute in seconds", "60", 0);
+    test ("1s in ms", "1000", 0);
+
+    /* Temperature */
+    //test ("100ËC in ËF", "", 0);
+    //test ("0ËC in ËF", "32", 0);
+    //test ("0ËK in ËC", "â273.15", 0);
+    test ("100degC in degF", "212", 0);
+    test ("0degC in degF", "32", 0);
+    test ("0 K in degC", "â273.15", 0);
+}
+
+private void test_equations ()
+{
+    number_base = 10;
+    wordlen = 32;
+    angle_units = AngleUnit.DEGREES;
+    enable_conversions = false;
+    enable_variables = true;
+
+    number_base = 2;
+    test ("2ââ", "10", 0);
+
+    number_base = 8;
+    test ("16434824ââ", "76543210", 0);
+
+    number_base = 16;
+    test ("FF", "FF", 0);
+    test ("18364758544493064720ââ", "FEDCBA9876543210", 0);
+
+    number_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â", "", ErrorCode.INVALID); test ("2â", "2", 0); test ("2", "2", 0); test ("2ââ", "2", 0);
+    test ("3â", "", ErrorCode.INVALID); test ("3â", "3", 0); test ("3", "3", 0); test ("3ââ", "3", 0);
+    test ("4â", "", ErrorCode.INVALID); test ("4â", "4", 0); test ("4", "4", 0); test ("4ââ", "4", 0);
+    test ("5â", "", ErrorCode.INVALID); test ("5â", "5", 0); test ("5", "5", 0); test ("5ââ", "5", 0);
+    test ("6â", "", ErrorCode.INVALID); test ("6â", "6", 0); test ("6", "6", 0); test ("6ââ", "6", 0);
+    test ("7â", "", ErrorCode.INVALID); test ("7â", "7", 0); test ("7", "7", 0); test ("7ââ", "7", 0);
+    test ("8â", "", ErrorCode.INVALID); test ("8â", "", ErrorCode.INVALID); test ("8", "8", 0); test ("8ââ", "8", 0);
+    test ("9â", "", ErrorCode.INVALID); test ("9â", "", ErrorCode.INVALID); test ("9", "9", 0); test ("9ââ", "9", 0);
+    test ("Aâ", "", ErrorCode.UNKNOWN_VARIABLE); test ("Aâ", "", ErrorCode.UNKNOWN_VARIABLE); test ("A", "", ErrorCode.UNKNOWN_VARIABLE); test ("Aââ", "10", 0);
+    test ("Bâ", "", ErrorCode.UNKNOWN_VARIABLE); test ("Bâ", "", ErrorCode.UNKNOWN_VARIABLE); test ("B", "", ErrorCode.UNKNOWN_VARIABLE); test ("Bââ", "11", 0);
+    test ("Câ", "", ErrorCode.UNKNOWN_VARIABLE); test ("Câ", "", ErrorCode.UNKNOWN_VARIABLE); test ("C", "", ErrorCode.UNKNOWN_VARIABLE); test ("Cââ", "12", 0);
+    test ("Dâ", "", ErrorCode.UNKNOWN_VARIABLE); test ("Dâ", "", ErrorCode.UNKNOWN_VARIABLE); test ("D", "", ErrorCode.UNKNOWN_VARIABLE); test ("Dââ", "13", 0);
+    test ("Eâ", "", ErrorCode.UNKNOWN_VARIABLE); test ("Eâ", "", ErrorCode.UNKNOWN_VARIABLE); test ("E", "", ErrorCode.UNKNOWN_VARIABLE); test ("Eââ", "14", 0);
+    test ("Fâ", "", ErrorCode.UNKNOWN_VARIABLE); test ("Fâ", "", ErrorCode.UNKNOWN_VARIABLE); test ("F", "", ErrorCode.UNKNOWN_VARIABLE); test ("Fââ", "15", 0);
+    test ("aâ", "", ErrorCode.UNKNOWN_VARIABLE); test ("aâ", "", ErrorCode.UNKNOWN_VARIABLE); test ("a", "", ErrorCode.UNKNOWN_VARIABLE); test ("aââ", "10", 0);
+    test ("bâ", "", ErrorCode.UNKNOWN_VARIABLE); test ("bâ", "", ErrorCode.UNKNOWN_VARIABLE); test ("b", "", ErrorCode.UNKNOWN_VARIABLE); test ("bââ", "11", 0);
+    test ("câ", "", ErrorCode.UNKNOWN_VARIABLE); test ("câ", "", ErrorCode.UNKNOWN_VARIABLE); test ("c", "", ErrorCode.UNKNOWN_VARIABLE); test ("cââ", "12", 0);
+    test ("dâ", "", ErrorCode.UNKNOWN_VARIABLE); test ("dâ", "", ErrorCode.UNKNOWN_VARIABLE); test ("d", "", ErrorCode.UNKNOWN_VARIABLE); test ("dââ", "13", 0);
+    test ("eâ", "", ErrorCode.UNKNOWN_VARIABLE); test ("eâ", "", ErrorCode.UNKNOWN_VARIABLE); /* e is a built-in variable */              test ("eââ", "14", 0);
+    test ("fâ", "", ErrorCode.UNKNOWN_VARIABLE); test ("fâ", "", ErrorCode.UNKNOWN_VARIABLE); test ("f", "", ErrorCode.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", ErrorCode.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", "", ErrorCode.UNKNOWN_VARIABLE);
+    test ("2y", "6", 0);
+    test ("y2", "", ErrorCode.INVALID);
+    test ("y 2", "", ErrorCode.INVALID);
+    test ("2z", "", ErrorCode.UNKNOWN_VARIABLE);  
+    test ("z2", "", ErrorCode.UNKNOWN_VARIABLE);
+    test ("z 2", "", ErrorCode.UNKNOWN_VARIABLE);
+    test ("z(2)", "", ErrorCode.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", "", ErrorCode.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", "", ErrorCode.MP);
+    test ("0Ã0", "", ErrorCode.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);
+    test ("2(1+1)", "4", 0);
+    test ("4Ã2(1+1)", "4", 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!", "", ErrorCode.MP);
+    test ("â1!", "â1", 0);
+    test ("(â1)!", "", ErrorCode.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 ("0^0.5", "0", 0);
+    test ("2^0", "1", 0);
+    test ("2^1", "2", 0);
+    test ("2^2", "4", 0);
+    test ("2âÂ", "0.5", 0);
+    test ("2â", "", ErrorCode.MP);
+    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", "", ErrorCode.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", "", ErrorCode.MP);
+    test ("ln 1", "0", 0);
+    test ("ln 2", "0.693147181", 0);
+    test ("ln e", "1", 0);
+    test ("2 ln 2", "1.386294361", 0);
+
+    angle_units = AngleUnit.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", "", ErrorCode.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 ("acos 0", "90", 0);
+    test ("acos 1", "0", 0);
+
+    test ("sinâ 0", "0", 0);
+    test ("sinâ 1", "90", 0);
+    test ("sinâ (â1)", "â90", 0);
+    test ("sinâ (1Ãâ2)", "45", 0);
+    test ("asin 0", "0", 0);
+    test ("asin 1", "90", 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);
+
+    angle_units = AngleUnit.DEGREES;
+    test ("sin 90", "1", 0);
+
+    angle_units = AngleUnit.RADIANS;
+    test ("sin (ÏÃ2)", "1", 0); // FIXME: Shouldn't need brackets
+
+    angle_units = AngleUnit.GRADIANS;
+    test ("sin 100", "1", 0);
+
+    /* Complex numbers */
+    angle_units = AngleUnit.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", "", ErrorCode.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);
+
+    number_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);
+
+    number_base = 2;
+    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);
+    //wordlen = 2;
+    //test ("Â01â", "10â", 0);
+    //test ("ÂÂ10â", "10â", 0);
+}
+
+public int main (string args[])
+{
+    Intl.setlocale (LocaleCategory.ALL, "C");
+
+    test_conversions ();
+    test_equations ();
+
+    if (fail_count == 0)
+        stdout.printf ("Passed all %i tests\n", pass_count);
+    else
+        stdout.printf ("Failed %i/%d tests\n", fail_count, pass_count + fail_count);
+
+    return fail_count;
+}
diff --git a/src/test-number.vala b/src/test-number.vala
new file mode 100644
index 0000000..5c5da62
--- /dev/null
+++ b/src/test-number.vala
@@ -0,0 +1,1165 @@
+/*
+ * Copyright (C) 2008-2012 Robert Ancell.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+private int fail_count = 0;
+private int pass_count = 0;
+
+private void pass (string? text = null)
+{
+    //stdout.printf ("PASS: %s\n", text);
+    pass_count++;
+}
+
+private void fail (string text)
+{
+    stdout.printf ("*FAIL: %s\n", text);
+    fail_count++;
+}
+
+private void test_integer ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        var z = new Number.integer (a);
+        if (z.to_integer () != a)
+        {
+            fail ("Number.integer (%d).to_integer () -> %lli, expected %i".printf (a, z.to_integer (), a));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_unsigned_integer ()
+{
+    for (var a = 0; a <= 10; a++)
+    {
+        var z = new Number.unsigned_integer (a);
+        if (z.to_unsigned_integer () != a)
+        {
+            fail ("Number.unsigned_integer (%d).to_unsigned_integer () -> %i, expected %i".printf (a, (int) z.to_unsigned_integer (), a));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_fraction ()
+{
+    for (var a = 0; a <= 10; a++)
+    {
+        for (var b = 1; b <= 10; b++)
+        {
+            var z = new Number.fraction (a, b);
+            var expected = (double) a / b;
+            if (!double_matches (z, expected))
+            {
+                fail ("Number.fraction (%d, %d) -> %f, expected %f".printf (a, b, z.to_double (), expected));
+                return;
+            }
+        }
+    }
+
+    pass ();
+}
+
+private void test_float ()
+{
+    for (var a = -10.0f; a <= 10.0f; a += 0.5f)
+    {
+        var z = new Number.float (a);
+        if (z.to_float () != a)
+        {
+            fail ("Number.float (%f).to_float () -> %f, expected %f".printf (a, z.to_float (), a));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_double ()
+{
+    for (var a = -10.0; a <= 10.0; a += 0.5)
+    {
+        var z = new Number.double (a);
+        if (z.to_double () != a)
+        {
+            fail ("Number.double (%f).to_double () -> %f, expected %f".printf (a, z.to_double (), a));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_complex ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        for (var b = -10; b <= 10; b++)
+        {
+            var z = new Number.complex (new Number.integer (a), new Number.integer (b));
+            var re_expected = a;
+            var im_expected = b;
+            if (z.real_component ().to_integer () != re_expected || z.imaginary_component ().to_integer () != im_expected)
+            {
+                fail ("Number.complex (%d%+di) -> %d%+di, expected %d%+di".printf (a, b, (int) z.real_component ().to_integer (), (int) z.imaginary_component ().to_integer (), re_expected, im_expected));
+                return;
+            }
+        }
+    }
+}
+
+private void test_polar ()
+{
+    for (var ri = -10; ri <= 10; ri++)
+    {
+        for (var theta_i = -10; theta_i <= 10; theta_i++)
+        {
+            var r = (double) ri;
+            var theta = 2 * Math.PI * theta_i / 10.0;
+            var z = new Number.polar (new Number.double (r), new Number.double (theta));
+            var re_expected = r * Math.cos (theta);
+            var im_expected = r * Math.sin (theta);
+            if (!double_matches (z.real_component (), re_expected) || !double_matches (z.imaginary_component (), im_expected))
+            {
+                fail ("Number.polar (%f, %f) -> %f%+fi, expected %f%+fi".printf (r, theta, z.real_component ().to_double (), z.imaginary_component ().to_double (), re_expected, im_expected));
+                return;
+            }
+        }
+    }
+}
+
+private void test_i ()
+{
+    var z = new Number.i ();
+    if (z.real_component ().to_integer () != 0 && z.imaginary_component ().to_integer () != 1)
+    {
+        fail ("Number.i () -> %d%+di, expected i".printf ((int) z.real_component ().to_integer (), (int) z.imaginary_component ().to_integer ()));
+        return;
+    }
+
+    pass ();
+}
+
+private void test_pi ()
+{
+    var z = new Number.pi ();
+    var expected = Math.PI;
+    if (!double_matches (z, expected))
+    {
+        fail ("Number.pi () -> %f, expected %f".printf (z.to_double (), expected));
+        return;
+    }
+
+    pass ();
+}
+
+private void test_eulers ()
+{
+    var z = new Number.eulers ();
+    var expected = Math.E;
+    if (!double_matches (z, expected))
+    {
+        fail ("Number.eulers () -> %f, expected %f".printf (z.to_double (), expected));
+        return;
+    }
+
+    pass ();
+}
+
+private void test_string ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        var s = "%d".printf (a);
+        var z = mp_set_from_string (s);
+        if (z == null)
+        {
+            fail ("mp_set_from_string (\"%s\") -> null".printf (s));
+            return;
+        }
+
+        if (z.to_integer () != a)
+        {
+            fail ("mp_set_from_string (\"%s\").to_integer () -> %d, expected %d".printf (s, (int) z.to_integer (), a));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_sgn ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        var z = new Number.integer (a);
+        var expected = 0;
+        if (a < 0)
+            expected = -1;
+        if (a > 0)
+            expected = 1;
+        if (z.sgn ().to_integer () != expected)
+        {
+            fail ("(%d).sgn () -> %d, expected %d".printf (a, (int) z.sgn ().to_integer (), expected));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_invert_sign ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        var z = new Number.integer (a);
+        var expected = -a;
+        if (z.invert_sign ().to_integer () != expected)
+        {
+            fail ("(%d).invert_sign () -> %d, expected %d".printf (a, (int) z.invert_sign ().to_integer (), expected));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_abs ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        var z = new Number.integer (a);
+        var expected = a.abs ();
+        if (z.abs ().to_integer () != expected)
+        {
+            fail ("(%d).abs () -> %d, expected %d".printf (a, (int) z.abs ().to_integer (), expected));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_arg ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        for (var b = -10; b <= 10; b++)
+        {
+            var z = new Number.complex (new Number.integer (a), new Number.integer (b));
+            z = z.arg ();
+            var expected = Math.atan2 (b, a);
+            if (!double_matches (z.real_component (), expected) || !z.imaginary_component ().is_zero ())
+            {
+                fail ("(%d%+di).arg () -> %f%+fi, expected %f".printf (a, b, z.real_component ().to_double (), z.imaginary_component ().to_double (), expected));
+                return;
+            }
+        }
+    }
+}
+
+private void test_conjugate ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        for (var b = -10; b <= 10; b++)
+        {
+            var z = new Number.complex (new Number.integer (a), new Number.integer (b));
+            z = z.conjugate ();
+            var re_expected = a;
+            var im_expected = -b;
+            if (z.real_component ().to_integer () != re_expected || z.imaginary_component ().to_integer () != im_expected)
+            {
+                fail ("(%d%+di).real_component () -> %d%+di, expected %d%+di".printf (a, b, (int) z.real_component ().to_integer (), (int) z.imaginary_component ().to_integer (), re_expected, im_expected));
+                return;
+            }
+        }
+    }
+}
+
+private void test_real_component ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        for (var b = -10; b <= 10; b++)
+        {
+            var z = new Number.complex (new Number.integer (a), new Number.integer (b));
+            var expected = a;
+            if (z.real_component ().to_integer () != expected)
+            {
+                fail ("(%d+%di).real_component () -> %d, expected %d".printf (a, b, (int) z.real_component ().to_integer (), expected));
+                return;
+            }
+        }
+    }
+
+    pass ();
+}
+
+private void test_imaginary_component ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        for (var b = -10; b <= 10; b++)
+        {
+            var z = new Number.complex (new Number.integer (a), new Number.integer (b));
+            var expected = b;
+            if (z.imaginary_component ().to_integer () != expected)
+            {
+                fail ("(%d+%di).imaginary_component () -> %d, expected %d".printf (a, b, (int) z.imaginary_component ().to_integer (), expected));
+                return;
+            }
+        }
+    }
+
+    pass ();
+}
+
+private bool double_matches (Number a, double b)
+{
+    return double_string (a.to_double ()) == double_string (b);
+}
+
+private string double_string (double x)
+{
+    var value = "%.6f".printf (x);
+    if (value == "-0.000000")
+        return "0.000000";
+    else
+        return value;
+}
+
+private void test_integer_component ()
+{
+    for (var ai = -100; ai <= 100; ai++)
+    {
+        var a = ai / 10.0;
+        var z = new Number.double (a).integer_component ();
+        var expected = Math.trunc (a);
+        if (!double_matches (z, expected))
+        {
+            fail ("(%f).integer_component () -> %f, expected %f".printf (a, z.to_double (), expected));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_fractional_component ()
+{
+    for (var ai = -100; ai <= 100; ai++)
+    {
+        var a = ai / 10.0;
+        var z = new Number.double (a).fractional_component ();
+        var expected = a - Math.trunc (a);
+        if (!double_matches (z, expected))
+        {
+            fail ("(%f).fractional_component () -> %f, expected %f".printf (a, z.to_double (), expected));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_fractional_part ()
+{
+    for (var ai = -100; ai <= 100; ai++)
+    {
+        var a = ai / 10.0;
+        var z = new Number.double (a).fractional_part ();
+        var expected = a - Math.floor (a);
+        if (!double_matches (z, expected))
+        {
+            fail ("(%f).fractional_part () -> %f, expected %f".printf (a, z.to_double (), expected));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_floor ()
+{
+    for (var ai = -100; ai <= 100; ai++)
+    {
+        var a = ai / 10.0;
+        var z = new Number.double (a).floor ();
+        var expected = Math.floor (a);
+        if (!double_matches (z, expected))
+        {
+            fail ("(%f).floor () -> %f, expected %f".printf (a, z.to_double (), expected));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_ceiling ()
+{
+    for (var ai = -100; ai <= 100; ai++)
+    {
+        var a = ai / 10.0;
+        var z = new Number.double (a).ceiling ();
+        var expected = Math.ceil (a);
+        if (!double_matches (z, expected))
+        {
+            fail ("(%f).ceiling () -> %f, expected %f".printf (a, z.to_double (), expected));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_round ()
+{
+    for (var ai = -100; ai <= 100; ai++)
+    {
+        var a = ai / 10.0;
+        var z = new Number.double (a).round ();
+        var expected = Math.round (a);
+        if (!double_matches (z, expected))
+        {
+            fail ("(%f).round () -> %f, expected %f".printf (a, z.to_double (), expected));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_reciprocal ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        if (a == 0)
+            continue;
+
+        var z = new Number.double (a).reciprocal ();
+        var expected = 1.0 / a;
+        if (!double_matches (z, expected))
+        {
+            fail ("(%f).reciprocal () -> %f, expected %f".printf (a, z.to_double (), expected));
+            return;
+        }
+
+        z = new Number.double (expected).reciprocal ();
+        if (!double_matches (z, a))
+        {
+            fail ("(%f).reciprocal () -> %f, expected %f".printf (expected, z.to_double (), a));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_epowy ()
+{
+    for (var ai = -100; ai <= 100; ai++)
+    {
+        var a = ai / 10.0;
+        var z = new Number.double (a).epowy ();
+        var expected = Math.exp (a);
+        if (!double_matches (z, expected))
+        {
+            fail ("(%f).epowy () -> %f, expected %f".printf (a, z.to_double (), expected));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_xpowy ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        for (var b = -10; b <= 10; b++)
+        {
+            var z = new Number.integer (a).xpowy (new Number.integer (b));
+            var expected = 1.0;
+            if (a == 0)
+            {
+                if (b != 0)
+                    expected = 0.0;
+            }
+            else
+            {
+                if (b < 0)
+                    for (var i = 0; i > b; i--)
+                        expected /= a;
+                else if (b > 0)
+                    for (var i = 0; i < b; i++)
+                        expected *= a;
+            }
+
+            if (!double_matches (z, expected))
+            {
+                fail ("(%d).xpowy (%d) -> %f, expected %f".printf (a, b, z.to_double (), expected));
+                return;
+            }
+        }
+    }
+
+    pass ();
+}
+
+private void test_xpowy_integer ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        for (var b = -10; b <= 10; b++)
+        {
+            var z = new Number.integer (a).xpowy_integer (b);
+            var expected = 1.0;
+            if (a == 0)
+            {
+                if (b != 0)
+                    expected = 0.0;
+            }
+            else
+            {
+                if (b < 0)
+                    for (var i = 0; i > b; i--)
+                        expected /= a;
+                else if (b > 0)
+                    for (var i = 0; i < b; i++)
+                        expected *= a;
+            }
+
+            if (!double_matches (z, expected))
+            {
+                fail ("(%d).xpowy_integer (%d) -> %f, expected %f".printf (a, b, z.to_double (), expected));
+                return;
+            }
+        }
+    }
+
+    pass ();
+}
+
+private void test_root3 ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        var z = new Number.double (a).root (3);
+        var expected = Math.cbrt (a);
+        if (!double_matches (z, expected))
+        {
+            fail ("(%f).root (3) -> %f, expected %f".printf (a, z.to_double (), expected));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_sqrt ()
+{
+    for (var a = 0; a <= 10; a++)
+    {
+        var z = new Number.double (a).sqrt ();
+        var expected = Math.sqrt (a);
+        if (!double_matches (z, expected))
+        {
+            fail ("(%f).sqrt () -> %f, expected %f".printf (a, z.to_double (), expected));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_ln ()
+{
+    for (var a = 1; a <= 10; a++)
+    {
+        var z = new Number.double (a).ln ();
+        var expected = Math.log (a);
+        if (!double_matches (z, expected))
+        {
+            fail ("(%f).ln () -> %f, expected %f".printf (a, z.to_double (), expected));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_logarithm2 ()
+{
+    for (var a = 1; a <= 10; a++)
+    {
+        var z = new Number.double (a).logarithm (2);
+        var expected = Math.log2 (a);
+        if (!double_matches (z, expected))
+        {
+            fail ("(%f).logarithm (2) -> %f, expected %f".printf (a, z.to_double (), expected));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_logarithm10 ()
+{
+    for (var a = 1; a <= 10; a++)
+    {
+        var z = new Number.double (a).logarithm (10);
+        var expected = Math.log10 (a);
+        if (!double_matches (z, expected))
+        {
+            fail ("(%f).logarithm (10) -> %f, expected %f".printf (a, z.to_double (), expected));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_is_zero ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        var z = new Number.integer (a);
+        var expected = a == 0;
+        if (z.is_zero () != expected)
+        {
+            fail ("(%d).is_zero () -> %s, expected %s".printf (a, z.is_zero () ? "true" : "false", expected ? "true" : "false"));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_is_negative ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        var z = new Number.integer (a);
+        var expected = a < 0;
+        if (z.is_negative () != expected)
+        {
+            fail ("(%d).is_negative () -> %s, expected %s".printf (a, z.is_negative () ? "true" : "false", expected ? "true" : "false"));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_is_integer ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        var z = new Number.integer (a);
+        var expected = true;
+        if (z.is_integer () != expected)
+        {
+            fail ("(%d).is_integer () -> %s, expected %s".printf (a, z.is_integer () ? "true" : "false", expected ? "true" : "false"));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_is_positive_integer ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        var z = new Number.integer (a);
+        var expected = a >= 0;
+        if (z.is_positive_integer () != expected)
+        {
+            fail ("(%d).is_positive_integer () -> %s, expected %s".printf (a, z.is_positive_integer () ? "true" : "false", expected ? "true" : "false"));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_is_natural ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        var z = new Number.integer (a);
+        var expected = a > 0;
+        if (z.is_natural () != expected)
+        {
+            fail ("(%d).is_natural () -> %s, expected %s".printf (a, z.is_natural () ? "true" : "false", expected ? "true" : "false"));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_is_complex ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        for (var b = -10; b <= 10; b++)
+        {
+            var z = new Number.complex (new Number.integer (a), new Number.integer (b));
+            var expected = b != 0;
+            if (z.is_complex () != expected)
+            {
+                fail ("(%d+%di).is_complex () -> %s, expected %s".printf (a, b, z.is_complex () ? "true" : "false", expected ? "true" : "false"));
+                return;
+            }
+        }
+    }
+
+    pass ();
+}
+
+private void test_factorial ()
+{
+    for (var a = 0; a <= 10; a++)
+    {
+        var z = new Number.integer (a);
+        var expected = 1;
+        for (var i = 2; i <= a; i++)
+            expected *= i;
+        if (z.factorial ().to_integer () != expected)
+        {
+            fail ("(%d).factorial () -> %lli, expected %lli".printf (a, z.factorial ().to_integer (), expected));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_add ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        for (var b = -10; b <= 10; b++)
+        {
+            var z = (new Number.integer (a)).add (new Number.integer (b));
+            var expected = a + b;
+            if (z.to_integer () != expected)
+            {
+                fail ("(%d).add (%d) -> %lli, expected %d".printf (a, b, z.to_integer (), expected));
+                return;
+            }
+        }
+    }
+
+    pass ();
+}
+
+private void test_subtract ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        for (var b = -10; b <= 10; b++)
+        {
+            var z = (new Number.integer (a)).subtract (new Number.integer (b));
+            var expected = a - b;
+            if (z.to_integer () != expected)
+            {
+                fail ("(%d).subtract (%d) -> %lli, expected %d".printf (a, b, z.to_integer (), expected));
+                return;
+            }
+        }
+    }
+
+    pass ();
+}
+
+private void test_multiply ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        for (var b = -10; b <= 10; b++)
+        {
+            var z = (new Number.integer (a)).multiply (new Number.integer (b));
+            var expected = a * b;
+            if (z.to_integer () != expected)
+            {
+                fail ("(%d).multiply (%d) -> %lli, expected %d".printf (a, b, z.to_integer (), expected));
+                return;
+            }
+        }
+    }
+
+    pass ();
+}
+
+private void test_multiply_integer ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        for (var b = -10; b <= 10; b++)
+        {
+            var z = (new Number.integer (a)).multiply_integer (b);
+            var expected = a * b;
+            if (z.to_integer () != expected)
+            {
+                fail ("(%d).multiply_integer (%d) -> %lli, expected %d".printf (a, b, z.to_integer (), expected));
+                return;
+            }
+        }
+    }
+
+    pass ();
+}
+
+private void test_divide ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        for (var b = -10; b <= 10; b++)
+        {
+            if (b == 0)
+                continue;
+
+            var z = (new Number.integer (a * b)).divide (new Number.integer (b));
+            var expected = a;
+            if (z.to_integer () != expected)
+            {
+                fail ("(%d).divide (%d) -> %lli, expected %d".printf (a * b, b, z.to_integer (), expected));
+                return;
+            }
+        }
+    }
+
+    pass ();
+}
+
+private void test_divide_integer ()
+{
+    for (var a = -10; a <= 10; a++)
+    {
+        for (var b = -10; b <= 10; b++)
+        {
+            if (b == 0)
+                continue;
+
+            var z = (new Number.integer (a * b)).divide_integer (b);
+            var expected = a;
+            if (z.to_integer () != expected)
+            {
+                fail ("(%d).divide_integer (%d) -> %lli, expected %d".printf (a * b, b, z.to_integer (), expected));
+                return;
+            }
+        }
+    }
+
+    pass ();
+}
+
+private void test_modulus_divide ()
+{
+    for (var a = 0; a <= 10; a++)
+    {
+        for (var b = 1; b <= 10; b++)
+        {
+            var z = (new Number.integer (a)).modulus_divide (new Number.integer (b));
+            var expected = a % b;
+            if (z.to_integer () != expected)
+            {
+                fail ("(%d).modulus_divide (%d) -> %lli, expected %d".printf (a, b, z.to_integer (), expected));
+                return;
+            }
+        }
+    }
+
+    pass ();
+}
+
+static void test_sin ()
+{
+    for (var a = -Math.PI; a <= Math.PI; a += Math.PI / 16)
+    {
+        var z = new Number.double (a).sin ();
+        var expected = Math.sin (a);
+        if (!double_matches (z, expected))
+        {
+            fail ("(%f).sin () -> %f, expected %f".printf (a, z.to_double (), expected));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+static void test_cos ()
+{
+    for (var a = -Math.PI; a <= Math.PI; a += Math.PI / 16)
+    {
+        var z = new Number.double (a).cos ();
+        var expected = Math.cos (a);
+        if (!double_matches (z, expected))
+        {
+            fail ("(%f).cos () -> %f, expected %f".printf (a, z.to_double (), expected));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_and ()
+{
+    for (var a = 0; a < 10; a++)
+    {
+        for (var b = 0; b < 10; b++)
+        {
+            var z = (new Number.integer (a)).and (new Number.integer (b));
+            var expected = a & b;
+            if (z.to_integer () != expected)
+            {
+                fail ("(%d).and (%d) -> %lli, expected %d".printf (a, b, z.to_integer (), expected));
+                return;
+            }
+        }
+    }
+
+    pass ();
+}
+
+private void test_or ()
+{
+    for (var a = 0; a < 10; a++)
+    {
+        for (var b = 0; b < 10; b++)
+        {
+            var z = (new Number.integer (a)).or (new Number.integer (b));
+            var expected = a | b;
+            if (z.to_integer () != expected)
+            {
+                fail ("(%d).or (%d) -> %lli, expected %d".printf (a, b, z.to_integer (), expected));
+                return;
+            }
+        }
+    }
+
+    pass ();
+}
+
+private void test_xor ()
+{
+    for (var a = 0; a < 10; a++)
+    {
+        for (var b = 0; b < 10; b++)
+        {
+            var z = (new Number.integer (a)).xor (new Number.integer (b));
+            var expected = a ^ b;
+            if (z.to_integer () != expected)
+            {
+                fail ("(%d).xor (%d) -> %lli, expected %d".printf (a, b, z.to_integer (), expected));
+                return;
+            }
+        }
+    }
+
+    pass ();
+}
+
+private void test_not ()
+{
+    for (var a = 0; a < 10; a++)
+    {
+        var z = (new Number.integer (a)).not (8);
+        var expected = ~a & 0xFF;
+        if (z.to_integer () != expected)
+        {
+            fail ("(%d).not () -> %lli, expected %d".printf (a, z.to_integer (), expected));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private void test_shift ()
+{
+    for (var a = 0; a < 10; a++)
+    {
+        for (var b = -10; b < 10; b++)
+        {
+            var z = (new Number.integer (a)).shift (b);
+            var expected = a << b;
+            if (b < 0)
+                expected = a >> -b;
+            if (z.to_integer () != expected)
+            {
+                fail ("(%d).shift (%d) -> %lli, expected %d".printf (a, b, z.to_integer (), expected));
+                return;
+            }
+        }
+    }
+
+    pass ();
+}
+
+private void test_factorize ()
+{
+    for (var a = 0; a < 100; a++)
+    {
+        var factors = (new Number.integer (a)).factorize ();
+        var expected = factorize (a);
+
+        var matches = false;
+        if (factors.length () == expected.length ())
+        {
+            matches = true;
+            for (var i = 0 ; i < factors.length (); i++)
+                if (factors.nth_data (i).to_integer () != expected.nth_data (i))
+                    matches = false;
+        }
+
+        if (!matches)
+        {
+            var factors_string = "";
+            foreach (var f in factors)
+            {
+                if (factors_string != "")
+                    factors_string += ", ";
+                factors_string += "%d".printf ((int) f.to_integer ());
+            }
+            var expected_string = "";
+            foreach (var f in expected)
+            {
+                if (expected_string != "")
+                    expected_string += ", ";
+                expected_string += "%d".printf (f);
+            }
+            fail ("(%d).factorize () -> (%s), expected (%s)".printf (a, factors_string, expected_string));
+            return;
+        }
+    }
+
+    pass ();
+}
+
+private List<int> factorize (int number)
+{
+    var factors = new List<int> ();
+    if (number < 2)
+    {
+        factors.append (number);
+        return factors;
+    }
+
+    var n = number;
+    while (true)
+    {
+        for (var factor = 2; factor <= n; factor++)
+        {
+            if (n % factor == 0)
+            {
+                factors.append (factor);
+                n /= factor;
+                if (n == 1)
+                    return factors;
+                break;
+            }
+        }
+    }
+}
+
+static int main (string[] args)
+{
+    Intl.setlocale (LocaleCategory.ALL, "C");
+
+    test_integer ();
+    test_unsigned_integer ();
+    test_fraction ();
+    test_float ();
+    test_double ();
+    test_complex ();
+    test_polar ();
+    test_string ();
+    test_eulers ();
+    test_i ();
+    test_pi ();
+    //test_random ();
+    test_is_zero ();
+    test_is_negative ();
+    test_is_integer ();
+    test_is_positive_integer ();
+    test_is_natural ();
+    test_is_complex ();
+    test_sgn ();
+    test_invert_sign ();
+    test_abs ();
+    test_arg ();
+    test_conjugate ();
+    test_real_component ();
+    test_imaginary_component ();
+    test_integer_component ();
+    test_fractional_component ();
+    test_fractional_part ();
+    test_floor ();
+    test_ceiling ();
+    test_round ();
+    test_reciprocal ();
+    test_epowy ();
+    test_xpowy ();
+    test_xpowy_integer ();
+    test_root3 (); // FIXME: should check other roots
+    test_sqrt ();
+    test_ln ();
+    test_logarithm2 (); // FIXME: Should check other bases
+    test_logarithm10 (); // FIXME: Should check other bases
+    test_factorial ();
+    test_add ();
+    test_subtract ();
+    test_multiply ();
+    test_multiply_integer ();
+    test_divide ();
+    test_divide_integer ();
+    test_modulus_divide ();
+    test_sin ();
+    test_cos ();
+    //test_tan ();
+    //test_asin ();
+    //test_acos ();
+    //test_atan ();
+    //test_sinh ();
+    //test_cosh ();
+    //test_tanh ();
+    //test_asinh ();
+    //test_acosh ();
+    //test_atanh ();
+    test_and ();
+    test_or ();
+    test_xor ();
+    test_not ();
+    //test_mask ();
+    test_shift ();
+    //test_ones_complement ();
+    //test_twos_complement ();
+    test_factorize ();
+
+    if (fail_count == 0)
+        stdout.printf ("Passed all %i tests\n", pass_count);
+    else
+        stdout.printf ("Failed %i/%d tests\n", fail_count, pass_count + fail_count);
+
+    return fail_count;
+}
diff --git a/src/unit.vala b/src/unit.vala
new file mode 100644
index 0000000..fec4078
--- /dev/null
+++ b/src/unit.vala
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2008-2012 Robert Ancell.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+private UnitManager? default_unit_manager = null;
+
+public class UnitManager : Object
+{
+    private List<UnitCategory> categories;
+    
+    public UnitManager ()
+    {
+        categories = new List<UnitCategory> ();
+    }
+
+    public static UnitManager get_default ()
+    {
+        if (default_unit_manager != null)
+            return default_unit_manager;
+
+        default_unit_manager = new UnitManager ();
+
+        var angle_category = default_unit_manager.add_category ("angle", _("Angle"));
+        var length_category = default_unit_manager.add_category ("length", _("Length"));
+        var area_category = default_unit_manager.add_category ("area", _("Area"));
+        var volume_category = default_unit_manager.add_category ("volume", _("Volume"));
+        var weight_category = default_unit_manager.add_category ("weight", _("Weight"));
+        var duration_category = default_unit_manager.add_category ("duration", _("Duration"));
+        var temperature_category = default_unit_manager.add_category ("temperature", _("Temperature"));
+
+        /* FIXME: Approximations of 1/(units in a circle), therefore, 360 deg != 400 grads */
+        angle_category.add_unit (new Unit ("degree", _("Degrees"), dpgettext2 (null, "unit-format", "%s degrees"), "Ï*x/180", "180x/Ï", dpgettext2 (null, "unit-symbols", "degree,degrees,deg")));
+        angle_category.add_unit (new Unit ("radian", _("Radians"), dpgettext2 (null, "unit-format", "%s radians"), "x", "x", dpgettext2 (null, "unit-symbols", "radian,radians,rad")));
+        angle_category.add_unit (new Unit ("gradian", _("Gradians"), dpgettext2 (null, "unit-format", "%s gradians"), "Ï*x/200", "200x/Ï", dpgettext2 (null, "unit-symbols", "gradian,gradians,grad")));
+        length_category.add_unit (new Unit ("parsec", _("Parsecs"), dpgettext2 (null, "unit-format", "%s pc"), "30857000000000000x", "x/30857000000000000", dpgettext2 (null, "unit-symbols", "parsec,parsecs,pc")));
+        length_category.add_unit (new Unit ("lightyear", _("Light Years"), dpgettext2 (null, "unit-format", "%s ly"), "9460730472580800x", "x/9460730472580800", dpgettext2 (null, "unit-symbols", "lightyear,lightyears,ly")));
+        length_category.add_unit (new Unit ("astronomical-unit", _("Astronomical Units"), dpgettext2 (null, "unit-format", "%s au"), "149597870691x", "x/149597870691", dpgettext2 (null, "unit-symbols", "au")));
+        length_category.add_unit (new Unit ("nautical-mile", _("Nautical Miles"), dpgettext2 (null, "unit-format", "%s nmi"), "1852x", "x/1852", dpgettext2 (null, "unit-symbols", "nmi")));
+        length_category.add_unit (new Unit ("mile", _("Miles"), dpgettext2 (null, "unit-format", "%s mi"), "1609.344x", "x/1609.344", dpgettext2 (null, "unit-symbols", "mile,miles,mi")));
+        length_category.add_unit (new Unit ("kilometer", _("Kilometers"), dpgettext2 (null, "unit-format", "%s km"), "1000x", "x/1000", dpgettext2 (null, "unit-symbols", "kilometer,kilometers,km,kms")));
+        length_category.add_unit (new Unit ("cable", _("Cables"), dpgettext2 (null, "unit-format", "%s cb"), "219.456x", "x/219.456", dpgettext2 (null, "unit-symbols", "cable,cables,cb")));
+        length_category.add_unit (new Unit ("fathom", _("Fathoms"), dpgettext2 (null, "unit-format", "%s ftm"), "1.8288x", "x/1.8288", dpgettext2 (null, "unit-symbols", "fathom,fathoms,ftm")));
+        length_category.add_unit (new Unit ("meter", _("Meters"), dpgettext2 (null, "unit-format", "%s m"), "x", "x", dpgettext2 (null, "unit-symbols", "meter,meters,m")));
+        length_category.add_unit (new Unit ("yard", _("Yards"), dpgettext2 (null, "unit-format", "%s yd"), "0.9144x", "x/0.9144", dpgettext2 (null, "unit-symbols", "yard,yards,yd")));
+        length_category.add_unit (new Unit ("foot", _("Feet"), dpgettext2 (null, "unit-format", "%s ft"), "0.3048x", "x/0.3048", dpgettext2 (null, "unit-symbols", "foot,feet,ft")));
+        length_category.add_unit (new Unit ("inch", _("Inches"), dpgettext2 (null, "unit-format", "%s in"), "0.0254x", "x/0.0254", dpgettext2 (null, "unit-symbols", "inch,inches,in")));
+        length_category.add_unit (new Unit ("centimeter", _("Centimeters"), dpgettext2 (null, "unit-format", "%s cm"), "x/100", "100x", dpgettext2 (null, "unit-symbols", "centimeter,centimeters,cm,cms")));
+        length_category.add_unit (new Unit ("millimeter", _("Millimeters"), dpgettext2 (null, "unit-format", "%s mm"), "x/1000", "1000x", dpgettext2 (null, "unit-symbols", "millimeter,millimeters,mm")));
+        length_category.add_unit (new Unit ("micrometer", _("Micrometers"), dpgettext2 (null, "unit-format", "%s Îm"), "x/1000000", "1000000x", dpgettext2 (null, "unit-symbols", "micrometer,micrometers,um")));
+        length_category.add_unit (new Unit ("nanometer", _("Nanometers"), dpgettext2 (null, "unit-format", "%s nm"), "x/1000000000", "1000000000x", dpgettext2 (null, "unit-symbols", "nanometer,nanometers,nm")));
+        area_category.add_unit (new Unit ("hectare", _("Hectares"), dpgettext2 (null, "unit-format", "%s ha"), "10000x", "x/10000", dpgettext2 (null, "unit-symbols", "hectare,hectares,ha")));
+        area_category.add_unit (new Unit ("acre", _("Acres"), dpgettext2 (null, "unit-format", "%s acres"), "4046.8564224x", "x/4046.8564224", dpgettext2 (null, "unit-symbols", "acre,acres")));
+        area_category.add_unit (new Unit ("square-meter", _("Square Meters"), dpgettext2 (null, "unit-format", "%s mÂ"), "x", "x", dpgettext2 (null, "unit-symbols", "mÂ")));
+        area_category.add_unit (new Unit ("square-centimeter", _("Square Centimeters"), dpgettext2 (null, "unit-format", "%s cmÂ"), "0.0001x", "10000x", dpgettext2 (null, "unit-symbols", "cmÂ")));
+        area_category.add_unit (new Unit ("square-millimeter", _("Square Millimeters"), dpgettext2 (null, "unit-format", "%s mmÂ"), "0.000001x", "1000000x", dpgettext2 (null, "unit-symbols", "mmÂ")));
+        volume_category.add_unit (new Unit ("cubic-meter", _("Cubic Meters"), dpgettext2 (null, "unit-format", "%s mÂ"), "1000x", "x/1000", dpgettext2 (null, "unit-symbols", "mÂ")));
+        volume_category.add_unit (new Unit ("gallon", _("Gallons"), dpgettext2 (null, "unit-format", "%s gal"), "3.785412x", "x/3.785412", dpgettext2 (null, "unit-symbols", "gallon,gallons,gal")));
+        volume_category.add_unit (new Unit ("litre", _("Litres"), dpgettext2 (null, "unit-format", "%s L"), "x", "x", dpgettext2 (null, "unit-symbols", "litre,litres,liter,liters,L")));
+        volume_category.add_unit (new Unit ("quart", _("Quarts"), dpgettext2 (null, "unit-format", "%s qt"), "0.9463529x", "x/0.9463529", dpgettext2 (null, "unit-symbols", "quart,quarts,qt")));
+        volume_category.add_unit (new Unit ("pint", _("Pints"), dpgettext2 (null, "unit-format", "%s pt"), "0.4731765x", "x/0.4731765", dpgettext2 (null, "unit-symbols", "pint,pints,pt")));
+        volume_category.add_unit (new Unit ("millilitre", _("Millilitres"), dpgettext2 (null, "unit-format", "%s mL"), "0.001x", "1000x", dpgettext2 (null, "unit-symbols", "millilitre,millilitres,milliliter,milliliters,mL,cmÂ")));
+        volume_category.add_unit (new Unit ("microlitre", _("Microlitres"), dpgettext2 (null, "unit-format", "%s ÎL"), "0.000001x", "1000000x", dpgettext2 (null, "unit-symbols", "mmÂ,ÎL,uL")));
+        weight_category.add_unit (new Unit ("tonne", _("Tonnes"), dpgettext2 (null, "unit-format", "%s T"), "1000x", "x/1000", dpgettext2 (null, "unit-symbols", "tonne,tonnes")));
+        weight_category.add_unit (new Unit ("kilograms", _("Kilograms"), dpgettext2 (null, "unit-format", "%s kg"), "x", "x", dpgettext2 (null, "unit-symbols", "kilogram,kilograms,kilogramme,kilogrammes,kg,kgs")));
+        weight_category.add_unit (new Unit ("pound", _("Pounds"), dpgettext2 (null, "unit-format", "%s lb"), "0.45359237x", "x/0.45359237", dpgettext2 (null, "unit-symbols", "pound,pounds,lb")));
+        weight_category.add_unit (new Unit ("ounce", _("Ounces"), dpgettext2 (null, "unit-format", "%s oz"), "0.02834952x", "x/0.02834952", dpgettext2 (null, "unit-symbols", "ounce,ounces,oz")));
+        weight_category.add_unit (new Unit ("gram", _("Grams"), dpgettext2 (null, "unit-format", "%s g"), "0.001x", "1000x", dpgettext2 (null, "unit-symbols", "gram,grams,gramme,grammes,g")));
+        duration_category.add_unit (new Unit ("year", _("Years"), dpgettext2 (null, "unit-format", "%s years"), "31557600x", "x/31557600", dpgettext2 (null, "unit-symbols", "year,years")));
+        duration_category.add_unit (new Unit ("day", _("Days"), dpgettext2 (null, "unit-format", "%s days"), "86400x", "x/86400", dpgettext2 (null, "unit-symbols", "day,days")));
+        duration_category.add_unit (new Unit ("hour", _("Hours"), dpgettext2 (null, "unit-format", "%s hours"), "3600x", "x/3600", dpgettext2 (null, "unit-symbols", "hour,hours")));
+        duration_category.add_unit (new Unit ("minute", _("Minutes"), dpgettext2 (null, "unit-format", "%s minutes"), "60x", "x/60", dpgettext2 (null, "unit-symbols", "minute,minutes")));
+        duration_category.add_unit (new Unit ("second", _("Seconds"), dpgettext2 (null, "unit-format", "%s s"), "x", "x", dpgettext2 (null, "unit-symbols", "second,seconds,s")));
+        duration_category.add_unit (new Unit ("millisecond", _("Milliseconds"), dpgettext2 (null, "unit-format", "%s ms"), "0.001x", "1000x", dpgettext2 (null, "unit-symbols", "millisecond,milliseconds,ms")));
+        duration_category.add_unit (new Unit ("microsecond", _("Microseconds"), dpgettext2 (null, "unit-format", "%s Îs"), "0.000001x", "1000000x", dpgettext2 (null, "unit-symbols", "microsecond,microseconds,us,Îs")));
+        temperature_category.add_unit (new Unit ("degree-celcius", _("Celsius"), dpgettext2 (null, "unit-format", "%s ËC"), "x+273.15", "x-273.15", dpgettext2 (null, "unit-symbols", "degC,ËC")));
+        temperature_category.add_unit (new Unit ("degree-farenheit", _("Farenheit"), dpgettext2 (null, "unit-format", "%s ËF"), "(x+459.67)*5/9", "x*9/5-459.67", dpgettext2 (null, "unit-symbols", "degF,ËF")));
+        temperature_category.add_unit (new Unit ("degree-kelvin", _("Kelvin"), dpgettext2 (null, "unit-format", "%s K"), "x", "x", dpgettext2 (null, "unit-symbols", "K")));
+        temperature_category.add_unit (new Unit ("degree-rankine", _("Rankine"), dpgettext2 (null, "unit-format", "%s ËR"), "x*5/9", "x*9/5", dpgettext2 (null, "unit-symbols", "degR,ËR,ËRa")));
+
+        var currency_category = default_unit_manager.add_category ("currency", _("Currency"));
+        var currencies = CurrencyManager.get_default ().get_currencies ();
+        currencies.sort ((a, b) => { return strcmp (a.display_name, b.display_name); });
+        foreach (var currency in currencies)
+        {
+            /* Translators: result of currency conversion, %s is the symbol, %%s is the placeholder for amount, i.e.: USD100 */
+            var format = _("%s%%s").printf (currency.symbol);
+            var unit = new Unit (currency.name, currency.display_name, format, null, null, currency.name);
+            currency_category.add_unit ( unit);
+        }
+
+        return default_unit_manager;
+    }
+
+    public UnitCategory add_category (string name, string display_name)
+    {
+        var category = new UnitCategory (name, display_name);
+        categories.append (category);
+        return category;
+    }
+
+    public List<UnitCategory> get_categories ()
+    {
+        var r = new List<UnitCategory> ();
+        foreach (var c in categories)
+            r.append (c);
+        return r;
+    }
+
+    public UnitCategory? get_category (string category)
+    {
+        foreach (var c in categories)
+            if (c.name == category)
+                return c;
+
+        return null;
+    }
+
+    public Unit? get_unit_by_name (string name)
+    {
+        foreach (var c in categories)
+        {
+            var u = c.get_unit_by_name (name);
+            if (u != null)
+                return u;
+        }
+
+        return null;
+    }
+
+    public Unit? get_unit_by_symbol (string symbol)
+    {
+        foreach (var c in categories)
+        {
+            var u = c.get_unit_by_symbol (symbol);
+            if (u != null)
+                return u;
+        }
+
+        return null;
+    }
+
+    public Number? convert_by_symbol (Number x, string x_symbol, string z_symbol)
+    {
+        foreach (var c in categories)
+        {
+            var x_units = c.get_unit_by_symbol (x_symbol);
+            var z_units = c.get_unit_by_symbol (z_symbol);
+            if (x_units != null && z_units != null)
+                return c.convert (x, x_units, z_units);
+        }
+
+        return null;
+    }
+}
+
+public class UnitCategory : Object
+{
+    private List<Unit> units;
+
+    private string _name;
+    public string name { owned get { return _name; } }
+
+    private string _display_name;
+    public string display_name { owned get { return _display_name; } }
+
+    public UnitCategory (string name, string display_name)
+    {
+        _name = name;
+        _display_name = display_name;
+        units = new List<Unit> ();
+    }
+
+    public void add_unit (Unit unit)
+    {
+        units.append (unit);
+    }
+
+    public Unit? get_unit_by_name (string name)
+    {
+        foreach (var unit in units)
+            if (unit.name == name)
+                return unit;
+
+        return null;
+    }
+
+    public Unit? get_unit_by_symbol (string symbol)
+    {
+        foreach (var unit in units)
+            if (unit.matches_symbol (symbol))
+                return unit;
+
+        return null;
+    }
+
+    public unowned List<Unit> get_units ()
+    {
+        return units;
+    }
+
+    public Number? convert (Number x, Unit x_units, Unit z_units)
+    {
+        var t = x_units.convert_from (x);
+        if (t == null)
+            return null;
+        return z_units.convert_to (t);
+    }
+}
+
+public class Unit : Object
+{
+    private string _name;
+    public string name { owned get { return _name; } }
+
+    private string _display_name;
+    public string display_name { owned get { return _display_name; } }
+
+    private string _format;
+    private List<string> _symbols;
+    private string? from_function;
+    private string? to_function;
+    private Serializer serializer;
+
+    public Unit (string name, string display_name, string format, string? from_function, string? to_function, string symbols)
+    {
+        serializer = new Serializer (DisplayFormat.AUTOMATIC, 10, 2);
+        serializer.set_leading_digits (6);
+
+        _name = name;
+        _display_name = display_name;
+        this._format = format;
+        this.from_function = from_function;
+        this.to_function = to_function;
+        _symbols = new List<string> ();
+        var symbol_names = symbols.split (",", 0);
+        foreach (var symbol_name in symbol_names)
+            _symbols.append (symbol_name);
+    }
+
+    public bool matches_symbol (string symbol)
+    {
+        foreach (var s in _symbols)
+            if (s == symbol)
+                return true;
+
+        return false;
+    }
+
+    public unowned List<string> get_symbols ()
+    {
+        return _symbols;
+    }
+
+    public Number? convert_from (Number x)
+    {
+        if (from_function != null)
+            return solve_function (from_function, x);
+        else
+        {
+            // FIXME: Hack to make currency work
+            var r = CurrencyManager.get_default ().get_value (name);
+            if (r == null)
+                return null;
+            return x.divide (r);
+        }
+    }
+
+    public Number? convert_to (Number x)
+    {
+        if (to_function != null)
+            return solve_function (to_function, x);
+        else
+        {
+            // FIXME: Hack to make currency work
+            var r = CurrencyManager.get_default ().get_value (name);
+            if (r == null)
+                return null;
+            return x.multiply (r);
+        }
+    }
+
+    public string format (Number x)
+    {
+        var number_text = serializer.to_string (x);
+        return _format.printf (number_text);
+    }
+
+    private Number? solve_function (string function, Number x)
+    {
+        var equation = new UnitSolveEquation (function, x);
+        equation.base = 10;
+        equation.wordlen = 32;
+
+        var z = equation.parse ();
+        if (z == null)
+            warning ("Failed to convert value: %s", function);
+
+        return z;
+    }
+}
+
+private class UnitSolveEquation : Equation
+{
+    private Number x;
+
+    public UnitSolveEquation (string function, Number x)
+    {
+        base (function);
+        this.x = x;
+    }
+    
+    public override bool variable_is_defined (string name)
+    {
+        return true;
+    }
+
+    public override Number? get_variable (string name)
+    {
+        return x;
+    }
+}
\ No newline at end of file



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]