equation-solver-js
Version:
a javascript library for solving mathematical equations
273 lines (222 loc) • 11.7 kB
JavaScript
import { InvalidCoefficientsError, NoSolutionError, InfiniteSolutionsError } from './errors.js';
import { MAX_SAFE_COEFFICIENT, FLOATING_POINT_PRECISION } from './constants.js';
/**
* Validates equation coefficients object
* @param {Object} equation - Equation coefficients object
* @throws {InvalidCoefficientsError} When coefficients are invalid
*/
const validateEquationCoefficients = (equation) => {
const isInvalidObject = !equation || typeof equation !== 'object';
const isXNotNumber = typeof equation.xCoefficient !== 'number';
const isYNotNumber = typeof equation.yCoefficient !== 'number';
const isConstantNotNumber = typeof equation.constant !== 'number';
if (isInvalidObject) {
throw new InvalidCoefficientsError(
'Invalid equation coefficients object',
equation
);
}
if (isXNotNumber || isYNotNumber || isConstantNotNumber) {
throw new InvalidCoefficientsError(
'Coefficients must be numbers',
equation
);
}
// NaN and Infinity validation
const isXInvalid = !Number.isFinite(equation.xCoefficient);
const isYInvalid = !Number.isFinite(equation.yCoefficient);
const isConstantInvalid = !Number.isFinite(equation.constant);
if (isXInvalid || isYInvalid || isConstantInvalid) {
throw new InvalidCoefficientsError(
'Coefficients cannot be NaN or Infinity',
equation
);
}
// Large number validation
const isTooLarge = Math.abs(equation.xCoefficient) > MAX_SAFE_COEFFICIENT ||
Math.abs(equation.yCoefficient) > MAX_SAFE_COEFFICIENT ||
Math.abs(equation.constant) > MAX_SAFE_COEFFICIENT;
if (isTooLarge) {
throw new InvalidCoefficientsError(
'Coefficients too large for safe calculation',
equation
);
}
}
/**
* Chooses which variable to isolate from which equation for optimal solving
*/
const chooseVariableToIsolate = (firstEquationCoefficients, secondEquationCoefficients) => {
const firstEquationXCoefficient = firstEquationCoefficients.xCoefficient;
const firstEquationYCoefficient = firstEquationCoefficients.yCoefficient;
const secondEquationXCoefficient = secondEquationCoefficients.xCoefficient;
const secondEquationYCoefficient = secondEquationCoefficients.yCoefficient;
const canIsolateXFromFirst = Math.abs(firstEquationXCoefficient) === 1;
const canIsolateYFromFirst = Math.abs(firstEquationYCoefficient) === 1;
const canIsolateXFromSecond = Math.abs(secondEquationXCoefficient) === 1;
const canIsolateYFromSecond = Math.abs(secondEquationYCoefficient) === 1;
const absFirstX = Math.abs(firstEquationXCoefficient);
const absFirstY = Math.abs(firstEquationYCoefficient);
const absSecondX = Math.abs(secondEquationXCoefficient);
const absSecondY = Math.abs(secondEquationYCoefficient);
if (canIsolateXFromFirst) {
return {variable: 'x', equation: 'first'};
}
if (canIsolateYFromFirst) {
return {variable: 'y', equation: 'first'};
}
if (canIsolateXFromSecond) {
return {variable: 'x', equation: 'second'};
}
if (canIsolateYFromSecond) {
return {variable: 'y', equation: 'second'};
}
// Find smallest non-zero coefficient for isolation
const candidates = [];
if (absFirstX > FLOATING_POINT_PRECISION) {
candidates.push({value: absFirstX, variable: 'x', equation: 'first'});
}
if (absFirstY > FLOATING_POINT_PRECISION) {
candidates.push({value: absFirstY, variable: 'y', equation: 'first'});
}
if (absSecondX > FLOATING_POINT_PRECISION) {
candidates.push({value: absSecondX, variable: 'x', equation: 'second'});
}
if (absSecondY > FLOATING_POINT_PRECISION) {
candidates.push({value: absSecondY, variable: 'y', equation: 'second'});
}
if (candidates.length === 0) {
throw new InvalidCoefficientsError('All coefficients are zero - no solution possible');
}
// Choose the smallest non-zero coefficient
const smallest = candidates.reduce((min, current) =>
current.value < min.value ? current : min
);
return {variable: smallest.variable, equation: smallest.equation};
}
/**
* Isolates a variable from an equation
*/
const isolateVariable = (equationCoefficients, variableToIsolate, detailedSolutionSteps) => {
const xCoefficient = equationCoefficients.xCoefficient;
const yCoefficient = equationCoefficients.yCoefficient;
const constant = equationCoefficients.constant;
detailedSolutionSteps.push(`Original equation: ${xCoefficient}x + ${yCoefficient}y = ${constant}`);
if (variableToIsolate === 'x') {
if (Math.abs(xCoefficient) < FLOATING_POINT_PRECISION) {
throw new InvalidCoefficientsError(
`Cannot isolate x: coefficient is zero. Equation: ${xCoefficient}x + ${yCoefficient}y = ${constant}`
);
}
detailedSolutionSteps.push(`Isolating x:`);
detailedSolutionSteps.push(`${xCoefficient}x = ${constant} - ${yCoefficient}y`);
detailedSolutionSteps.push(`x = (${constant} - ${yCoefficient}y) / ${xCoefficient}`);
return {
isolatedVariable: 'x',
constantTerm: constant / xCoefficient,
otherVariableCoefficient: -yCoefficient / xCoefficient,
expression: `(${constant} - ${yCoefficient}y) / ${xCoefficient}`
};
} else {
if (Math.abs(yCoefficient) < FLOATING_POINT_PRECISION) {
throw new InvalidCoefficientsError(
`Cannot isolate y: coefficient is zero. Equation: ${xCoefficient}x + ${yCoefficient}y = ${constant}`
);
}
detailedSolutionSteps.push(`Isolating y:`);
detailedSolutionSteps.push(`${yCoefficient}y = ${constant} - ${xCoefficient}x`);
detailedSolutionSteps.push(`y = (${constant} - ${xCoefficient}x) / ${yCoefficient}`);
return {
isolatedVariable: 'y',
constantTerm: constant / yCoefficient,
otherVariableCoefficient: -xCoefficient / yCoefficient,
expression: `(${constant} - ${xCoefficient}x) / ${yCoefficient}`
};
}
}
/**
* Substitutes isolated variable into target equation and solves
*/
const substituteAndSolve = (isolatedForm, targetEquationCoefficients, detailedSolutionSteps) => {
const targetXCoefficient = targetEquationCoefficients.xCoefficient;
const targetYCoefficient = targetEquationCoefficients.yCoefficient;
const targetConstant = targetEquationCoefficients.constant;
detailedSolutionSteps.push(`Substitute into other equation:`);
detailedSolutionSteps.push(`Original: ${targetXCoefficient}x + ${targetYCoefficient}y = ${targetConstant}`);
if (isolatedForm.isolatedVariable === 'x') {
detailedSolutionSteps.push(`Replace x with ${isolatedForm.expression}:`);
detailedSolutionSteps.push(`${targetXCoefficient}(${isolatedForm.expression}) + ${targetYCoefficient}y = ${targetConstant}`);
const newYCoefficient = (targetXCoefficient * isolatedForm.otherVariableCoefficient) + targetYCoefficient;
const newConstant = targetConstant - (targetXCoefficient * isolatedForm.constantTerm);
detailedSolutionSteps.push(`Simplify: ${newYCoefficient}y = ${newConstant}`);
if (Math.abs(newYCoefficient) < FLOATING_POINT_PRECISION) {
if (Math.abs(newConstant) < FLOATING_POINT_PRECISION) {
throw new InfiniteSolutionsError(
'System has infinite solutions - equations are equivalent'
);
} else {
throw new NoSolutionError(
'System has no solution - equations represent parallel lines'
);
}
}
const yValue = newConstant / newYCoefficient;
detailedSolutionSteps.push(`Solve for y: y = ${newConstant} / ${newYCoefficient} = ${yValue}`);
const xValue = isolatedForm.constantTerm + (isolatedForm.otherVariableCoefficient * yValue);
detailedSolutionSteps.push(`Calculate x: x = ${isolatedForm.constantTerm} + (${isolatedForm.otherVariableCoefficient} * ${yValue}) = ${xValue}`);
return {xValue, yValue};
} else {
detailedSolutionSteps.push(`Replace y with ${isolatedForm.expression}:`);
detailedSolutionSteps.push(`${targetXCoefficient}x + ${targetYCoefficient}(${isolatedForm.expression}) = ${targetConstant}`);
const newXCoefficient = targetXCoefficient + (targetYCoefficient * isolatedForm.otherVariableCoefficient);
const newConstant = targetConstant - (targetYCoefficient * isolatedForm.constantTerm);
if (Math.abs(newXCoefficient) < FLOATING_POINT_PRECISION) {
if (Math.abs(newConstant) < FLOATING_POINT_PRECISION) {
throw new InfiniteSolutionsError(
'System has infinite solutions - equations are equivalent'
);
} else {
throw new NoSolutionError(
'System has no solution - equations represent parallel lines'
);
}
}
detailedSolutionSteps.push(`Simplify: ${newXCoefficient}x = ${newConstant}`);
const xValue = newConstant / newXCoefficient;
detailedSolutionSteps.push(`Solve for x: x = ${newConstant} / ${newXCoefficient} = ${xValue}`);
const yValue = isolatedForm.constantTerm + (isolatedForm.otherVariableCoefficient * xValue);
detailedSolutionSteps.push(`Calculate y: y = ${isolatedForm.constantTerm} + (${isolatedForm.otherVariableCoefficient} * ${xValue}) = ${yValue}`);
return {xValue, yValue};
}
}
/**
* Solves a system of two linear equations with two variables
*/
const solveLinearEquationSystem = (firstEquationCoefficients, secondEquationCoefficients) => {
validateEquationCoefficients(firstEquationCoefficients);
validateEquationCoefficients(secondEquationCoefficients);
const detailedSolutionSteps = [];
detailedSolutionSteps.push(`System of equations:`);
detailedSolutionSteps.push(`Equation 1: ${firstEquationCoefficients.xCoefficient}x + ${firstEquationCoefficients.yCoefficient}y = ${firstEquationCoefficients.constant}`);
detailedSolutionSteps.push(`Equation 2: ${secondEquationCoefficients.xCoefficient}x + ${secondEquationCoefficients.yCoefficient}y = ${secondEquationCoefficients.constant}`);
detailedSolutionSteps.push('');
const isolationChoice = chooseVariableToIsolate(firstEquationCoefficients, secondEquationCoefficients);
detailedSolutionSteps.push(`Strategy: Isolate ${isolationChoice.variable} from ${isolationChoice.equation} equation`);
detailedSolutionSteps.push('');
const sourceEquation = isolationChoice.equation === 'first'
? firstEquationCoefficients
: secondEquationCoefficients;
const targetEquation = isolationChoice.equation === 'first'
? secondEquationCoefficients
: firstEquationCoefficients;
const isolatedForm = isolateVariable(sourceEquation, isolationChoice.variable, detailedSolutionSteps);
detailedSolutionSteps.push('');
const solutionResult = substituteAndSolve(isolatedForm, targetEquation, detailedSolutionSteps);
detailedSolutionSteps.push('');
detailedSolutionSteps.push(`Final Solution: x = ${solutionResult.xValue}, y = ${solutionResult.yValue}`);
return {
solution: {x: solutionResult.xValue, y: solutionResult.yValue},
steps: detailedSolutionSteps
};
}
export {solveLinearEquationSystem};