UNPKG

@fleupold/dex-contracts

Version:

Contracts for dFusion multi-token batch auction exchange

181 lines (152 loc) 5.23 kB
import BN from "bn.js"; const MAX_FLOAT_WIDTH = 1024; const MAX_FLOAT_PRECISION = 52; export class Fraction { private numerator: BN; private denominator: BN; constructor(numerator: BN | number, denominator: BN | number) { this.numerator = new BN(numerator); this.denominator = new BN(denominator); this.reduce(); } isZero(): boolean { return this.numerator.isZero(); } eq(other: Fraction): boolean { const diff = this.sub(other); return diff.isZero(); } gt(other: Fraction): boolean { const diff = this.sub(other); return diff.numerator.mul(diff.denominator).gt(new BN(0)); } lt(other: Fraction): boolean { const diff = this.sub(other); return diff.numerator.mul(diff.denominator).lt(new BN(0)); } reduce(): void { const greatest_common_divisor = this.numerator.gcd(this.denominator); this.numerator = this.numerator.div(greatest_common_divisor); this.denominator = this.denominator.div(greatest_common_divisor); } inverted(): Fraction { return new Fraction(this.denominator, this.numerator); } negated(): Fraction { return new Fraction(this.numerator.neg(), this.denominator); } mul(other: Fraction): Fraction { return new Fraction( this.numerator.mul(other.numerator), this.denominator.mul(other.denominator), ); } abs(): Fraction { return new Fraction(this.numerator.abs(), this.denominator.abs()); } div(other: Fraction): Fraction { return this.mul(other.inverted()); } sub(other: Fraction): Fraction { return this.add(other.negated()); } add(other: Fraction): Fraction { return new Fraction( this.numerator .mul(other.denominator) .iadd(other.numerator.mul(this.denominator)), this.denominator.mul(other.denominator), ); } toNumber(): number { let numerator = this.numerator.clone(); let denominator = this.denominator.clone(); // If ratio between numerator and denominator is larger than the maximum // float number precision, use integer division if ( !denominator.isZero() && numerator.div(denominator).bitLength() > MAX_FLOAT_PRECISION ) { numerator = numerator.div(denominator); denominator = new BN(1); } else if ( !numerator.isZero() && denominator.div(numerator).bitLength() > MAX_FLOAT_PRECISION ) { denominator = denominator.div(numerator); numerator = new BN(1); } // Prevent overflow by only keeping the most 1023 significant bits. // Since 2**1023 < Number.MAX_VALUE < 2**1024 this is safe. if ( Math.max( parseInt(numerator.toString()), parseInt(denominator.toString()), ) === Infinity ) { const longestWidth = Math.max( numerator.bitLength(), denominator.bitLength(), ); numerator = numerator.ishrn(longestWidth - MAX_FLOAT_WIDTH + 1); denominator = denominator.ishrn(longestWidth - MAX_FLOAT_WIDTH + 1); } return parseInt(numerator.toString()) / parseInt(denominator.toString()); } /** * Represents a Javascript number as a pair numerator/denominator without precision loss * by retrieving mantissa, exponent, and sign from its bit representation * @param number - The Javascript number to be represented * @returns a BigInt array with two elements: numerator and denominator */ private static numberToNumAndDen(number: number): [bigint, bigint] { const view = new DataView(new ArrayBuffer(8)); view.setFloat64(0, number); const bits = view.getBigUint64(0); const sign = bits >> BigInt(63) ? BigInt(-1) : BigInt(1); const exponent = ((bits >> BigInt(52)) & BigInt(0x7ff)) - BigInt(1023); const one = BigInt(1) << BigInt(52); const mantissa = bits & (one - BigInt(1)); // number is 1.mantissa * 2**exponent switch (exponent) { case BigInt(1024): // infinities and NaN throw Error("Invalid number"); case BigInt(-1023): if (mantissa == BigInt(0)) // positive and negative zero return [BigInt(0), sign]; // subnormal numbers else throw Error("Subnormal numbers are not supported"); } const mantissa_plus_one = mantissa + one; const shifted_exponent = exponent - BigInt(52); if (shifted_exponent >= BigInt(0)) return [ sign * mantissa_plus_one * (BigInt(1) << shifted_exponent), BigInt(1), ]; else return [sign * mantissa_plus_one, BigInt(1) << -shifted_exponent]; } static fromNumber(number: number): Fraction { const [numerator, denominator] = Fraction.numberToNumAndDen(number); return new Fraction( new BN(numerator.toString()), new BN(denominator.toString()), ); } toBN(): BN { return this.numerator.div(this.denominator); } clone(): Fraction { return new Fraction(this.numerator.clone(), this.denominator.clone()); } static fromJSON(o: FractionJson): Fraction { const numerator = new BN(o.numerator, "hex"); const denominator = new BN(o.denominator, "hex"); return new Fraction(numerator, denominator); } } export interface FractionJson { numerator: string; denominator: string; }