/**
* @module rational-module
* @desc A module that defines a Rational class to emulate the addon-generated Rational class.
* @version 1.0.0
* @author Essam A. El-Sherif
*/
/**
* @class Rational
* @static
* @desc A class for representing and manipulating rational numbers.
*/
class Rational{
/**
* @method
* @static
* @memberof module:rational-module.Rational
* @param {number} a - An integer.
* @param {number} b - An integer.
* @returns {number} The greatest common divisor of two integers.
* @desc Calculates the greatest common divisor of two integers.
*/
static gcd(a, b){
return b === 0 ? a : Rational.gcd(b, a % b);
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @param {object|number} arg1 - [Optional] Rational object or numerator.
* @param {string} arg2 - [Optional] denominator.
* @desc Constructs a new Rational object.
* @throws {TypeError} If more than two arguments are given, or invalid argument type is used.
*/
constructor(arg1, arg2){
switch(arguments.length){
case 0:
this.den = 1;
this.num = 0;
break;
case 1:
if(typeof arg1 === 'object' && arg1 instanceof Rational){
this.den = arg1.getDenominator();
this.num = arg1.getNumerator();
}
else{
this.den = 1;
this.num = this._validate(arg1);
}
break;
case 2:
this.den = this._validate(arg2);
this.num = this._validate(arg1);
this._normalize();
break;
default:
throw TypeError(`Rational: invalid number of arguments`);
}
Object.defineProperty(this, 'den', { enumerable: false });
Object.defineProperty(this, 'num', { enumerable: false });
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @param {any} arg - The argument to be validated as a numerator or denominator.
* @returns {number} An integer number.
* @desc Validates the given argument to be used as a numerator or denominator.
* @throws {TypeError} If the given argument is an invalid numerator or denominator.
*/
_validate(arg){
if(typeof arg === 'number' && !Number.isNaN(arg)){
return Math.trunc(arg);
}
else
if(typeof arg === 'string' && arg && !isNaN(arg)){
return Math.trunc(Number(arg));
}
else
if(typeof arg === 'bigint'){
throw TypeError('Rational: BigInt type is not accepted');
}
else{
throw TypeError(`Rational: invalid argument`);
}
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @desc Normalizes the rational number i.e. no common factors and denominator is positive.
* @throws {TypeError} In case of zero denominator.
*/
_normalize(){
if(this.den === 0){
throw TypeError('Rational: bad rational, zero denominator');
}
else
if(this.num === 0){
this.den = 1;
}
else{
let g = Math.abs( Rational.gcd(this.num, this.den) );
this.num /= g;
this.den /= g;
if(this.den < 0){
this.num = -this.num;
this.den = -this.den;
}
}
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @desc An accessor function to return the numerator.
* @returns {number} The numerator.
*/
getNumerator(){ return this.num; }
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @desc An accessor function to return the denominator.
* @returns {number} The denominator.
*/
getDenominator(){ return this.den; }
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @param {object|number} arg1 - [Optional] Rational object or numerator.
* @param {string} arg2 - [Optional] denominator.
* @desc Assignment to this Rational object.
* @throws {TypeError} If neither of one or two arguments are given, or invalid argument type is used.
*/
assign(arg1, arg2){
switch(arguments.length){
case 1:
if(typeof arg1 === 'object' && arg1 instanceof Rational){
this.num = arg1.getNumerator();
this.den = arg1.getDenominator();
}
else{
this.num = this._validate(arg1);
this.den = 1;
}
break;
case 2:
this.num = this._validate(arg1);
this.den = this._validate(arg2);
this._normalize();
break;
default:
throw TypeError(`Rational: invalid number of arguments`);
}
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @param {object|number} arg - Rational object or integer number.
* @desc Adds to this Rational object another Rational object or an integer value.
* @returns {object} The Rational object denoted by this.
* @throws {TypeError} If other than one arguments is given, or invalid argument type is used.
*/
selfAdd(arg){
switch(arguments.length){
case 1:
if(typeof arg === 'object' && arg instanceof Rational){
let r_num = arg.getNumerator();
let r_den = arg.getDenominator();
let g = Rational.gcd(this.den, r_den);
this.den /= g;
this.num = this.num * (r_den / g) + r_num * this.den;
g = Math.abs( Rational.gcd(this.num, g) );
this.num /= g;
this.den *= (r_den/g);
}
else{
arg = this._validate(arg);
this.num += arg * this.den;
}
return this;
default:
throw TypeError(`Rational: invalid number of arguments`);
}
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @param {object|number} arg - Rational object or integer number.
* @desc Subtracts from this Rational object another Rational object or an integer value.
* @returns {object} The Rational object denoted by this.
* @throws {TypeError} If other than one arguments is given, or invalid argument type is used.
*/
selfSub(arg){
switch(arguments.length){
case 1:
if(typeof arg === 'object' && arg instanceof Rational){
let r_num = arg.getNumerator();
let r_den = arg.getDenominator();
let g = Rational.gcd(this.den, r_den);
this.den /= g;
this.num = this.num * (r_den / g) - r_num * this.den;
g = Math.abs( Rational.gcd(this.num, g) );
this.num /= g;
this.den *= (r_den/g);
}
else{
arg = this._validate(arg);
this.num -= arg * this.den;
}
return this;
default:
throw TypeError(`Rational: invalid number of arguments`);
}
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @param {object|number} arg - Rational object or integer number.
* @desc Multiplies this Rational object by another Rational object or an integer value.
* @returns {object} The Rational object denoted by this.
* @throws {TypeError} If other than one arguments is given, or invalid argument type is used.
*/
selfMul(arg){
switch(arguments.length){
case 1:
if(typeof arg === 'object' && arg instanceof Rational){
let r_num = arg.getNumerator();
let r_den = arg.getDenominator();
let gcd1 = Math.abs( Rational.gcd(this.num, r_den) );
let gcd2 = Math.abs( Rational.gcd(r_num, this.den) );
this.num = (this.num/gcd1) * (r_num/gcd2);
this.den = (this.den/gcd2) * (r_den/gcd1);
}
else{
arg = this._validate(arg);
let gcd = Math.abs( Rational.gcd( arg, this.den ) );
this.num *= arg / gcd;
this.den /= gcd;
}
return this;
default:
throw TypeError(`Rational: invalid number of arguments`);
}
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @param {object|number} arg - Rational object or integer number.
* @desc Divides this Rational object by another Rational object or an integer value.
* @returns {object} The Rational object denoted by this.
* @throws {TypeError} If other than one arguments is given, or invalid argument type is used.
*/
selfDiv(arg){
switch(arguments.length){
case 1:
if(typeof arg === 'object' && arg instanceof Rational){
let r_num = arg.getNumerator();
let r_den = arg.getDenominator();
if(r_num === 0){
throw TypeError(`Rational: division by zero`);
}
if(this.num === 0) return this;
let gcd1 = Math.abs( Rational.gcd(this.num, r_num) );
let gcd2 = Math.abs( Rational.gcd(r_den, this.den) );
this.num = (this.num/gcd1) * (r_den/gcd2);
this.den = (this.den/gcd2) * (r_num/gcd1);
if(this.den < 0){
this.num = -this.num;
this.den = -this.den;
}
}
else{
arg = this._validate(arg);
if(arg === 0){
throw TypeError(`Rational: division by zero`);
}
if(this.num == 0) return this;
let gcd = Math.abs( Rational.gcd(this.num, arg) );
this.num = this.num / gcd;
this.den *= (arg / gcd);
if(this.den < 0){
this.num = -this.num;
this.den = -this.den;
}
}
return this;
default:
throw TypeError(`Rational: invalid number of arguments`);
}
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @param {number} arg - Integer number.
* @desc Raise this Rational object to the power given.
* @returns {object} The Rational object denoted by this.
* @throws {TypeError} If other than one arguments is given, or invalid argument type is used.
*/
selfPow(arg){
switch(arguments.length){
case 1:
arg = this._validate(arg);
let r = new Rational(1);
for(let i = 0; i < Math.abs(arg); i++){
r = r.selfMul(this);
}
if(arg < 0){
r = (new Rational(1)).selfDiv(r);
}
this.num = r.getNumerator();
this.den = r.getDenominator();
return this;
default:
throw TypeError(`Rational: invalid number of arguments`);
}
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @param {object|number} arg - Rational object or integer number.
* @desc Adds this Rational object to another Rational object or an integer value.
* @returns {object} New Rational object.
* @throws {TypeError} If other than one arguments is given, or invalid argument type is used.
*/
add(arg){
switch(arguments.length){
case 1:
let _num = this.num;
let _den = this.den;
if(typeof arg === 'object' && arg instanceof Rational){
let r_num = arg.getNumerator();
let r_den = arg.getDenominator();
let g = Rational.gcd(_den, r_den);
_den /= g;
_num = _num * (r_den / g) + r_num * _den;
g = Math.abs( Rational.gcd(_num, g) );
_num /= g;
_den *= (r_den/g);
}
else{
arg = this._validate(arg);
_num += arg * _den;
}
return new Rational(_num, _den);
default:
throw TypeError(`Rational: invalid number of arguments`);
}
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @param {object|number} arg - Rational object or integer number.
* @desc Subtracts from this Rational object another Rational object or an integer value.
* @returns {object} New Rational object.
* @throws {TypeError} If other than one arguments is given, or invalid argument type is used.
*/
sub(arg){
switch(arguments.length){
case 1:
let _num = this.num;
let _den = this.den;
if(typeof arg === 'object' && arg instanceof Rational){
let r_num = arg.getNumerator();
let r_den = arg.getDenominator();
let g = Rational.gcd(_den, r_den);
_den /= g;
_num = _num * (r_den / g) - r_num * _den;
g = Math.abs( Rational.gcd(_num, g) );
_num /= g;
_den *= (r_den/g);
}
else{
arg = this._validate(arg);
_num -= arg * _den;
}
return new Rational(_num, _den);
default:
throw TypeError(`Rational: invalid number of arguments`);
}
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @param {object|number} arg - Rational object or integer number.
* @desc Multiplies this Rational object by another Rational object or an integer value.
* @returns {object} New Rational object.
* @throws {TypeError} If other than one arguments is given, or invalid argument type is used.
*/
mul(arg){
switch(arguments.length){
case 1:
let _num = this.num;
let _den = this.den;
if(typeof arg === 'object' && arg instanceof Rational){
let r_num = arg.getNumerator();
let r_den = arg.getDenominator();
let gcd1 = Math.abs( Rational.gcd(_num, r_den) );
let gcd2 = Math.abs( Rational.gcd(r_num, _den) );
_num = (_num/gcd1) * (r_num/gcd2);
_den = (_den/gcd2) * (r_den/gcd1);
}
else{
arg = this._validate(arg);
let gcd = Math.abs( Rational.gcd( arg, _den ) );
_num *= arg / gcd;
_den /= gcd;
}
return new Rational(_num, _den);
default:
throw TypeError(`Rational: invalid number of arguments`);
}
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @param {object|number} arg - Rational object or integer number.
* @desc Divides this Rational object by another Rational object or an integer value.
* @returns {object} New Rational object.
* @throws {TypeError} If other than one arguments is given, or invalid argument type is used.
*/
div(arg){
switch(arguments.length){
case 1:
let _num = this.num;
let _den = this.den;
if(typeof arg === 'object' && arg instanceof Rational){
let r_num = arg.getNumerator();
let r_den = arg.getDenominator();
if(r_num === 0){
throw TypeError(`Rational: division by zero`);
}
if(_num === 0) return new Rational(_num, _den);
let gcd1 = Math.abs( Rational.gcd(_num, r_num) );
let gcd2 = Math.abs( Rational.gcd(r_den, _den) );
_num = (_num/gcd1) * (r_den/gcd2);
_den = (_den/gcd2) * (r_num/gcd1);
if(_den < 0){
_num = -_num;
_den = -_den;
}
}
else{
arg = this._validate(arg);
if(arg === 0){
throw TypeError(`Rational: division by zero`);
}
if(_num == 0) return this;
let gcd = Math.abs( Rational.gcd(_num, arg) );
_num = _num / gcd;
_den *= (arg / gcd);
if(_den < 0){
_num = -_num;
_den = -_den;
}
}
return new Rational(_num, _den);
default:
throw TypeError(`Rational: invalid number of arguments`);
}
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @param {number} arg - Integer number.
* @desc Raise this Rational object to the power given.
* @returns {object} New Rational object.
* @throws {TypeError} If other than one arguments is given, or invalid argument type is used.
*/
pow(arg){
switch(arguments.length){
case 1:
arg = this._validate(arg);
let r = new Rational(1);
for(let i = 0; i < Math.abs(arg); i++){
r = r.selfMul(this);
}
if(arg < 0){
r = (new Rational(1)).selfDiv(r);
}
return new Rational(r.getNumerator(), r.getDenominator());
default:
throw TypeError(`Rational: invalid number of arguments`);
}
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @desc Increment and return this Rational object.
* @returns {object} The Rational object denoted by this.
* @throws {TypeError} If an argument was given.
*/
preInc(){
if(arguments.length > 0){
throw TypeError(`Rational: invalid number of arguments`);
}
this.num += this.den;
return this;
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @desc Decrement and return this Rational object.
* @returns {object} The Rational object denoted by this.
* @throws {TypeError} If an argument was given.
*/
preDec(){
if(arguments.length > 0){
throw TypeError(`Rational: invalid number of arguments`);
}
this.num -= this.den;
return this;
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @desc Return this Rational object, then increment it.
* @returns {object} Copy of this Rational object before increment.
* @throws {TypeError} If an argument was given.
*/
postInc(){
if(arguments.length > 0){
throw TypeError(`Rational: invalid number of arguments`);
}
let t = new Rational(this);
this.num += this.den;
return t;
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @desc Return this Rational object, then decrement it.
* @returns {object} Copy of this Rational object before decrement.
* @throws {TypeError} If an argument was given.
*/
postDec(){
if(arguments.length > 0){
throw TypeError(`Rational: invalid number of arguments`);
}
let t = new Rational(this);
this.num -= this.den;
return t;
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @desc Set this Rational object to its absolute value.
* @returns {object} The Rational object denoted by this.
* @throws {TypeError} If an argument was given.
*/
selfAbs(){
if(arguments.length > 0){
throw TypeError(`Rational: invalid number of arguments`);
}
if(this.num < 0) this.num = -this.num;
return this;
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @desc Negate this Rational object sign.
* @returns {object} The Rational object denoted by this.
* @throws {TypeError} If an argument was given.
*/
selfNeg(){
if(arguments.length > 0){
throw TypeError(`Rational: invalid number of arguments`);
}
if(this.num !== 0) this.num = -this.num;
return this;
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @desc Returns absolute value of Rational object.
* @returns {object} New Rational object.
* @throws {TypeError} If an argument was given.
*/
abs(){
if(arguments.length > 0){
throw TypeError(`Rational: invalid number of arguments`);
}
if(this.num < 0) return new Rational(-this.num, this.den);
return new Rational(this.num, this.den);;
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @desc Returns negate of this Rational object sign.
* @returns {object} New Rational object.
* @throws {TypeError} If an argument was given.
*/
neg(){
if(arguments.length > 0){
throw TypeError(`Rational: invalid number of arguments`);
}
if(this.num !== 0) return new Rational(-this.num, this.den);
return new Rational(this.num, this.den);
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @desc Returns true if this Rational object is zero, false otherwise.
* @returns {boolean} True if this Rational object is zero, false otherwise.
* @throws {TypeError} If an argument was given.
*/
not(){
if(arguments.length > 0){
throw TypeError(`Rational: invalid number of arguments`);
}
return !this.num;
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @desc Returns true if this Rational object is not zero, false otherwise.
* @returns {boolean} True if this Rational object is not zero, false otherwise.
* @throws {TypeError} If an argument was given.
*/
bool(){
if(arguments.length > 0){
throw TypeError(`Rational: invalid number of arguments`);
}
return !!this.num;
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @param {object|number} arg - Rational object or integer number.
* @desc Compare this Rational object to the given argument.
* @returns {boolean} True if this Rational object is less than the given argument.
* @throws {TypeError} If other than one arguments is given, or invalid argument type is used.
*/
lessThan(arg){
switch(arguments.length){
case 1:
if(typeof arg === 'object' && arg instanceof Rational){
let ts = {
n: this.num,
d: this.den,
q: Math.trunc(this.num / this.den),
r: this.num % this.den
};
let rs = {
n: arg.getNumerator(),
d: arg.getDenominator(),
q: Math.trunc(arg.getNumerator() / arg.getDenominator()),
r: arg.getNumerator() % arg.getDenominator()
};
while (ts.r < 0){ ts.r += ts.d; --ts.q; }
while (rs.r < 0){ rs.r += rs.d; --rs.q; }
let reverse = 0x0;
for( ;; ){
if(ts.q !== rs.q){
return reverse ? ts.q > rs.q : ts.q < rs.q;
}
reverse ^= 0x1;
if(ts.r === 0 || rs.r === 0) break;
ts.n = ts.d; ts.d = ts.r;
ts.q = Math.trunc(ts.n / ts.d); ts.r = ts.n % ts.d;
rs.n = rs.d; rs.d = rs.r;
rs.q = Math.trunc(rs.n / rs.d); rs.r = rs.n % rs.d;
}
if(ts.r === rs.r)
return false;
else
return ( ts.r !== zero ) !== ( !!reverse );
}
else{
arg = this._validate(arg);
let q = Math.trunc(this.num / this.den);
let r = this.num % this.den;
while(r < 0){ r += this.den; --q; }
return q < arg;
}
default:
throw TypeError(`Rational: invalid number of arguments`);
}
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @param {object|number} arg - Rational object or integer number.
* @desc Compare this Rational object to the given argument.
* @returns {boolean} True if this Rational object is greater than the given argument.
* @throws {TypeError} If other than one arguments is given, or invalid argument type is used.
*/
greaterThan(arg){
switch(arguments.length){
case 1:
if(typeof arg === 'object' && arg instanceof Rational){
return !(this.lessThan(arg) || this.equalTo(arg));
}
else{
arg = this._validate(arg);
return !(this.lessThan(arg) || this.equalTo(arg));
}
default:
throw TypeError(`Rational: invalid number of arguments`);
}
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @param {object|number} arg - Rational object or integer number.
* @desc Compare this Rational object to the given argument.
* @returns {boolean} True if this Rational object is equal to the given argument.
* @throws {TypeError} If other than one arguments is given, or invalid argument type is used.
*/
equalTo(arg){
switch(arguments.length){
case 1:
if(typeof arg === 'object' && arg instanceof Rational){
return this.num === arg.getNumerator() && this.den === arg.getDenominator();
}
else{
arg = this._validate(arg);
return this.num === arg && this.den === 1;
}
default:
throw TypeError(`Rational: invalid number of arguments`);
}
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @param {object|number} arg - Rational object or integer number.
* @desc Compare this Rational object to the given argument.
* @returns {boolean} True if this Rational object is not equal to the given argument.
* @throws {TypeError} If other than one arguments is given, or invalid argument type is used.
*/
notEqualTo(arg){
switch(arguments.length){
case 1:
if(typeof arg === 'object' && arg instanceof Rational){
return !this.equalTo(arg);
}
else{
arg = this._validate(arg);
return !this.equalTo(arg);
}
default:
throw TypeError(`Rational: invalid number of arguments`);
}
}
/**
* @method
* @instance
* @memberof module:rational-module.Rational
* @desc Returns this Rational object converted to a real number.
* @returns {number} Real number.
* @throws {TypeError} If an argument was given.
*/
valueOf(){
if(arguments.length > 0){
throw TypeError(`Rational: invalid number of arguments`);
}
return this.num / this.den;
}
toString(){
if(arguments.length > 0){
throw TypeError(`Rational: invalid number of arguments`);
}
return `${this.num < 0 ? '-' : ''}${Math.abs(this.num)}/${this.den}`;
}
}
module.exports.Rational = Rational;