Skip to content

Commit

Permalink
Fix safe limit blowing with moderately complex fractions
Browse files Browse the repository at this point in the history
  • Loading branch information
frostburn committed Feb 27, 2024
1 parent 1bc3ae9 commit 8f8fcfe
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 17 deletions.
53 changes: 53 additions & 0 deletions src/__tests__/fraction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,4 +447,57 @@ describe('Fraction', () => {
);
expect(one.equals(1)).toBe(true);
});

it('adds terms with large denominators', () => {
const a = new Fraction('123456789/94906267');
const b = new Fraction('987654321/94906267');
expect(a.add(b).equals('1111111110/94906267')).toBe(true);
});

it('subtracts terms with large denominators', () => {
const a = new Fraction('987654321/94906267');
const b = new Fraction('123456789/94906267');
expect(a.sub(b).equals('864197532/94906267'));
});

it('lens-adds terms with large numerators', () => {
const a = new Fraction('94906267/123456789');
const b = new Fraction('94906267/987654321');
expect(a.lensAdd(b).equals('94906267/1111111110')).toBe(true);
});

it('lens-subtracts terms with large numerators', () => {
const a = new Fraction('94906267/123456789');
const b = new Fraction('94906267/987654321');
expect(a.lensSub(b).equals('-94906267/864197532')).toBe(true);
});

it('mods terms with large denominators', () => {
const a = new Fraction('123456789/94906267');
const b = new Fraction('987654321/94906267');
expect(b.mod(a).equals('9/94906267')).toBe(true);
});

it('mmods terms with large denominators', () => {
const a = new Fraction('123456789/94906267');
const b = new Fraction('987654321/94906267');
expect(b.mmod(a).equals('9/94906267')).toBe(true);
});

it('checks divisibility of complex fractions', () => {
const a = new Fraction('123456789/94906267');
expect(a.mul(21).divisible(a)).toBe(true);
});

it('computes gcd of factors with large denominators', () => {
const a = new Fraction('123456789/94906267');
const b = new Fraction('987654321/94906267');
expect(a.gcd(b).equals('9/94906267')).toBe(true);
});

it('computes lcm of factors with with large numerators', () => {
const a = new Fraction('94906267/123456789');
const b = new Fraction('94906267/987654321');
expect(a.lcm(b).equals('94906267/9')).toBe(true);
});
});
64 changes: 47 additions & 17 deletions src/fraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,12 @@ export class Fraction {
**/
add(other: FractionValue) {
const {s, n, d} = new Fraction(other);
return new Fraction(this.s * this.n * d + s * n * this.d, this.d * d);
// Must pre-reduce to avoid blowing the limits
const denominator = lcm(this.d, d);
return new Fraction(
this.s * this.n * (denominator / this.d) + s * n * (denominator / d),
denominator
);
}

/**
Expand All @@ -490,7 +495,12 @@ export class Fraction {
**/
sub(other: FractionValue) {
const {s, n, d} = new Fraction(other);
return new Fraction(this.s * this.n * d - s * n * this.d, this.d * d);
// Must pre-reduce to avoid blowing the limits
const denominator = lcm(this.d, d);
return new Fraction(
this.s * this.n * (denominator / this.d) - s * n * (denominator / d),
denominator
);
}

/**
Expand All @@ -500,11 +510,16 @@ export class Fraction {
*/
lensAdd(other: FractionValue) {
const {s, n, d} = new Fraction(other);
if (!n) {
if (!n || !this.n) {
// Based on behavior in the limit where both terms become zero.
return new Fraction({s: 0, n: 0, d: 1});
}
return new Fraction(this.s * this.n * s * n, this.n * d + n * this.d);
// Must pre-reduce to avoid blowing the limits
const numerator = lcm(this.n, n);
return new Fraction(
this.s * s * numerator,
(numerator / n) * d + (numerator / this.n) * this.d
);
}

/**
Expand All @@ -514,11 +529,16 @@ export class Fraction {
*/
lensSub(other: FractionValue) {
const {s, n, d} = new Fraction(other);
if (!n) {
if (!n || !this.n) {
// Based on behavior in the limit where both terms become zero.
return new Fraction({s: 0, n: 0, d: 1});
}
return new Fraction(this.s * this.n * s * n, n * this.d - this.n * d);
// Must pre-reduce to avoid blowing the limits
const numerator = lcm(this.n, n);
return new Fraction(
this.s * s * numerator,
(numerator / this.n) * this.d - (numerator / n) * d
);
}

/**
Expand Down Expand Up @@ -562,10 +582,12 @@ export class Fraction {
* Ex: new Fraction("4.'3'").mod("7/8") => (13/3) % (7/8) = (5/6)
**/
mod(other: FractionValue) {
other = new Fraction(other);
const {n, d} = new Fraction(other);
// Must pre-reduce to avoid blowing the limits
const denominator = lcm(this.d, d);
return new Fraction(
(this.s * (other.d * this.n)) % (other.n * this.d),
this.d * other.d
(this.s * ((denominator / this.d) * this.n)) % (n * (denominator / d)),
denominator
);
}

Expand All @@ -575,10 +597,12 @@ export class Fraction {
* Ex: new Fraction("-4.'3'").mmod("7/8") => (-13/3) % (7/8) = (1/24)
**/
mmod(other: FractionValue) {
other = new Fraction(other);
const {n, d} = new Fraction(other);
// Must pre-reduce to avoid blowing the limits
const denominator = lcm(this.d, d);
return new Fraction(
mmod(this.s * (other.d, this.n), other.n * this.d),
this.d * other.d
mmod(this.s * ((denominator / this.d) * this.n), n * (denominator / d)),
denominator
);
}

Expand Down Expand Up @@ -705,8 +729,14 @@ export class Fraction {
*/
divisible(other: FractionValue) {
try {
other = new Fraction(other);
return !(!(other.n * this.d) || (this.n * other.d) % (other.n * this.d));
const {n, d} = new Fraction(other);
const nFactor = gcd(this.n, n);
const dFactor = gcd(this.d, d);
return !(
!n ||
((this.n / nFactor) * (d / dFactor)) %
((n / nFactor) * (this.d / dFactor))
);
} catch {
return false;
}
Expand All @@ -719,7 +749,7 @@ export class Fraction {
*/
gcd(other: FractionValue) {
const {n, d} = new Fraction(other);
return new Fraction(gcd(n, this.n) * gcd(d, this.d), d * this.d);
return new Fraction(gcd(n, this.n), lcm(this.d, d));
}

/**
Expand All @@ -729,10 +759,10 @@ export class Fraction {
*/
lcm(other: FractionValue) {
const {n, d} = new Fraction(other);
if (n === 0 && this.n === 0) {
if (!n && !this.n) {
return new Fraction({s: 0, n: 0, d: 1});
}
return new Fraction(n * this.n, gcd(n, this.n) * gcd(d, this.d));
return new Fraction(lcm(n, this.n), gcd(d, this.d));
}

/**
Expand Down

0 comments on commit 8f8fcfe

Please sign in to comment.