Source: tpa.js

/**
 * @file The complete code for Total Precision Arithmetic
 * @author Dominic Thwaites
 * @copyright (c) 2016 Dominic Thwaites dominicthwaites@mac.com
 * @licence MIT
 * @module TPA
 */

module.exports = (/** @lends module:TPA*/function(globalObj) { //eslint-disable-line
    'use strict';

    /**
     * The error message given when passing an invalid initial value for a new number
     *
     * @const
     */
    var INPUT_ERROR_MESSAGE='Number initialisation parameter badly formed';
    /**
     * number of DPs to take from a numeric construction and to output with the value() method
     *
     * @const
     */
    var VALUE_DECIMAL_PLACES=8;

    // Polyfill Math.trunc
    Math.trunc = Math.trunc || function(x) {
        return x < 0 ? Math.ceil(x) : Math.floor(x);
    };

    // Polyfill Math.sign
    Math.sign = Math.sign || function(x) {
        x = +x; // convert to a number
        if (x === 0 || isNaN(x)) {
            return x;
        }
        return x > 0 ? 1 : -1;
    };

    /**
     * Tpa stores and manipulates rational numbers with total precision
     *
     * @param {(number|string|module:TPA~Tpa)} [initialValue] Initial value to set this number.
     * 1. Numeric values are only represented to a precision of 8 decimal places and, in any case, are limited by the precision of a JS floating point number. To initialise a number with definite accuracy the string form is recommended.
     * 2. String values can be represented in decimal or fractional form.
     *   * Decimal form: `<+/->iii.ddd[rrr]` where `+/-` is optional, `iii` represents the integer part and `ddd` the decimal part with `[rrr]` representing an optional recurring decimal part
     *   * Fractional form: `<+/-> iii nnn/ddd` where `+/-` is optional, `iii` represents the (optional) integer part, `nnn` the numerator and `ddd` the denominator. The fraction may be top heavy.
     * 3. Tpa instance causes this constructor to return a copy of it.
     *
     * Tpa may be called statically, in which case a new instance is still returned.
     * __Note well:__ If initialValue is itself a Tpa, then the same Tpa is returned *without* making a copy when called statically.
     * @param {boolean} [isInteger=true] Set to `false` to enable this number to represent fractions.
     * If the initialValue is fractional in any way then isInteger will default to `false`.
     * The initial setting of this number (integer or fractional) is always kept throughout its life unless the {@link makeFractional} or {@link makeInteger} methods are called to change it.
     * @see #makeFractional
     * @see #makeInteger
     * @example
     * var a=new Tpa();                 // Creates a new number set to zero
     * var b=Tpa(20);                   // Creates a new number preset to 20
     * var c=Tpa['0.[6]'];              // Creates a new number preset to 2/3
     * var d=new Tpa('2/3'];            // Creates a new number preset to 2/3
     * var e=new Tpa('-4 538/1284');    // Creates a new number preset to -4.4[19003115264797507788161993769470404984423676012461059]
     * var f=new Tpa(b);                // Creates a new number preset to 20
     * var g=Tpa(e);                    // Does NOT create a new number: Object g references Object f
     * var h=Tpa(false);                // Creates a new number set to zero but is configured to represent fractions
     * var i=Tpa(100,false);            // Creates a new number set to 100 but is configured to represent fractions
     * var j=Tpa(100.5,true);           // Creates a new number set to 100 as we explicitly set it to be integer only and the fractional part is ignored
     * var k=Tpa('10 20/3',true);       // Creates a new number set to 16 as we explicitly set it to be integer only and the fractional part is ignored
     * @constructor
     * @throws {external:Error} If the constructor parameters are not valid
     */
    var Tpa=TPA;

    var N=require('./N.js');

    // Utility function to return a remainder in standard form
    function standardRemainder(numerator,denominator) {
        return {
            numerator: numerator instanceof N ? numerator : new N(),
            denominator: denominator instanceof N ? denominator : new N(1)
        };
    }

    // Constructor for a new Tpa
    function TPA(initial,integer) {
        // Logic to redirect a static call to return a new object
        if (!(this instanceof Tpa)) {
            // The only exception to the above is that if an instance of this class is passed in the static
            // call it will be returned with creating a new copy - so long as the type is the same (integer or not)
            if (initial instanceof Tpa && (typeof integer != 'boolean' || integer == initial.integer)) return initial;
            switch (arguments.length) {
            case 0:
                return new Tpa();
            case 1:
                return new Tpa(initial);
            default:
                return new Tpa(initial, integer);
            }
        }
        return this.set.apply(this,arguments);
    }

    /**
     * Sets this number to a new value
     *
     * Parameters passed are exactly those expected for construction of a new Tpa
     *
     * @param {(number|string|module:TPA~Tpa)} [initialValue] Initial value to set this number.
     * @param {boolean} [isInteger=true] Set to `false` to enable this number to represent fractions.
     * @return {module:TPA~Tpa} this number for chaining purposes
     */
    Tpa.prototype.set=function(initialValue,isInteger) {
        var me = this;
        // Establish whether this instance is to be an integer only
        this.integer=true;
        if (typeof initialValue == 'boolean') this.integer=initialValue;
        if (typeof isInteger=='boolean') this.integer=isInteger;

        // If the constructor argument is an instance of this class then we return a copy of that instance
        if (initialValue instanceof Tpa) {
            this.number=new N(initialValue.number);
            if (!(typeof isInteger == 'boolean') && initialValue.isFractional()) this.integer=false;
            if (!this.integer) {
                if (initialValue.isInteger()) this.remainder=standardRemainder();
                else {
                    this.remainder = {
                        numerator: new N(initialValue.remainder.numerator),
                        denominator: new N(initialValue.remainder.denominator)
                    };
                }
            }
            return this;
        }

        // If the constructor argument is a number then we preset this number with the number given
        if (typeof initialValue == 'number') {
            this.number=new N(initialValue);
            var denominator=Math.pow(10,VALUE_DECIMAL_PLACES);
            var numerator=Math.trunc((initialValue-Math.trunc(initialValue)).toFixed(VALUE_DECIMAL_PLACES)*denominator);

            // Note that the fractional part only takes 8 decimal places (as per VALUE_DECIMAL_PLACES)
            if (typeof isInteger!='boolean' || !this.integer) {
                if (typeof isInteger!='boolean') this.integer=numerator==0;
                while (numerator != 0 && numerator % 10 == 0) {
                    numerator/=10;
                    denominator/=10;
                }
                if (numerator>0 || !this.integer) {
                    this.integer=false;
                    this.remainder = {
                        numerator: new N(numerator),
                        denominator: new N(denominator)
                    };
                }
            }
            return this;
        }

        // Helper function to parse and create a fraction from an arbitrary number of decimal input representation
        // Note that the input is assumed to be "clean"
        function parseDecimal(input,sign) {
            me.remainder=standardRemainder();
            for (var i= 0,recurring=null; i<input.length; i++) {
                if (input[i]=='[' && recurring===null) {
                    recurring = {
                        numerator: new N(me.remainder.numerator),
                        denominator: new N(me.remainder.denominator)
                    };
                    continue;
                }
                // The recurring section is mathematically achieved by subtracting the values at the start
                if (recurring && input[i]==']') {
                    me.remainder.numerator.subtract(recurring.numerator);
                    me.remainder.denominator.subtract(recurring.denominator);
                    return this;
                }
                me.remainder.denominator._digitMultiplyWithAdd(10, 0);
                me.remainder.numerator._digitMultiplyWithAdd(10, sign*parseInt(input[i]));
            }
            if (recurring) throw new Error(INPUT_ERROR_MESSAGE);
        }

        // Helper function to parse and create a fraction from a fractional input representation
        // Note that the input is assumed to be "clean" and that the regexps below will match
        function parseFraction(input) {
            var remainder=standardRemainder(new N(input.match(/^[\+\-]?\d+/)[0]),new N(input.match(/\/(\d+)$/)[1]));
            if (remainder.denominator.isZero()) throw new Error(INPUT_ERROR_MESSAGE);
            if (!me.integer) {
                me.remainder=remainder;
                me._normaliseRemainder();
            }
            else me.number.add(remainder.numerator.quotient(remainder.denominator));
        }

        if (typeof initialValue == 'string') {
            initialValue=initialValue.trim();
            if (!this.integer) this.remainder=standardRemainder();
            if (initialValue.match(/^[\+\-]?\d+\/\d+$/)) {                               // [+/-]nnn/nnn
                if (typeof isInteger != 'boolean') this.integer = false;
                this.number=new N();
                parseFraction(initialValue);
            } else {
                var sign=initialValue[0]=='-';
                if (initialValue.match(/^[\+\-]?\d*/)===null) throw new Error(INPUT_ERROR_MESSAGE);
                this.number=new N(initialValue.match(/^[\+\-]?\d*/)[0]);                 // [+/-]nnn
                if (initialValue.match(/^[\+\-]?\d*$/)) return this;
                var match=initialValue.match(/^[\+\-]?\d*([\. ])/);
                if (match===null) throw new Error(INPUT_ERROR_MESSAGE);
                var remaining=initialValue.match(/^[\+\-]?\d*[\. ](.*)/)[1];             // [+/-]nnn[./ ]
                if (typeof isInteger != 'boolean') this.integer = false;
                switch (match[1]) {
                case '.':
                    // Parse for decimal representation
                    if (remaining.match(/^\d*\[?\d+\]?$/)===null) throw new Error(INPUT_ERROR_MESSAGE);
                    if (!this.integer) parseDecimal(remaining,sign ? -1 : 1);
                    break;

                case ' ':
                    // Parse for fractional representation
                    if (remaining.match(/^\d+\/\d+$/)===null) throw new Error(INPUT_ERROR_MESSAGE);
                    parseFraction((sign ? '-' : '')+remaining);
                    break;
                }
            }
            return this;
        }

        if (typeof initialValue=='undefined' && arguments.length>0) throw new Error(INPUT_ERROR_MESSAGE);
        // If we had no initialiser, then set this number to zero
        this.number=new N();
        if (!this.integer) this.remainder = standardRemainder();
        return this;
    };

    /**
     * Attempts a simplification of the remaining fraction
     *
     * Finding common factors (which would be prime numbers) is a time-consuming job.
     * Just as well, as otherwise most security mechanism (i.e. RSA) could be hacked in a jiffy.
     * So there is a limit to how large a fraction can be simplified. A realistic limit has therefore been
     * established here whereby prime factors can not exceed the BASE of the internal number representation.
     * Thus the highest prime explored is **33,554,393**.
     * Fractions that have their numerator larger than the square of this number may not be completely simplified - i.e. numbers of more than 15 digits.
     *
     * @param {number} [milliseconds=100] The maximum time in milliseconds to attempt simplification. 0 sets no limit.
     * @returns {boolean} `true` if simplification complete, `false` if there may still be some common factors left
     * @throws {external:Error} If an invalid limit is given
     */
    Tpa.prototype.simplify=function(milliseconds) {
        // Preparations
        if (arguments.length>0 && (typeof milliseconds!='number' || isNaN(milliseconds)))
            throw new Error('Simplify() takes an optional numeric argument specifying the maximum number of millisecondsto process');
        if (typeof milliseconds=='undefined') milliseconds=100;
        if (this.isInteger() || this.remainder.numerator.isZero()) return true;
        var isNegative=this.remainder.numerator.isNegative();
        this.remainder.numerator.abs();
        var limit= this.remainder.numerator._roughSqrt().value();
        var primes=new N.Primes();
        var start=new Date().getTime();
        var factor=new N().set(1);

        // Loop through all the primes up to the square root of the numerator to test for common factors
        for (var prime= primes.next(); prime>0 && prime<=limit; prime= primes.next()) {
            while (this.remainder.numerator.isDivisibleBy(prime)) {
                this.remainder.numerator.digitDivide(prime);
                if (this.remainder.denominator.isDivisibleBy(prime)) this.remainder.denominator.digitDivide(prime);
                else factor.digitMultiply(prime);
            }
            // Abort if our time is up
            if (new Date().getTime()-start>milliseconds && milliseconds>0) {
                prime=0;
                break;
            }
        }

        // Clean up and set the factorised remainder accordingly
        var denominator=new N(this.remainder.denominator);
        var remainder=denominator.divide(this.remainder.numerator);
        if (remainder.isZero()) {
            this.remainder.denominator=denominator;
            this.remainder.numerator=factor;
            prime=1;
        } else this.remainder.numerator.multiply(factor);
        if (isNegative) this.remainder.numerator.negate();
        // If prime is zero then we never got to finish
        return prime>0;
    };

    /**
     * Sets this number to hold integers only - removes any existing fractional part
     *
     * @returns {module:TPA~Tpa} This number for chaining purposes
     */
    Tpa.prototype.makeInteger=function() {
        this.integer=true;
        delete this.remainder;
        return this;
    };

    /**
     * Sets this number to accept fractional amounts, if not already set
     *
     * @returns {module:TPA~Tpa} This number for chaining purposes
     */
    Tpa.prototype.makeFractional=function() {
        if (this.integer) {
            this.integer = false;
            this.remainder = standardRemainder();
        }
        return this;
    };

    /**
     * @returns {boolean} `true` if this number only represents integers
     */
    Tpa.prototype.isInteger=function() {
        return this.integer;
    };

    /**
     * @returns {boolean} `true` if this number is capable of representing fractions
     */
    Tpa.prototype.isFractional=function() {
        return !this.integer;
    };

    /**
     * @returns {boolean} `true` if this number is less than zero
     */
    Tpa.prototype.isNegative=function() {
        if (this.isZero()) return false;
        if (this.number.isZero()) return this.remainder.numerator.isNegative();
        else return this.number.isNegative();
    };

    /**
     * @returns {boolean} `true` if this number is greater than zero
     */
    Tpa.prototype.isPositive=function() {
        if (this.isZero()) return false;
        if (this.number.isZero()) return this.remainder.numerator.isPositive();
        else return this.number.isPositive();
    };

    /**
     * @returns {boolean} `true` if this number is equal to zero
     */
    Tpa.prototype.isZero=function() {
        this._normaliseRemainder();
        return this.number.isZero() && (this.isInteger() || this.remainder.numerator.isZero());
    };

    /**
     * @returns {number} `-1` if this number is negative, `0` if zero or `1` if positive
     */
    Tpa.prototype.sign=function() {
        if (this.isZero()) return 0;
        return this.isNegative() ? -1 : 1;
    };

    /**
     * @returns {boolean} `true` if this number has a non-zero fractional part
     */
    Tpa.prototype.hasFraction=function() {
        if (this.integer) return false;
        this._normaliseRemainder();
        return !this.remainder.numerator.isZero();
    };

    /**
     * Gets the value of this number in standard JS floating point number
     *
     * Note that precision may well be lost in order to accommodate the limitations of floating point numbers.
     * For this reason, the number of decimal places is restricted to 8.
     * Tpa numbers can be so large as to cause an overflow on a floating point number to yield `infinity`
     *
     * @returns {number} A numeric value of this number
     */
    Tpa.prototype.value=function() {
        var power=Math.pow(10,VALUE_DECIMAL_PLACES);
        if (this.integer) return this.number.value();
        else {
            var numerator = new N(this.remainder.numerator).multiply(new N(power));
            numerator.divide(this.remainder.denominator);
            return (this.number.value() + (numerator.value() / power).toFixed(VALUE_DECIMAL_PLACES)*1);
        }
    };

    /**
     * Sets a number to hold fractional value
     *
     * @param {(number|string|module:TPA~Tpa)} number The number to copy
     * @returns {module:TPA~Tpa} A new number with the ability to hold fractions
     */
    Tpa.makeFractional=function(number) {
        return new Tpa(number).makeFractional();
    };

    /**
     * Sets a number to hold integer values only
     *
     * @param {(number|string|module:TPA~Tpa)} number The number to copy
     * @returns {module:TPA~Tpa} A new number *without* the ability to hold fractions
     */
    Tpa.makeInteger=function(number) {
        return new Tpa(number).makeInteger();
    };

    /**
     * Sets the integer part of a number to zero
     *
     * @param {(number|string|module:TPA~Tpa)} number The number from which the integer part is to be removed
     */
    Tpa.frac=function(number) {
        return new Tpa(number).frac();
    };

    /**
     * Sets the fractional part of a number to zero
     *
     * @param {(number|string|module:TPA~Tpa)} number The number from which the fractional part is to be removed
     */
    Tpa.int=function(number) {
        return new Tpa(number).int();
    };

    /**
     * Adds two numbers
     *
     * Aliases: `plus`
     *
     * @param {(number|string|module:TPA~Tpa)} a First number
     * @param {(number|string|module:TPA~Tpa)} b Second number
     * @returns {module:TPA~Tpa} a + b
     */
    Tpa.add=function(a,b) {
        return new Tpa(a).add(b);
    };

    /**
     * Subtracts two numbers
     *
     * Aliases: `minus`, `sub`
     *
     * @param {(number|string|module:TPA~Tpa)} a First number
     * @param {(number|string|module:TPA~Tpa)} b Second number
     * @returns {module:TPA~Tpa} a - b
     */
    Tpa.subtract=function(a,b) {
        return new Tpa(a).subtract(b);
    };

    /**
     * Multiplies two numbers
     *
     * Aliases: `mult`, `times`
     *
     * @param {(number|string|module:TPA~Tpa)} a First number
     * @param {(number|string|module:TPA~Tpa)} b Second number
     * @returns {module:TPA~Tpa} a * b
     */
    Tpa.multiply=function(a,b) {
        return new Tpa(a).multiply(b);
    };

    /**
     * Divides two numbers ( a / b )
     *
     * Aliases: `div`
     *
     * @param {(number|string|module:TPA~Tpa)} a First number
     * @param {(number|string|module:TPA~Tpa)} b Second number
     * @returns {module:TPA~Tpa} a / b
     */
    Tpa.divide=function(a,b) {
        return new Tpa(a).divide(b);
    };

    /**
     * Modulus of two numbers
     *
     * Aliases: `mod`
     *
     * @param {(number|string|module:TPA~Tpa)} a First number
     * @param {(number|string|module:TPA~Tpa)} b Second number
     * @returns {module:TPA~Tpa} a mod b
     */
    Tpa.modulus=function(a,b) {
        return new Tpa(a).mod(b);
    };

    /**
     * Absolute value of a number
     *
     * @param {(number|string|module:TPA~Tpa)} n The number
     * @returns {module:TPA~Tpa} |n|
     */
    Tpa.abs=function(n) {
        return new Tpa(n).abs();
    };

    /**
     * Creates a random number of an approximate number of decimal digits long
     *
     * @param {number} digits The number of decimal digits
     * @returns {module:TPA~Tpa} A new number set a a random value
     */
    Tpa.random=function(digits) {
        if (typeof digits=='number' && digits>0) {
            var result=new Tpa();
            result.number.random(digits);
        } else throw new Error('You must specify a positive number of decimal digits as an approximate size for this number');
        return result;
    };

    /**
     * Compares the given number with this number
     *
     * @param {(number|string|module:TPA~Tpa)} number The number to compare
     * @returns {number} `-1` if this number is less than the given number, `0` if equal, `1` if greater
     */
    Tpa.prototype.compare=function(number) {
        function compare(a,b) {
            return N.abs(a).normalise().positivise().compare(N.abs(b).positivise().normalise());
        }

        if (number===this) return 0;
        number = Tpa(number);
        this._normaliseRemainder();
        number._normaliseRemainder();
        if (this.sign()!=number.sign()) {
            if (this.sign()==0) return -number.sign();
            else return this.sign();
        }
        var result = compare(this.number,number.number);
        if (result == 0 && this.isFractional()) {
            if (number.isFractional()) result=compare(new N(this.remainder.numerator).multiply(number.remainder.denominator),new N(this.remainder.denominator).multiply(number.remainder.numerator));
        }
        return result;
    };

    /**
     * @param {(number|string|module:TPA~Tpa)} number The number to compare
     * @returns {boolean} `true` if this number is less than the given number
     */
    Tpa.prototype.lt=function(number) {
        return this.compare(number)==-1;
    };

    /**
     * @param {(number|string|module:TPA~Tpa)} number The number to compare
     * @returns {boolean} `true` if this number is less than or equal to the given number
     */
    Tpa.prototype.lte=function(number) {
        return this.compare(number)!=1;
    };

    /**
     * @param {(number|string|module:TPA~Tpa)} number The number to compare
     * @returns {boolean} `true` if this number is greater than the given number
     */
    Tpa.prototype.gt=function(number) {
        return this.compare(number)==1;
    };

    /**
     * @param {(number|string|module:TPA~Tpa)} number The number to compare
     * @returns {boolean} `true` if this number is greater than or equal to the given number
     */
    Tpa.prototype.gte=function(number) {
        return this.compare(number)!=-1;
    };

    /**
     * @param {(number|string|module:TPA~Tpa)} number The number to compare
     * @returns {boolean} `true` if this number is equal to the given number
     */
    Tpa.prototype.eq=function(number) {
        return this.compare(number)==0;
    };

    /**
     * Sets the fractional part of this number to zero
     *
     * @return {(number|string|module:TPA~Tpa)} This number for chaining purposes
     */
    Tpa.prototype.int=function() {
        if (!this.integer) this.remainder=standardRemainder();
        return this;
    };

    /**
     * Sets the integer part of this number to zero
     *
     * @return {(number|string|module:TPA~Tpa)} This number for chaining purposes
     */
    Tpa.prototype.frac=function() {
        this._normaliseRemainder().number.reset();
        return this;
    };

    /**
     * Takes the absolute value of this number
     *
     * @return {(number|string|module:TPA~Tpa)} This number for chaining purposes
     */
    Tpa.prototype.abs=function() {
        this.number.abs();
        if (!this.integer) this.remainder.numerator.abs();
        return this;
    };

    /**
     * Multiply this number by the one given
     *
     * Aliases: `mult`
     *
     * If this number is fractional, then it will perform a full fractional multiplication.
     * If it is set as an integer then the multiplication will ignore any fractional part of the multiplier
     *
     * @param {(number|string|module:TPA~Tpa)} number The number to multiply by
     * @returns {module:TPA~Tpa} This number for chaining purposes
     */
    Tpa.prototype.multiply=function(number) {
        if (!(number instanceof TPA)) number=Tpa(number);
        if (!this.integer) {
            if (!number.integer) {
                this.remainder.numerator.multiply(N.temporary(number.remainder.denominator).multiply(number.number).add(number.remainder.numerator))
                                        .add(N.temporary(number.remainder.numerator).multiply(this.number).multiply(this.remainder.denominator));
                this.remainder.denominator.multiply(number.remainder.denominator);
            } else this.remainder.numerator.multiply(number.number);
        }
        this.number.multiply(number.number);
        return this;
    };

    /**
     * Divide this number by the one given
     *
     * Aliases: `div`
     *
     * If this number is fractional, then it will perform a full fractional division.
     * If it is set as an integer then the division will ignore any fractional part of the divisor
     *
     * @param {(number|string|module:TPA~Tpa)} number The number to multiply by
     * @returns {module:TPA~Tpa} This number for chaining purposes
     */
    Tpa.prototype.divide=function(number) {
        if (!(number instanceof TPA)) number=Tpa(number);
        if (!this.integer) {
            if (!number.integer) {
                this.number.multiply(this.remainder.denominator).add(this.remainder.numerator).multiply(number.remainder.denominator);
                this.remainder.numerator =this.number.divide(this.remainder.denominator.multiply(N.temporary(number.number).multiply(number.remainder.denominator).add(number.remainder.numerator)));
            } else {
                this.number.multiply(this.remainder.denominator).add(this.remainder.numerator);
                this.remainder.numerator = this.number.divide(this.remainder.denominator.multiply(number.number));
            }
        } else this.number.divide(number.number);
        return this;
    };

    /**
     * Sets this number to the modulus of the number given
     *
     * Aliases: `mod`
     *
     * Fractional parts of either number are ignored - the modulus is based on the integer parts ony
     *
     * @param {(number|string|module:TPA~Tpa)} number The divisor number
     * @returns {module:TPA~Tpa} This number for chaining purposes
     */
    Tpa.prototype.modulus=function(number) {
        if (!(number instanceof TPA)) number=Tpa(number);
        this.number=this.number.divide(number.number);
        if (!this.integer) this.remainder=standardRemainder();
        return this;
    };

    /**
     * Subtracts the given number from this number
     *
     * Aliases: `sub`, `minus`
     *
     * If this number is fractional, then it will perform a full fractional subtraction.
     * If it is set as an integer then the subtraction will ignore any fractional part of the number to be subtracted
     *
     * @param {(number|string|module:TPA~Tpa)} number The number to subtract
     * @returns {module:TPA~Tpa} This number for chaining purposes
     */
    Tpa.prototype.subtract=function(number) {
        if (!(number instanceof TPA)) number=Tpa(number);
        this.number.subtract(number.number);
        if (!this.integer) {
            if (!number.integer && !number.remainder.numerator.isZero()) {
                this.remainder.numerator.multiply(number.remainder.denominator).subtract(N.temporary(number.remainder.numerator).multiply(this.remainder.denominator));
                this.remainder.denominator.multiply(number.remainder.denominator);
            }
            this._normaliseRemainder();
        }
        return this;
    };

    /**
     * Adds the given number to this number
     *
     * Aliases: `plus`
     *
     * If this number is fractional, then it will perform a full fractional addition.
     * If it is set as an integer then the addition will ignore any fractional part of the number to be added
     *
     * @param {(number|string|module:TPA~Tpa)} number The number to add
     * @returns {module:TPA~Tpa} This number for chaining purposes
     */
    Tpa.prototype.add=function(number) {
        if (!(number instanceof TPA)) number=Tpa(number);
        this.number.add(number.number);
        if (!this.integer) {
            if (!number.integer && !number.remainder.numerator.isZero()) {
                this.remainder.numerator.multiply(number.remainder.denominator).add(N.temporary(number.remainder.numerator).multiply(this.remainder.denominator));
                this.remainder.denominator.multiply(number.remainder.denominator);
            }
            this._normaliseRemainder();
        }
        return this;
    };

    /**
     * Outputs a decimal representation of this number
     *
     * All Tpa numbers are rational and thus have a limited or recurring set of decimal places
     * Recurring decimals are notated in square brackets - e.g. 33.[3] for 33 and one third
     * If there are more decimals to output than the maximum requested the output is cut off and finishes with an ellipsis (...)
     *
     * @param {number} [maxDecimalPlaces=100] The maximum number of decimal places to give
     * @see #toString
     * @returns {string} The number in format: `[-]nnn.ddd[rrr]`
     */
    Tpa.prototype.toDecimal=function(maxDecimalPlaces) {
        return typeof maxDecimalPlaces=='undefined' ? this.toString() : this.toString(maxDecimalPlaces);
    };

    /**
     * Outputs the decimal representation of the integer part of this number only
     *
     * @returns {string} The number in decimal form: `[-]nnn`
     */
    Tpa.prototype.toInteger=function() {
        this._normaliseRemainder();
        return (this.isNegative() ? '-' : '')+N.abs(this.number).toString();
    };

    /**
     * Outputs this number in fractional representation: `[-]nnn nnn/nnn`
     *
     * @returns {string} The number in fractional form
     */
    Tpa.prototype.toFraction=function() {
        var result=this.toInteger();
        if (this.isFractional() && !this.remainder.numerator.isZero()) {
            result=result+' '+ N.abs(this.remainder.numerator).toString();
            result=result+'/'+this.remainder.denominator.toString();
        }
        return result;
    };

    /**
     * Outputs a decimal representation of this number
     *
     * All Tpa numbers are rational and thus have a limited or recurring set of decimal places
     * Recurring decimals are notated in square brackets - e.g. 33.[3] for 33 and one third
     * If there are more decimals to output than the maximum requested the result is cut off andends with an ellipsis (...)
     *
     * @param {number} [maxDecimalPlaces=100] The maximum number of decimal places to give
     * @see #toDecimal
     * @returns {string} The number in format: `[-]nnn.ddd[rrr]`
     */
    Tpa.prototype.toString=function(maxdp) {
        if (typeof maxdp != 'number' || isNaN(maxdp)) {
            if (arguments.length>0) throw new Error('toString() takes an optional parameter to specify the maximum DPs to output [default=100]');
            else maxdp=100;
        }

        var result=this.toInteger();
        if (this.isFractional() && !this.remainder.numerator.isZero()) {
            result+='.';
            var numeratorstore=[];
            for (var numerator=new N(this.remainder.numerator).abs().normalise().positivise(),remainder=0; !numerator.isZero() && maxdp>0; maxdp--) {
                for (var i=numeratorstore.length-1; i>=0; i--) {
                    if (numeratorstore[i].compare(numerator)==0) break;
                }
                if (i>=0) {
                    result=result.substr(0,result.length+i-numeratorstore.length)+'['+result.substr(result.length+i-numeratorstore.length)+']';
                    break;
                }
                numeratorstore.push(new N(numerator));
                remainder = numerator._digitMultiplyWithAdd(10, 0).divide(this.remainder.denominator);
                result+=numerator.lsb();
                numerator=remainder;
            }
            if (maxdp==0 && !numerator.isZero()) result=result+'...';
        }
        return result;
    };

    /**
     * Normalises the remainder - ensures the numerator is less than the denominator
     *
     * @private
     * @returns {module:TPA~Tpa} This number for chaining purposes
     */
    Tpa.prototype._normaliseRemainder=function() {
        if (!this.integer) {
            var numerator = this.remainder.numerator.divide(this.remainder.denominator);
            this.number.add(this.remainder.numerator);
            this.remainder.numerator = numerator;
            if (this.remainder.numerator.isZero()) this.remainder.denominator.set(1);
            else {
                if (this.remainder.numerator.isNegative()) {
                    if (this.number.isPositive()) {
                        this.remainder.numerator.add(this.remainder.denominator);
                        this.number.subtract(N.ONE);
                    }
                } else {
                    if (this.number.isNegative()) {
                        this.remainder.numerator.subtract(this.remainder.denominator);
                        this.number.add(N.ONE);
                    }
                }
            }
        }
        return this;
    };

    // Allow external access to the internal N class - for testing purposes only
    if (typeof PRODUCTION==='undefined') {
        Tpa.N = N;
    }

    // Aliases
    Tpa.plus=Tpa.add;
    Tpa.prototype.plus=Tpa.prototype.add;
    Tpa.minus=Tpa.subtract;
    Tpa.prototype.minus=Tpa.prototype.subtract;
    Tpa.sub=Tpa.subtract;
    Tpa.prototype.sub=Tpa.prototype.subtract;
    Tpa.times=Tpa.multiply;
    Tpa.prototype.times=Tpa.prototype.multiply;
    Tpa.mult=Tpa.multiply;
    Tpa.prototype.mult=Tpa.prototype.multiply;
    Tpa.div=Tpa.divide;
    Tpa.prototype.div=Tpa.prototype.divide;
    Tpa.mod=Tpa.modulus;
    Tpa.prototype.mod=Tpa.prototype.modulus;

    return Tpa;
})();