Initial Commit
This commit is contained in:
65
lib/math/decimals.ts
Normal file
65
lib/math/decimals.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
export function roundToWholeNumber(value: number): number {
|
||||
return Math.round(value);
|
||||
}
|
||||
|
||||
export function roundToDecimalPlaces(value: number, places: number): number {
|
||||
const factor = Math.pow(10, places);
|
||||
return Math.round(value * factor) / factor;
|
||||
}
|
||||
|
||||
export function roundToSignificantFigures(value: number, sigFigs: number): number {
|
||||
if (value === 0) return 0;
|
||||
const magnitude = Math.floor(Math.log10(Math.abs(value)));
|
||||
const factor = Math.pow(10, sigFigs - 1 - magnitude);
|
||||
return Math.round(value * factor) / factor;
|
||||
}
|
||||
|
||||
export function toStandardForm(value: number): { coefficient: number; exponent: number } {
|
||||
if (value === 0) return { coefficient: 0, exponent: 0 };
|
||||
const exponent = Math.floor(Math.log10(Math.abs(value)));
|
||||
const coefficient = value / Math.pow(10, exponent);
|
||||
return {
|
||||
coefficient: roundToDecimalPlaces(coefficient, 10),
|
||||
exponent,
|
||||
};
|
||||
}
|
||||
|
||||
export function fromStandardForm(coefficient: number, exponent: number): number {
|
||||
return coefficient * Math.pow(10, exponent);
|
||||
}
|
||||
|
||||
export function compareDecimals(a: number, b: number): -1 | 0 | 1 {
|
||||
if (a > b) return 1;
|
||||
if (a < b) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function getPlaceValue(value: number): { digit: number; place: string }[] {
|
||||
const places = [
|
||||
"thousands", "hundreds", "tens", "units", "tenths", "hundredths", "thousandths",
|
||||
];
|
||||
const str = Math.abs(value).toFixed(3);
|
||||
const [intPart, decPart] = str.split(".");
|
||||
const paddedInt = intPart.padStart(4, "0");
|
||||
const result: { digit: number; place: string }[] = [];
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
result.push({ digit: parseInt(paddedInt[i]), place: places[i] });
|
||||
}
|
||||
for (let i = 0; i < 3; i++) {
|
||||
result.push({ digit: parseInt(decPart[i]), place: places[4 + i] });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function multiplyByPowerOf10(value: number, power: number): number {
|
||||
return roundToDecimalPlaces(value * Math.pow(10, power), 10);
|
||||
}
|
||||
|
||||
export function divideByPowerOf10(value: number, power: number): number {
|
||||
return roundToDecimalPlaces(value / Math.pow(10, power), 10);
|
||||
}
|
||||
|
||||
export function standardFormToKatex(coefficient: number, exponent: number): string {
|
||||
return `${coefficient} \\times 10^{${exponent}}`;
|
||||
}
|
||||
107
lib/math/fractions.ts
Normal file
107
lib/math/fractions.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
export function gcd(a: number, b: number): number {
|
||||
a = Math.abs(a);
|
||||
b = Math.abs(b);
|
||||
while (b) {
|
||||
[a, b] = [b, a % b];
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
export function lcm(a: number, b: number): number {
|
||||
return Math.abs(a * b) / gcd(a, b);
|
||||
}
|
||||
|
||||
export function simplify(num: number, den: number): [number, number] {
|
||||
if (den === 0) return [num, den];
|
||||
const g = gcd(Math.abs(num), Math.abs(den));
|
||||
const sign = den < 0 ? -1 : 1;
|
||||
return [(num / g) * sign, (den / g) * sign];
|
||||
}
|
||||
|
||||
export function add(
|
||||
n1: number, d1: number,
|
||||
n2: number, d2: number,
|
||||
): [number, number] {
|
||||
const num = n1 * d2 + n2 * d1;
|
||||
const den = d1 * d2;
|
||||
return simplify(num, den);
|
||||
}
|
||||
|
||||
export function subtract(
|
||||
n1: number, d1: number,
|
||||
n2: number, d2: number,
|
||||
): [number, number] {
|
||||
const num = n1 * d2 - n2 * d1;
|
||||
const den = d1 * d2;
|
||||
return simplify(num, den);
|
||||
}
|
||||
|
||||
export function multiply(
|
||||
n1: number, d1: number,
|
||||
n2: number, d2: number,
|
||||
): [number, number] {
|
||||
return simplify(n1 * n2, d1 * d2);
|
||||
}
|
||||
|
||||
export function divide(
|
||||
n1: number, d1: number,
|
||||
n2: number, d2: number,
|
||||
): [number, number] {
|
||||
return simplify(n1 * d2, d1 * n2);
|
||||
}
|
||||
|
||||
export function toDecimal(num: number, den: number): number {
|
||||
return num / den;
|
||||
}
|
||||
|
||||
export function fromDecimal(decimal: number, precision: number = 6): [number, number] {
|
||||
const str = decimal.toFixed(precision);
|
||||
const parts = str.split(".");
|
||||
if (!parts[1]) return [parseInt(parts[0]), 1];
|
||||
const den = Math.pow(10, parts[1].length);
|
||||
const num = Math.round(decimal * den);
|
||||
return simplify(num, den);
|
||||
}
|
||||
|
||||
export function compare(
|
||||
n1: number, d1: number,
|
||||
n2: number, d2: number,
|
||||
): -1 | 0 | 1 {
|
||||
const diff = n1 * d2 - n2 * d1;
|
||||
if (diff > 0) return 1;
|
||||
if (diff < 0) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function fractionOfQuantity(num: number, den: number, quantity: number): number {
|
||||
return (num / den) * quantity;
|
||||
}
|
||||
|
||||
export function wholeFromFraction(num: number, den: number, part: number): number {
|
||||
return (part * den) / num;
|
||||
}
|
||||
|
||||
export function isProper(num: number, den: number): boolean {
|
||||
return Math.abs(num) < Math.abs(den);
|
||||
}
|
||||
|
||||
export function toMixed(num: number, den: number): [number, number, number] {
|
||||
const whole = Math.floor(Math.abs(num) / Math.abs(den));
|
||||
const remainder = Math.abs(num) % Math.abs(den);
|
||||
const sign = (num < 0) !== (den < 0) ? -1 : 1;
|
||||
return [whole * sign, remainder, Math.abs(den)];
|
||||
}
|
||||
|
||||
export function fromMixed(whole: number, num: number, den: number): [number, number] {
|
||||
const sign = whole < 0 ? -1 : 1;
|
||||
return [sign * (Math.abs(whole) * den + num), den];
|
||||
}
|
||||
|
||||
export function isSimplified(num: number, den: number): boolean {
|
||||
return gcd(Math.abs(num), Math.abs(den)) === 1;
|
||||
}
|
||||
|
||||
export function toKatex(num: number, den: number): string {
|
||||
if (den === 1) return `${num}`;
|
||||
return `\\frac{${num}}{${den}}`;
|
||||
}
|
||||
35
lib/math/ratios.ts
Normal file
35
lib/math/ratios.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { gcd } from "./fractions";
|
||||
|
||||
export function simplifyRatio(parts: number[]): number[] {
|
||||
if (parts.length === 0) return [];
|
||||
let g = parts[0];
|
||||
for (let i = 1; i < parts.length; i++) {
|
||||
g = gcd(g, parts[i]);
|
||||
}
|
||||
return parts.map((p) => p / g);
|
||||
}
|
||||
|
||||
export function divideInRatio(total: number, parts: number[]): number[] {
|
||||
const sum = parts.reduce((a, b) => a + b, 0);
|
||||
const onePart = total / sum;
|
||||
return parts.map((p) => p * onePart);
|
||||
}
|
||||
|
||||
export function ratioToFraction(a: number, b: number): [number, number] {
|
||||
return [a, a + b];
|
||||
}
|
||||
|
||||
export function areEquivalent(ratio1: number[], ratio2: number[]): boolean {
|
||||
if (ratio1.length !== ratio2.length) return false;
|
||||
const s1 = simplifyRatio(ratio1);
|
||||
const s2 = simplifyRatio(ratio2);
|
||||
return s1.every((v, i) => v === s2[i]);
|
||||
}
|
||||
|
||||
export function totalParts(parts: number[]): number {
|
||||
return parts.reduce((a, b) => a + b, 0);
|
||||
}
|
||||
|
||||
export function ratioToKatex(parts: number[]): string {
|
||||
return parts.join(":");
|
||||
}
|
||||
94
lib/math/validation.ts
Normal file
94
lib/math/validation.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { gcd } from "./fractions";
|
||||
import { simplifyRatio } from "./ratios";
|
||||
|
||||
export type AnswerResult =
|
||||
| { correct: true; simplified: boolean }
|
||||
| { correct: false; message: string };
|
||||
|
||||
export function checkFractionAnswer(
|
||||
userNum: number,
|
||||
userDen: number,
|
||||
expectedNum: number,
|
||||
expectedDen: number,
|
||||
requireSimplified: boolean = true,
|
||||
): AnswerResult {
|
||||
if (userDen === 0) {
|
||||
return { correct: false, message: "Denominator cannot be zero" };
|
||||
}
|
||||
|
||||
// Check mathematical equivalence
|
||||
const isEquivalent = userNum * expectedDen === expectedNum * userDen;
|
||||
|
||||
if (!isEquivalent) {
|
||||
return { correct: false, message: "That's not quite right. Try again!" };
|
||||
}
|
||||
|
||||
const isSimplified = gcd(Math.abs(userNum), Math.abs(userDen)) === 1;
|
||||
|
||||
if (requireSimplified && !isSimplified) {
|
||||
return { correct: true, simplified: false };
|
||||
}
|
||||
|
||||
return { correct: true, simplified: true };
|
||||
}
|
||||
|
||||
export function checkDecimalAnswer(
|
||||
userValue: number,
|
||||
expectedValue: number,
|
||||
tolerance: number = 0.001,
|
||||
): AnswerResult {
|
||||
if (Math.abs(userValue - expectedValue) <= tolerance) {
|
||||
return { correct: true, simplified: true };
|
||||
}
|
||||
return { correct: false, message: "That's not quite right. Try again!" };
|
||||
}
|
||||
|
||||
export function checkRatioAnswer(
|
||||
userParts: number[],
|
||||
expectedParts: number[],
|
||||
requireSimplified: boolean = true,
|
||||
): AnswerResult {
|
||||
if (userParts.length !== expectedParts.length) {
|
||||
return { correct: false, message: "Check the number of parts in your ratio" };
|
||||
}
|
||||
|
||||
const userSimplified = simplifyRatio(userParts);
|
||||
const expectedSimplified = simplifyRatio(expectedParts);
|
||||
|
||||
const isEquivalent = userSimplified.every((v, i) => v === expectedSimplified[i]);
|
||||
|
||||
if (!isEquivalent) {
|
||||
return { correct: false, message: "That's not quite right. Try again!" };
|
||||
}
|
||||
|
||||
if (requireSimplified) {
|
||||
const isAlreadySimplified = userParts.every((v, i) => v === userSimplified[i]);
|
||||
return { correct: true, simplified: isAlreadySimplified };
|
||||
}
|
||||
|
||||
return { correct: true, simplified: true };
|
||||
}
|
||||
|
||||
export function checkIntegerAnswer(
|
||||
userValue: number,
|
||||
expectedValue: number,
|
||||
): AnswerResult {
|
||||
if (userValue === expectedValue) {
|
||||
return { correct: true, simplified: true };
|
||||
}
|
||||
return { correct: false, message: "That's not quite right. Try again!" };
|
||||
}
|
||||
|
||||
export function checkOrderingAnswer(
|
||||
userOrder: number[],
|
||||
expectedOrder: number[],
|
||||
): AnswerResult {
|
||||
if (userOrder.length !== expectedOrder.length) {
|
||||
return { correct: false, message: "Make sure you've ordered all the numbers" };
|
||||
}
|
||||
const isCorrect = userOrder.every((v, i) => v === expectedOrder[i]);
|
||||
if (isCorrect) {
|
||||
return { correct: true, simplified: true };
|
||||
}
|
||||
return { correct: false, message: "Check your ordering. Try again!" };
|
||||
}
|
||||
Reference in New Issue
Block a user