Skip to content
Closed
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
67 changes: 67 additions & 0 deletions Maths/Fraction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* @function fraction
* @description This function returns the fraction of a float type number.
* @param {number} num - a float type number is expected, but an integer will also work.
* @param {number} accuracy - the accuracy of the fraction, the default is 6. like if the accuracy is 2 then for 1.333 result will be 10/9, where for 6 it will return 1333/1000.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please formulate this mathematically - something like "fraction - num <= 10^-accuracy".

* @return {Array} - an array containing the numerator and denominator of the fraction.
* @see https://en.wikipedia.org/wiki/Repeating_decimal and
* @see https://en.wikipedia.org/wiki/Decimal#Decimal_fractions
* @example fraction(0.5) // [1, 2]
* @example fraction(0.3333333333333333) // [1, 3]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are all these examples really useful? 0.5 and 0.25 for example seem redundant.

* @example fraction(0.25) // [1, 4]
* @example fraction(5.56) // [139, 25]
* @example fraction(0.33) // [33, 100]
* @example fraction(0.33,2) // [10, 3]
*/
function fraction(number, accuracy = 6) {
if (typeof number === "number" && !Number.isNaN(number) && Number.isFinite(number)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're still not using guards here; these checks are all redundant with the if-throws in the else. You just need to move the if-throws to the top of the function, making them guards.

&& typeof accuracy === "number" && !Number.isNaN(accuracy) && accuracy >= 1 && accuracy <= 16) {
let neg = 1;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd call this sign. Also, I'd just do const sign = Math.sign(number) and const abs = Math.abs(number) here.

// if number is a negative then following code will run
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment.

if (number < 0) {
neg = -1;
number = Math.abs(number);
}
// if number is a 0 then it will return [0, 1]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment.

if (number === 0) return [0, 1];
if (Number.isInteger(number)) return [neg * number, 1];
// if number is a not an integer then following code will run
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment.

number = number.toString();
let len;
let reg = number.match(/(\d+?)\1+$/);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a hacky way to deal with "repeating" fractional parts, and I'd argue that it solves the wrong problem. Since we're dealing with finite-size numbers (floats) here, there are no "repeating" decimals; we can't distinguish a "repeating" decimal from a "deliberately ending decimal". We need to treat both the same (it seems "accuracy" is more of a "threshold" of when you consider a decimal repeating?).

In fact, all floats can even be represented exactly as fractions (though perhaps maybe not as fractions of floats, that might get hairy towards some edge cases) where the denominator is a power of two and the numerator is an integer.

I wouldn't exclude the possibility that some special provisions for repeating decimals may be useful, but then what problem this exactly solves (and how) and what guarantees it makes should be explicitly stated.

// if number is a repeating decimal then following code will run
if (reg && reg[0].length > accuracy) {
let pos = number.split(".");
number = number.replace(reg[0], reg[1]);
let rec = pos[0] + pos[1].replace(reg[0], "");
number = Number(rec + reg[1]) - Number(rec);
len = Number(
"9".repeat(reg[1].length) + "0".repeat(rec.length - pos[0].length)
);
} else {
// if number is not a repeating decimal then following code will run
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again a redundant comment.

number = number.replace(".", "");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String manipulation for manipulating numbers is dirty here. Just multiply your numerator by the denominator, then round. Shouldn't the denominator be determined by the accuracy?

len = 10 ** (number.length - 1);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not apparent to me why this is correct: Isn't number.length the length of the whole number (without the period, in digits), not just the "fractional" part after the period here?

number = Number(number);
}
// it will find out the gcd of the number and the len to reduce the fraction nomitor and denominator like 4/8 will be 1/2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is called "shortening" the fraction. number and len are odd names for "numerator" and "denominator".

let div = gcd(number, len);
number /= div;
len /= div;
return [neg * number, len];
} else {
if (typeof number !== "number") throw new TypeError("Invalid number, a number type value expected");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These "guards" should all be moved to the top of the function. They could also be simplified a bit.

if (typeof accuracy !== "number") throw new TypeError("Invalid accuracy, a number type value expected");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant with the negated isFinite check.

if (Number.isNaN(number)) throw new TypeError("Invalid number, a number type value expected");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The NaN check is redundant with the negated isFinite check.

if (Number.isNaN(accuracy)) throw new TypeError("Invalid accuracy, a number type value expected");
if (!Number.isFinite(number)) throw new RangeError("Invalid number, a finite number expected");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep only this check for number. Similar for accuracy: Keep only the range + type check, which you can do in a single if statement.

if (accuracy < 1 || accuracy > 16) throw new RangeError("Invalid accuracy, a integer type value expected between 1 and 16");
}
}

function gcd(a, b) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a gcd implementation in this repo. Import and reuse it.

if (b == 0) return a;
return gcd(b, a % b);
}

export { fraction };
31 changes: 31 additions & 0 deletions Maths/test/Fraction.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { fraction } from './../Fraction.js';

describe('Fraction', () => {
it('should return [1, 2] for 0.5', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests should use it.each.

expect(fraction(0.5)).toEqual([1, 2]);
});
it('should return [1, 3] for 0.3333333333333333', () => {
expect(fraction(0.3333333333333333)).toEqual([1, 3]);
});
it('should return [1, 4] for 0.25', () => {
expect(fraction(0.25)).toEqual([1, 4]);
});
it('should return [0, 1] for 0', () => {
expect(fraction(0)).toEqual([0, 1]);
});
it('should return [139, 25] for 5.56', () => {
expect(fraction(5.56)).toEqual([139, 25]);
});
it('should return [33, 100] for 0.33', () => {
expect(fraction(0.33)).toEqual([33, 100]);
});
it('should return [5, 1] for 1', () => {
expect(fraction(5)).toEqual([5, 1]);
});
it('should return [3333, 1000] for 3.333', () => {
expect(fraction(3.33)).toEqual([333, 100]);
});
it('should return [1, 3] for 0.3333', () => {
expect(fraction(0.3333, 3)).toEqual([1, 3]);
});
});