/**
* @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;
})();