"use client"; import { useState, useCallback } from "react"; import { Card } from "@/components/ui/card"; import { StepControls } from "./step-controls"; type DecOp = "add" | "subtract" | "multiply" | "divide"; interface Step { label: string; columns: ColumnDisplay[]; carry?: string; resultRow?: string; } interface ColumnDisplay { rows: string[]; highlight?: number; // which row is highlighted separator?: boolean; // line above this row } function padAndAlign(a: string, b: string): { aStr: string; bStr: string; dotPos: number } { const aParts = a.split("."); const bParts = b.split("."); const aInt = aParts[0] || "0"; const bInt = bParts[0] || "0"; const aDec = aParts[1] || ""; const bDec = bParts[1] || ""; const maxInt = Math.max(aInt.length, bInt.length); const maxDec = Math.max(aDec.length, bDec.length); const aAligned = aInt.padStart(maxInt, " ") + (maxDec > 0 ? "." + aDec.padEnd(maxDec, "0") : ""); const bAligned = bInt.padStart(maxInt, " ") + (maxDec > 0 ? "." + bDec.padEnd(maxDec, "0") : ""); return { aStr: aAligned, bStr: bAligned, dotPos: maxInt }; } function buildAddSubSteps(a: number, b: number, op: DecOp): Step[] { const steps: Step[] = []; const aStr = a.toString(); const bStr = b.toString(); const { aStr: aAligned, bStr: bAligned } = padAndAlign(aStr, bStr); const opSymbol = op === "add" ? "+" : "−"; const result = op === "add" ? a + b : a - b; const resultStr = parseFloat(result.toFixed(10)).toString(); // Step 0: Show the problem steps.push({ label: `${aStr} ${opSymbol} ${bStr}`, columns: [], }); // Step 1: Align decimal points steps.push({ label: "Align the decimal points", columns: [ { rows: [aAligned, `${opSymbol} ${bAligned}`] }, ], }); // Step 2: Add trailing zeros steps.push({ label: "Fill in zeros as placeholders", columns: [ { rows: [aAligned, `${opSymbol} ${bAligned}`], separator: true }, ], }); // Step 3: Compute const { aStr: aFinal, bStr: bFinal } = padAndAlign(aStr, bStr); const resultAligned = padAndAlign(resultStr, aStr).aStr; steps.push({ label: `Compute column by column`, columns: [ { rows: [aFinal, `${opSymbol} ${bFinal}`, resultAligned], separator: true, highlight: 2 }, ], }); // Step 4: Result steps.push({ label: `${aStr} ${opSymbol} ${bStr} = ${resultStr}`, columns: [ { rows: [aFinal, `${opSymbol} ${bFinal}`, resultAligned], separator: true, highlight: 2 }, ], resultRow: resultStr, }); return steps; } function buildMultiplySteps(a: number, b: number): Step[] { const steps: Step[] = []; const aStr = a.toString(); const bStr = b.toString(); const aDec = (aStr.split(".")[1] || "").length; const bDec = (bStr.split(".")[1] || "").length; const totalDec = aDec + bDec; // Remove decimals for integer multiplication const aInt = Math.round(a * Math.pow(10, aDec)); const bInt = Math.round(b * Math.pow(10, bDec)); const result = a * b; const resultStr = parseFloat(result.toFixed(10)).toString(); steps.push({ label: `${aStr} × ${bStr}`, columns: [], }); if (totalDec > 0) { steps.push({ label: `Count decimal places: ${aDec} + ${bDec} = ${totalDec}`, columns: [ { rows: [`${aStr} → ${aDec} d.p.`, `${bStr} → ${bDec} d.p.`, `Total: ${totalDec} d.p.`] }, ], }); steps.push({ label: `Multiply as whole numbers: ${aInt} × ${bInt}`, columns: [ { rows: [`${aInt}`, `× ${bInt}`, `${aInt * bInt}`], separator: true, highlight: 2 }, ], }); steps.push({ label: `Place decimal point ${totalDec} place${totalDec !== 1 ? "s" : ""} from the right`, columns: [ { rows: [`${aInt * bInt}`, `→ ${resultStr}`], highlight: 1 }, ], resultRow: resultStr, }); } else { const intResult = a * b; steps.push({ label: `Multiply: ${aStr} × ${bStr} = ${intResult}`, columns: [ { rows: [`${aStr}`, `× ${bStr}`, `${intResult}`], separator: true, highlight: 2 }, ], resultRow: intResult.toString(), }); } return steps; } function buildDivideSteps(a: number, b: number): Step[] { const steps: Step[] = []; const aStr = a.toString(); const bStr = b.toString(); const result = a / b; const resultStr = parseFloat(result.toFixed(10)).toString(); steps.push({ label: `${aStr} ÷ ${bStr}`, columns: [], }); // Make divisor a whole number const bDec = (bStr.split(".")[1] || "").length; if (bDec > 0) { const factor = Math.pow(10, bDec); const newA = parseFloat((a * factor).toFixed(10)); const newB = parseFloat((b * factor).toFixed(10)); steps.push({ label: `Make divisor whole: multiply both by ${factor}`, columns: [ { rows: [`${aStr} × ${factor} = ${newA}`, `${bStr} × ${factor} = ${newB}`] }, ], }); steps.push({ label: `Now divide: ${newA} ÷ ${newB}`, columns: [ { rows: [`${newA} ÷ ${newB}`, `= ${resultStr}`], highlight: 1 }, ], }); } else { steps.push({ label: `Divide: ${aStr} ÷ ${bStr}`, columns: [ { rows: [`${aStr} ÷ ${bStr}`, `= ${resultStr}`], highlight: 1 }, ], }); } steps.push({ label: `${aStr} ÷ ${bStr} = ${resultStr}`, columns: [ { rows: [`${aStr} ÷ ${bStr} = ${resultStr}`] }, ], resultRow: resultStr, }); return steps; } function ColumnArithmetic({ columns, }: { columns: ColumnDisplay[]; }) { if (columns.length === 0) return null; return (
{columns.map((col, ci) => col.rows.map((row, ri) => (
{col.separator && ri === col.rows.length - 1 && (
)}
{row}
)), )}
); } export function DecimalArithmeticExplorer({ initialOperation = "add", }: { initialOperation?: DecOp; } = {}) { const [op, setOp] = useState(initialOperation); const [aInput, setAInput] = useState("12.45"); const [bInput, setBInput] = useState("3.7"); const [error, setError] = useState(""); const [steps, setSteps] = useState(null); const [currentStep, setCurrentStep] = useState(0); const [isPlaying, setIsPlaying] = useState(false); const done = steps ? currentStep >= steps.length - 1 : false; function handleGo() { setError(""); const a = parseFloat(aInput); const b = parseFloat(bInput); if (isNaN(a) || isNaN(b)) { setError("Enter valid decimal numbers."); return; } if (op === "divide" && b === 0) { setError("Cannot divide by zero."); return; } try { let s: Step[]; if (op === "add" || op === "subtract") { s = buildAddSubSteps(a, b, op); } else if (op === "multiply") { s = buildMultiplySteps(a, b); } else { s = buildDivideSteps(a, b); } setSteps(s); setCurrentStep(0); setIsPlaying(false); } catch { setError("Could not compute. Check your inputs."); } } const stepForward = useCallback(() => { if (!steps || currentStep >= steps.length - 1) return; setCurrentStep((s) => s + 1); if (currentStep + 1 >= steps.length - 1) setIsPlaying(false); }, [steps, currentStep]); const stepBack = useCallback(() => { if (currentStep <= 0) return; setCurrentStep((s) => s - 1); }, [currentStep]); const togglePlay = useCallback(() => setIsPlaying((p) => !p), []); const reset = useCallback(() => { setSteps(null); setCurrentStep(0); setIsPlaying(false); }, []); const step = steps ? steps[currentStep] : null; const opLabels: Record = { add: "Add (+)", subtract: "Subtract (−)", multiply: "Multiply (×)", divide: "Divide (÷)", }; return (
{/* Operation tabs */}
{(["add", "subtract", "multiply", "divide"] as DecOp[]).map((o) => ( ))}
{/* Input */}

Enter two decimal numbers

setAInput(e.target.value)} placeholder="e.g. 12.45" autoComplete="off" className="max-w-[140px] rounded-lg border-2 border-border bg-surface px-3.5 py-2.5 text-lg font-medium outline-none transition-colors focus:border-unit-3" aria-label="First number" /> {{ add: "+", subtract: "−", multiply: "×", divide: "÷" }[op]} setBInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleGo()} placeholder="e.g. 3.7" autoComplete="off" className="max-w-[140px] rounded-lg border-2 border-border bg-surface px-3.5 py-2.5 text-lg font-medium outline-none transition-colors focus:border-unit-3" aria-label="Second number" />
{error &&

{error}

}
{/* Display */} {!step ? (

Enter numbers above and click Go

) : ( <>

{step.label}

)}
{/* Controls */} 0} /> {/* Result */} {!done || !step?.resultRow ? (

Result will appear here when steps are complete

) : ( <>

Answer

{step.resultRow}

)}
); }