import BN from "bn.js"; import { array, publicKey, str, struct, u128, u16, u64, u8 } from '@coral-xyz/borsh'; import Decimal from "decimal.js"; import { X64 } from "../constants"; export interface Fraction { high: BN; // u64 -> BN low: BN; // u64 -> BN }; export const FractionLayout = struct([ u64('high'), u64('low'), ]); export function fractionToDecimal(fraction: Fraction): Decimal { let high = new Decimal(fraction.high.toString()); let low = new Decimal(fraction.low.toString()); let res = high.add(low.div(X64)); return res; } export function decimalToFraction(decimal: Decimal): Fraction { let res = decimal.mul(X64).floor(); let high = res.div(X64).floor(); let low = res.sub(high.mul(X64)); return { high: new BN(high.toString()), low: new BN(low.toString()) }; } // X64 = 2^64 const X64_BN = new BN(2).pow(new BN(64)); const U64_MAX = new BN(2).pow(new BN(64)).sub(new BN(1)); // Fraction addition: (a + b) // a = high_a * 2^64 + low_a, b = high_b * 2^64 + low_b // result = (high_a + high_b) * 2^64 + (low_a + low_b) // Handle carry from low to high export function fractionAdd(a: Fraction, b: Fraction): Fraction { const lowSum = a.low.add(b.low); const carry = lowSum.gt(U64_MAX) ? new BN(1) : new BN(0); const low = lowSum.mod(X64_BN); const high = a.high.add(b.high).add(carry); // Check overflow: high should fit in u64 if (high.gt(U64_MAX)) { throw new Error("MathError"); } return { high, low }; } // Fraction subtraction: (a - b) // a = high_a * 2^64 + low_a, b = high_b * 2^64 + low_b // result = (high_a - high_b) * 2^64 + (low_a - low_b) // Handle borrow from high to low export function fractionSub(a: Fraction, b: Fraction): Fraction { // Check if a < b if (a.high.lt(b.high) || (a.high.eq(b.high) && a.low.lt(b.low))) { throw new Error("MathError"); } let low = a.low.sub(b.low); let high = a.high.sub(b.high); // Handle borrow if low < 0 if (low.isNeg()) { low = low.add(X64_BN); high = high.sub(new BN(1)); } if (high.isNeg()) { throw new Error("MathError"); } return { high, low }; } // Fraction multiplication: (a * b) / X64 // (high_a * 2^64 + low_a) * (high_b * 2^64 + low_b) / 2^64 // = high_a * high_b * 2^64 + high_a * low_b + low_a * high_b + (low_a * low_b) / 2^64 export function fractionMul(a: Fraction, b: Fraction): Fraction { // Calculate all components const highHigh = a.high.mul(b.high).mul(X64_BN); const highLow = a.high.mul(b.low); const lowHigh = a.low.mul(b.high); const lowLow = a.low.mul(b.low).div(X64_BN); // Sum: highHigh + highLow + lowHigh + lowLow const sum = highHigh.add(highLow).add(lowHigh).add(lowLow); // Split into high and low const high = sum.div(X64_BN); const low = sum.mod(X64_BN); // Check overflow: high should fit in u64 if (high.gt(U64_MAX)) { throw new Error("MathError"); } return { high, low }; } // Fraction division: (a * X64) / b // (high_a * 2^64 + low_a) * 2^64 / (high_b * 2^64 + low_b) export function fractionDiv(a: Fraction, b: Fraction): Fraction { // Check division by zero if (b.high.isZero() && b.low.isZero()) { throw new Error("MathError"); } // numerator = a * X64 = (high_a * 2^64 + low_a) * 2^64 const numerator = a.high.mul(X64_BN).add(a.low).mul(X64_BN); // denominator = b = high_b * 2^64 + low_b const denominator = b.high.mul(X64_BN).add(b.low); // result = numerator / denominator const result = numerator.div(denominator); // Split into high and low const high = result.div(X64_BN); const low = result.mod(X64_BN); // Check overflow: high should fit in u64 if (high.gt(U64_MAX)) { throw new Error("MathError"); } return { high, low }; } // Comparison functions export function fractionLt(a: Fraction, b: Fraction): boolean { if (a.high.lt(b.high)) return true; if (a.high.gt(b.high)) return false; return a.low.lt(b.low); } export function fractionLte(a: Fraction, b: Fraction): boolean { if (a.high.lt(b.high)) return true; if (a.high.gt(b.high)) return false; return a.low.lte(b.low); } export function fractionGt(a: Fraction, b: Fraction): boolean { if (a.high.gt(b.high)) return true; if (a.high.lt(b.high)) return false; return a.low.gt(b.low); } export function fractionGte(a: Fraction, b: Fraction): boolean { if (a.high.gt(b.high)) return true; if (a.high.lt(b.high)) return false; return a.low.gte(b.low); } export function fractionEq(a: Fraction, b: Fraction): boolean { return a.high.eq(b.high) && a.low.eq(b.low); } // Round Fraction down to BN (floor) export function fractionRoundDown(fraction: Fraction): BN { return fraction.high; } // Round Fraction up to BN (ceiling) export function fractionRoundUp(fraction: Fraction): BN { if (fraction.low.isZero()) { return fraction.high; } return fraction.high.add(new BN(1)); }