Home Manual Reference Source

src/Integer.js

import {DEFAULT_DISPLAY_BASE} from './DEFAULT_DISPLAY_BASE.js';
import {ZeroDivisionError} from './ZeroDivisionError.js';

import {ValueError} from '@aureooms/js-error';

import {_from_number} from './_from_number.js';

import {
	stringify,
	convert,
	_trim_positive,
	_alloc,
	_copy,
	_zeros,
	jz,
	cmp,
	eq,
	add,
	_sub,
	mul,
	_idivmod,
	_pow_double,
	increment,
	euclidean_algorithm,
	extended_euclidean_algorithm,
} from '@aureooms/js-integer-big-endian';

import {MIN_NUMBER, MAX_NUMBER, MAX_BASE} from './_limits.js';

export class Integer {
	constructor(base, is_negative, limbs) {
		this._base = base;
		this._is_negative = is_negative;
		this._limbs = limbs;
	}

	move(other) {
		other._base = this._base;
		other._is_negative = this._is_negative;
		other._limbs = this._limbs;
		return other;
	}

	clone() {
		return new Integer(this._base, this._is_negative, this._limbs);
	}

	_limbs_in_base(base) {
		// TODO save result for later ? Maybe replace base ?
		return this._base === base
			? this._limbs
			: convert(this._base, base, this._limbs, 0, this._limbs.length);
	}

	toString(base = DEFAULT_DISPLAY_BASE) {
		if (this.iszero()) return '0';

		const digits = stringify(
			this._base,
			base,
			this._limbs,
			0,
			this._limbs.length,
		);

		return this._is_negative ? '-' + digits : digits;
	}

	add(other) {
		if (this._is_negative !== other._is_negative) {
			return other._is_negative
				? this.sub(other.opposite())
				: other.sub(this.opposite());
		}

		const result_is_negative = this._is_negative;
		const r = this._base;

		const a = this._limbs;

		const b = other._limbs_in_base(r);

		const c = _zeros(Math.max(a.length, b.length) + 1);

		add(r, a, 0, a.length, b, 0, b.length, c, 0, c.length);

		return new Integer(r, result_is_negative, c);
	}

	iadd(other) {
		// TODO optimize but be careful with side effects
		return this.add(other).move(this);
	}

	addn(number) {
		// TODO optimize
		return this.add(_from_number(number));
	}

	iaddn(number) {
		// TODO optimize but be careful with side effects
		return this.addn(number).move(this);
	}

	sub(other) {
		if (this._is_negative !== other._is_negative) {
			return other._is_negative
				? this.add(other.opposite())
				: this.opposite().add(other).opposite();
		}
		// /!\ _sub needs |c| >= |a| >= |b|

		const r = this._base;
		const a = this._limbs;
		const aj = a.length;
		const ai = _trim_positive(a, 0, aj);

		if (ai >= aj) return other.opposite();

		const b = other._limbs_in_base(r);
		const bj = b.length;
		const bi = _trim_positive(b, 0, bj);

		if (bi >= bj) return this.clone();

		if (cmp(a, ai, aj, b, bi, bj) < 0) {
			const c = _zeros(bj - bi);

			_sub(r, b, bi, bj, a, ai, aj, c, 0, c.length);

			return new Integer(r, ~this._is_negative, c);
		}

		const c = _zeros(aj - ai);

		_sub(r, a, ai, aj, b, bi, bj, c, 0, c.length);

		return new Integer(r, this._is_negative, c);
	}

	isub(other) {
		// TODO optimize but be careful with side effects
		return this.sub(other).move(this);
	}

	subn(number) {
		return this.sub(_from_number(number));
	}

	isubn(number) {
		return this.subn(number).move(this);
	}

	mul(other) {
		const result_is_negative = this._is_negative ^ other._is_negative;
		const r = this._base;

		const a = this._limbs;

		const b = other._limbs_in_base(r);

		const c = _zeros(a.length + b.length);

		mul(r, a, 0, a.length, b, 0, b.length, c, 0, c.length);

		return new Integer(r, result_is_negative, c);
	}

	imul(other) {
		// TODO optimize but be careful with side effects
		return this.mul(other).move(this);
	}

	muln(number) {
		return this.mul(_from_number(number));
	}

	imuln(number) {
		return this.muln(number).move(this);
	}

	/**
	 * Computes <code>this</code> raised to the <code>x</code>th power.
	 * <code>x</code> is a double smaller or equal to 2^53.
	 *
	 * @param {Number} x The power to raise <code>this</code> to.
	 * @return {Integer} <code>this ^ x</code>
	 */
	pown(x) {
		const is_negative = this._is_negative & x & 1 ? -1 : 0;

		const a = this._limbs;
		const c = _zeros(Math.max(1, a.length * x));

		_pow_double(this._base, x, a, 0, a.length, c, 0, c.length);

		return new Integer(this._base, is_negative, c);
	}

	pow(other) {
		return this.pown(other.valueOf());
	}

	ipow(other) {
		// TODO optimize but be careful with side effects
		return this.pow(other).move(this);
	}

	ipown(number) {
		// TODO optimize but be careful with side effects
		return this.pown(number).move(this);
	}

	square() {
		// TODO optimize but be careful with side effects
		// TODO use this.mul(this) instead?
		return this.pown(2);
	}

	isquare() {
		// TODO optimize but be careful with side effects
		// TODO use this.imul(this) instead?
		return this.square().move(this);
	}

	div(other) {
		return this.divmod(other)[0];
	}

	divn(number) {
		return this.div(_from_number(number));
	}

	idiv(other) {
		// TODO optimize but be careful with side effects
		return this.div(other).move(this);
	}

	idivn(number) {
		return this.divn(number).move(this);
	}

	mod(other) {
		return this.divmod(other)[1];
	}

	modn(number) {
		return this.mod(_from_number(number));
	}

	imod(other) {
		// TODO optimize but be careful with side effects
		return this.mod(other).move(this);
	}

	imodn(number) {
		return this.modn(number).move(this);
	}

	divround(other) {
		const [q, r] = this.divmod(other);
		if (r.ge(other.divn(2).addn(other.iseven() ? 0 : 1)))
			increment(q._base, q._limbs, 0, q._limbs.length);
		return q;
	}

	divmod(other) {
		if (other.iszero()) throw new ZeroDivisionError('Integer division by zero'); // Optimize

		const quotient_is_negative = this._is_negative ^ other._is_negative;
		const r = this._base;

		// The underlying algorithm does not allow leading 0's so we trim them.
		const lj = this._limbs.length;
		const li = _trim_positive(this._limbs, 0, lj);

		// Dividend is 0
		if (li >= lj)
			return [new Integer(this._base, 0, [0]), new Integer(this._base, 0, [0])];

		// Dividend (& Remainder)
		const D = _alloc(lj - li);
		_copy(this._limbs, li, lj, D, 0);

		// Divisor
		const d = other._limbs_in_base(r);
		const dj = d.length;
		const di = _trim_positive(d, 0, dj); // Di < dj because d != 0

		// Quotient
		const q = _zeros(D.length);

		_idivmod(r, D, 0, D.length, d, di, dj, q, 0, q.length);

		const Q = new Integer(r, quotient_is_negative, q); // Quotient
		const R = new Integer(r, 0, D); // Remainder

		if ((this._is_negative || other._is_negative) && !jz(D, 0, D.length)) {
			if (other._is_negative) {
				if (this._is_negative) {
					R.negate(); // TODO optimize
				} else {
					increment(r, q, 0, q.length);
					R.iadd(other); // TODO optimize
				}
			} else {
				increment(r, q, 0, q.length);
				R.negate().iadd(other); // TODO optimize
			}
		}

		return [Q, R];
	}

	idivmod(other) {
		// TODO optimize but be careful with side effects
		const [q, r] = this.divmod(other);
		return [q, r.move(this)];
	}

	divmodn(number) {
		return this.divmod(_from_number(number));
	}

	idivmodn(number) {
		const [q, r] = this.divmodn(number);
		return [q, r.move(this)];
	}

	opposite() {
		return new Integer(this._base, ~this._is_negative, this._limbs);
	}

	negate() {
		// TODO optimize but be careful with side effects
		return this.opposite().move(this);
	}

	abs() {
		return this.sign() >= 0 ? this : this.opposite();
	}

	iabs() {
		return this.abs().move(this);
	}

	sign() {
		return this.iszero() ? 0 : this._is_negative ? -1 : 1;
	}

	iszero() {
		return jz(this._limbs, 0, this._limbs.length);
	}

	isone() {
		if (this._is_negative) return false;
		return eq(this._limbs, 0, this._limbs.length, [1], 0, 1);
	}

	isnonzero() {
		return !this.iszero();
	}

	isnegative() {
		return this._is_negative === -1;
	}

	ispositive() {
		return this.sign() > 0;
	}

	isnonnegative() {
		return !this.isnegative();
	}

	isnonpositive() {
		return !this.ispositive();
	}

	parity() {
		// TODO optimize this, there is a much faster way to test for parity
		// when the base is a multiple of two
		return this.modn(2);
	}

	iseven() {
		return this.parity().iszero();
	}

	isodd() {
		return !this.iseven();
	}

	bin() {
		return this.toString(2);
	}

	oct() {
		return this.toString(8);
	}

	hex() {
		return this.toString(16);
	}

	toJSON() {
		return this.hex();
	}

	digits(base = DEFAULT_DISPLAY_BASE) {
		// TODO Once #to is implemented we can rewrite this as
		// return this.to(LITTLE_ENDIAN, base, Array) ;
		return convert(
			this._base,
			base,
			this._limbs,
			0,
			this._limbs.length,
		).reverse();
	}

	bits() {
		return this.digits(2);
	}

	divides(other) {
		return other.mod(this).iszero();
	}

	divide_knowing_divisible_by(other) {
		// TODO optimize
		return this.div(other);
	}

	cmp(other) {
		// TODO optimize with _trim_positive

		if (this.iszero()) {
			if (other.iszero()) return 0;
			if (other._is_negative) return 1;
			return -1;
		}

		if (this._is_negative < other._is_negative) return -1;
		if (this._is_negative > other._is_negative) return 1;

		const a = this._limbs;
		const b = other._limbs_in_base(this._base);

		return this._is_negative === 0
			? cmp(a, 0, a.length, b, 0, b.length)
			: cmp(b, 0, b.length, a, 0, a.length);
	}

	cmpn(number) {
		return this.cmp(_from_number(number));
	}

	eq(other) {
		return this.cmp(other) === 0;
	}

	eqn(number) {
		return this.cmpn(number) === 0;
	}

	ge(other) {
		return this.cmp(other) >= 0;
	}

	gen(number) {
		return this.cmpn(number) >= 0;
	}

	gt(other) {
		return this.cmp(other) > 0;
	}

	gtn(number) {
		return this.cmpn(number) > 0;
	}

	le(other) {
		return this.cmp(other) <= 0;
	}

	len(number) {
		return this.cmpn(number) <= 0;
	}

	lt(other) {
		return this.cmp(other) < 0;
	}

	ltn(number) {
		return this.cmpn(number) < 0;
	}

	ne(other) {
		return this.cmp(other) !== 0;
	}

	nen(number) {
		return this.cmpn(number) !== 0;
	}

	gcd(other) {
		const r = this._base;
		const a = this._limbs;
		const b = other._limbs_in_base(r);
		const [d, di, dj] = euclidean_algorithm(r, a, 0, a.length, b, 0, b.length);
		const gcd = _alloc(dj - di);
		_copy(d, di, dj, gcd, 0);
		return new Integer(r, 0, gcd);
	}

	egcd(other) {
		const r = this._base;
		const a = this._limbs;
		const b = other._limbs_in_base(r);
		const [
			R0,
			R0i,
			S0,
			S0i,
			T0,
			T0i,
			S1,
			S1i,
			T1,
			T1i,
			steps,
		] = extended_euclidean_algorithm(r, a, 0, a.length, b, 0, b.length);
		const gcd = _alloc(R0.length - R0i);
		_copy(R0, R0i, R0.length, gcd, 0);
		const x = _alloc(S0.length - S0i);
		_copy(S0, S0i, S0.length, x, 0);
		const y = _alloc(T0.length - T0i);
		_copy(T0, T0i, T0.length, y, 0);
		const u = _alloc(S1.length - S1i);
		_copy(S1, S1i, S1.length, u, 0);
		const v = _alloc(T1.length - T1i);
		_copy(T1, T1i, T1.length, v, 0);
		return {
			// TODO use immutable zero
			gcd: new Integer(r, 0, gcd),
			x:
				x.length > 0
					? new Integer(r, this._is_negative ^ ((steps % 2) - 1), x)
					: new Integer(r, 0, [0]),
			y:
				y.length > 0
					? new Integer(r, other._is_negative ^ -(steps % 2), y)
					: new Integer(r, 0, [0]),
			u:
				u.length > 0
					? new Integer(r, this._is_negative ^ -(steps % 2), u)
					: new Integer(r, 0, [0]),
			v:
				v.length > 0
					? new Integer(r, other._is_negative ^ ((steps % 2) - 1), v)
					: new Integer(r, 0, [0]),
		};
	}

	valueOf() {
		if (this.gtn(MAX_NUMBER))
			throw new ValueError(
				`Cannot call valueOf on Integer larger than ${MAX_NUMBER}. Got ${this.toString()}`,
			);
		if (this.ltn(MIN_NUMBER))
			throw new ValueError(
				`Cannot call valueOf on Integer smaller than ${MIN_NUMBER}. Got ${this.toString()}`,
			);

		const limbs = convert(
			this._base,
			MAX_BASE,
			this._limbs,
			0,
			this._limbs.length,
		);

		const sign = this._is_negative ? -1 : 1;

		const value =
			limbs.length === 2 ? limbs[0] * MAX_BASE + limbs[1] : limbs[0];

		return sign * value;
	}

	toNumber() {
		return this.valueOf();
	}
}