Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions fraction.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { roundTo } from "./utils.ts";
import { gcdEuclid } from "./gcd.ts";

export class Fraction {
constructor(
private numerator: number,
private denominator: number,
) {}
) {
this.cancel();
}

public add(other: Fraction) {
const newNumerator =
this.numerator * other.denominator + other.numerator * this.denominator;
const newDenominator = this.denominator * other.denominator;
this.numerator = newNumerator;
this.denominator = newDenominator;
this.cancel();
}

public subtract(other: Fraction) {
Expand All @@ -20,20 +24,38 @@ export class Fraction {
const newDenominator = this.denominator * other.denominator;
this.numerator = newNumerator;
this.denominator = newDenominator;
this.cancel();
}

public multiply(other: Fraction) {
const newNumerator = this.numerator * other.numerator;
const newDenominator = this.denominator * other.denominator;
this.numerator = newNumerator;
this.denominator = newDenominator;
this.cancel();
}

public divide(other: Fraction) {
const newNumerator = this.numerator * other.denominator;
const newDenominator = this.denominator * other.numerator;
this.numerator = newNumerator;
this.denominator = newDenominator;
this.cancel();
}

public cancel(): Fraction {
if (this.denominator < 0) {
this.numerator *= -1;
this.denominator *= -1;
}

const gcd = gcdEuclid(this.numerator, this.denominator);
if (gcd !== 0) {
this.numerator = this.numerator / gcd;
this.denominator = this.denominator / gcd;
}

return this;
}

public toFloat(precision: number): number {
Expand All @@ -54,6 +76,6 @@ export class Fraction {
if (Number.isNaN(numerator) || Number.isNaN(denominator)) {
throw new Error(`non-numeric numerator/denominator`);
}
return new Fraction(numerator, denominator);
return new Fraction(numerator, denominator).cancel();
}
}
63 changes: 63 additions & 0 deletions fraction_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,66 @@ Deno.test("1/3 + 2/6 = 2/3 is roughly 0.67", () => {
// Assert
assertAlmostEquals(left.toFloat(0.01), 0.67);
});

Deno.test("cancel() reduces 2/4 to 1/2", () => {
// Arrange
const fraction = new Fraction(2, 4);

// Act
fraction.cancel();

// Assert
assertEquals(fraction.toString(), "1/2");
});

Deno.test("constructor auto-cancels 6/9 to 2/3", () => {
// Arrange
const fraction = new Fraction(6, 9);

// Assert
assertEquals(fraction.toString(), "2/3");
});

Deno.test("parse auto-cancels 10/25 to 2/5", () => {
// Act
const fraction = Fraction.parse("10/25");

// Assert
assertEquals(fraction.toString(), "2/5");
});

Deno.test("auto-cancel after subtract", () => {
// Arrange
const left = new Fraction(3, 4);
const right = new Fraction(1, 4);

// Act
left.subtract(right);

// Assert
assertEquals(left.toString(), "1/2");
});

Deno.test("auto-cancel after multiply", () => {
// Arrange
const left = new Fraction(2, 3);
const right = new Fraction(3, 4);

// Act
left.multiply(right);

// Assert
assertEquals(left.toString(), "1/2");
});

Deno.test("auto-cancel after divide", () => {
// Arrange
const left = new Fraction(1, 2);
const right = new Fraction(3, 4);

// Act
left.divide(right);

// Assert
assertEquals(left.toString(), "2/3");
});
43 changes: 43 additions & 0 deletions gcd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
export function gcdBruteForce(a: number, b: number): number {
let left = Math.abs(a);
let right = Math.abs(b);

if (left === 0 && right === 0) {
return 0;
}
if (left === 0) {
return right;
}
if (right === 0) {
return left;
}

const min = Math.min(left, right);
for (let i = min; i >= 1; i--) {
if (left % i === 0 && right % i === 0) {
return i;
}
}

return 1;
}

export function gcdEuclid(a: number, b: number): number {
const left = Math.abs(a);
const right = Math.abs(b);

if (left === 0) {
return right;
}
if (right === 0) {
return left;
}
if (left === right) {
return left;
}

if (left > right) {
return gcdEuclid(left - right, right);
}
return gcdEuclid(left, right - left);
}
25 changes: 25 additions & 0 deletions gcd_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { assertEquals } from "@std/assert";
import { gcdBruteForce, gcdEuclid } from "./gcd.ts";

const gcdTests = [
{ a: 1, b: 1, gcd: 1 },
{ a: 1, b: 2, gcd: 1 },
{ a: 2, b: 2, gcd: 2 },
{ a: 3, b: 4, gcd: 1 },
{ a: 6, b: 9, gcd: 3 },
{ a: 81, b: 36, gcd: 9 },
];

Deno.test("gcdBruteForce computes expected gcd values", () => {
for (const { a, b, gcd } of gcdTests) {
const actual = gcdBruteForce(a, b);
assertEquals(actual, gcd);
}
});

Deno.test("gcdEuclid computes expected gcd values", () => {
for (const { a, b, gcd } of gcdTests) {
const actual = gcdEuclid(a, b);
assertEquals(actual, gcd);
}
});