simplify-fractions/src/main/js/main.js

166 lines
4.7 KiB
JavaScript

// noinspection JSUnresolvedVariable
const {$, doAfterLoad} = window.fwdekker;
// noinspection JSUnresolvedVariable
const {clearFormValidity, showInputInvalid} = window.fwdekker.validation;
import katex from "katex";
import "katex/dist/katex.min.css";
// noinspection EqualityComparisonWithCoercionJS
/**
* Returns `true` if and only if `n` is an integer.
*
* @param n {*} the value to check for integerness
* @returns {boolean} `true` if and only if `n` is an integer
*/
const isInt = n => n == parseInt(n);
/**
* Returns the greatest common divisor of `a` and `b`.
*
* @param a {number} the first operand
* @param b {number} the second operand
* @returns {number} the greatest common divisor of `a` and `b`
*/
const gcd = (a, b) => {
if (b > a) {
const temp = a;
a = b;
b = temp;
}
while (true) {
if (b === 0) return a;
a %= b;
if (a === 0) return b;
b %= a;
}
};
/**
* A fraction that can be simplified.
*/
class Fraction {
constructor(numerator, denominator) {
if (!isInt(numerator) || !isInt(denominator))
throw new Error("Numerator and denominator must be integer-like.");
this.sign = numerator < 0 !== denominator < 0 ? -1 : 1;
this.numerator = Math.abs(+numerator);
this.denominator = Math.abs(+denominator);
}
/**
* Returns a new fraction such that the gcd of the numerator and denominator is 1.
*
* @returns {Fraction} a new fraction such that the gcd of the numerator and denominator is 1
*/
simplifyGcd() {
const common = gcd(this.numerator, this.denominator);
return new Fraction(this.sign * this.numerator / common, this.denominator / common);
}
/**
* Returns the LaTeX string representation of this fraction.
*
* Unlike `#toReducedString`, this method never simplifies the output to an integer.
*
* @returns {string} the LaTeX string representation of this fraction
*/
toString() {
let frac = `\\frac{${this.numerator}}{${this.denominator}}`;
if (this.sign === -1)
frac = `-${frac}`;
return frac;
}
/**
* Returns the LaTeX string representation of this fraction, or of the numerator if the denominator is 1.
*
* @returns {string} the string representation of the reduced form of this fraction
*/
toReducedString() {
if (this.numerator === 0)
return "0";
let frac;
if (this.denominator === 1)
frac = `${this.numerator}`;
else
frac = `\\frac{${this.numerator}}{${this.denominator}}`;
if (this.sign === -1)
frac = `-${frac}`;
return frac;
}
}
doAfterLoad(() => {
const numeratorInput = $("#numerator");
const denominatorInput = $("#denominator");
const outputField = $("#out");
/**
* Returns `true` if and only if both inputs are valid, showing the validity results to the user immediately.
*
* @param numerator {string} the numerator value
* @param denominator {string} the denominator value
* @returns {boolean} `true` if and only if both inputs are valid
*/
const validateInputs = (numerator, denominator) => {
clearFormValidity($("#inputs"));
let isValid = true;
if (numerator.trim() === "") {
isValid = false;
} else if (!isInt(numerator)) {
isValid = false;
showInputInvalid(numeratorInput, "Enter an integer.");
}
if (denominator.trim() === "") {
isValid = false;
} else if (!isInt(denominator)) {
isValid = false;
showInputInvalid(denominatorInput, "Enter an integer.");
} else if (+denominator === 0) {
isValid = false;
showInputInvalid(denominatorInput, "Enter a number other than zero.");
}
return isValid;
};
/**
* Reads the inputs and tries to output the simplified fraction.
*/
const outputSimplifiedFraction = () => {
let numerator = numeratorInput.value;
let denominator = denominatorInput.value;
if (!validateInputs(numerator, denominator)) return;
const fraction = new Fraction(numeratorInput.value, denominatorInput.value);
outputField.innerHTML = katex.renderToString(
fraction.toString() + " = " + fraction.simplifyGcd().toReducedString(),
{
displayMode: true,
throwOnError: false
}
);
};
numeratorInput.addEventListener("input", () => outputSimplifiedFraction());
denominatorInput.addEventListener("input", () => outputSimplifiedFraction());
outputSimplifiedFraction();
});