Skip to content

Commit

Permalink
Implement a relative metric for fraction simplification
Browse files Browse the repository at this point in the history
  • Loading branch information
frostburn committed Mar 24, 2024
1 parent 49c9752 commit 1b04e6b
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 4 deletions.
16 changes: 14 additions & 2 deletions src/__tests__/fraction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,24 @@ describe('Fraction', () => {
}
});

it('can simplify a random number', () => {
const value = Math.random() * 2;
it('can simplify a random number using an absolute metric', () => {
const value = Math.random() * 4 - 2;
const fraction = new Fraction(value);
expect(fraction.simplify().valueOf()).toBeCloseTo(value);
});

it('can simplify a random number using a relative metric', () => {
const value =
Math.exp(Math.random() * 20 - 10) *
(Math.floor(2 * Math.random()) * 2 - 1);
const fraction = new Fraction(value);
const simplified = fraction.simplifyRelative().valueOf();
expect(Math.sign(simplified)).toBe(Math.sign(value));
expect(
Math.abs(Math.log(Math.abs(simplified)) - Math.log(Math.abs(value)))
).toBeLessThanOrEqual((Math.LN2 / 1200) * 3.5);
});

it('can parse a repeated decimal', () => {
const fraction = new Fraction("3.'3'");
expect(fraction.s).toBe(1);
Expand Down
28 changes: 26 additions & 2 deletions src/fraction.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// I'm rolling my own because fraction.js has trouble with TypeScript https://github.com/rawify/Fraction.js/issues/72
// -Lumi

import {valueToCents} from './conversion';
import {PRIMES} from './primes';

export type UnsignedFraction = {n: number; d: number};
Expand Down Expand Up @@ -395,7 +396,8 @@ export class Fraction {
}

/**
* Return a convergent of this fraction that is within the given tolerance.
* Return a convergent of this fraction that is within the given absolute tolerance.
* @param epsilon Absolute tolerance for error.
*/
simplify(epsilon = 0.001) {
const abs = this.abs();
Expand All @@ -408,7 +410,29 @@ export class Fraction {
s = s.inverse().add(cont[k]);
}

if (Math.abs(s.valueOf() - absValue) < epsilon) {
if (Math.abs(s.valueOf() - absValue) <= epsilon) {
return new Fraction({s: this.s, n: s.n, d: s.d} as Fraction);
}
}
return this.clone();
}

/**
* Return a convergent of this fraction that is within the given relative tolerance measured in cents.
* @param tolerance Relative tolerance measured in cents.
*/
simplifyRelative(tolerance = 3.5) {
const abs = this.abs();
const cont = abs.toContinued();
const absCents = valueToCents(abs.valueOf());

for (let i = 1; i < cont.length; i++) {
let s = new Fraction({s: 1, n: cont[i - 1], d: 1} as Fraction);
for (let k = i - 2; k >= 0; k--) {
s = s.inverse().add(cont[k]);
}

if (Math.abs(valueToCents(s.valueOf()) - absCents) <= tolerance) {
return new Fraction({s: this.s, n: s.n, d: s.d} as Fraction);
}
}
Expand Down

0 comments on commit 1b04e6b

Please sign in to comment.