Skill: decimal-operations
Decimal Operations Practice
An endless supply of never-repeating decimal problems with instant feedback. Line up the points for adding and subtracting, count decimal places when multiplying, slide the point when dividing — one problem at a time.
1 Practice
Type your answer and press Check answer (or Enter). A wrong answer earns a hint first; a second miss walks you through the full solution.
How to enter answers
- Decimal: type a decimal like
6.05. Dollar signs and commas are fine. Any equivalent form counts — 6.050 is the same as 6.05. - Trailing zeros don’t matter — 6.050 counts the same as 6.05.
2 What you’ll practice
Every problem is generated fresh from 10 templates — a problem never repeats until its whole pool is used up.
Add & subtract
Line up the decimal points and pad with zeros — tenths with tenths.
Multiply
Multiply as whole numbers, then count total decimal places.
Divide & money words
Slide the point to make the divisor whole; totals and change with dollars.
3 Watch out for these
The three mistakes students make most often with decimal operations.
Wrong: 4.25 + 1.8 = 4.43. Right: 4.25 + 1.80 = 6.05. The point is the anchor — pad with zeros.
Wrong: 2.4 × 0.35 = 8.4. Right: 0.84 (1 + 2 = 3 places). Estimate first: 2 × 0.4 ≈ 0.8.
Wrong: 7.5 ÷ 0.25 = 0.3. Right: slide both points → 750 ÷ 25 = 30.
/* ===== inlined practice core — tested by test_practice.js; do not hand-edit ===== */
(function (root, factory) { 'use strict'; var api = factory(); if (typeof module === 'undefined') { root.DecOpsPractice = api; } else { module.exports = api; } })(typeof self !== 'undefined' ? self : this, function () { 'use strict'; /* == decimal arithmetic (exact, integer-based; kept &&-free) == */ function dnorm(v, p) { while (!(!(p > 0) || !(v % 10 === 0))) { v = v / 10; p--; } return { v: v, p: p }; } function dparse(str) { str = String(str).trim().replace(/,/g, '').replace(/\$/g, ''); if (!/^-?(\d+\.?\d*|\.\d+)$/.test(str)) return { ok: false }; var neg = str.charAt(0) === '-'; if (neg) str = str.slice(1); var parts = str.split('.'); var ip = parts[0] === '' ? '0' : parts[0]; var fp = parts.length > 1 ? parts[1] : ''; var v = parseInt(ip + fp, 10); if (neg) v = -v; var r = dnorm(v, fp.length); return { ok: true, v: r.v, p: r.p }; } function dfmt(v, p) { var neg = v < 0; if (neg) v = -v; var s = String(v); while (s.length <= p) s = '0' + s; var out = p === 0 ? s : s.slice(0, s.length - p) + '.' + s.slice(s.length - p); if (out.charAt(0) === '.') out = '0' + out; return (neg ? '-' : '') + out; } function dcmp(a, b) { var P = Math.max(a.p, b.p); var x = a.v * Math.pow(10, P - a.p), y = b.v * Math.pow(10, P - b.p); return x < y ? -1 : (x > y ? 1 : 0); } function dsame(a, b) { var x = dnorm(a.v, a.p), y = dnorm(b.v, b.p); return !(!(x.v === y.v) || !(x.p === y.p)); } function dadd(a, b) { var P = Math.max(a.p, b.p); return dnorm(a.v * Math.pow(10, P - a.p) + b.v * Math.pow(10, P - b.p), P); } function dsub(a, b) { var P = Math.max(a.p, b.p); return dnorm(a.v * Math.pow(10, P - a.p) - b.v * Math.pow(10, P - b.p), P); } function dmul(a, b) { return dnorm(a.v * b.v, a.p + b.p); } /* exact terminating division: (a.v/10^a.p) / (b.v/10^b.p) = (a.v*10^b.p) / (b.v*10^a.p), then long-divide */ function ddiv(a, b) { var num = a.v * Math.pow(10, b.p), den = b.v * Math.pow(10, a.p); var q = Math.floor(num / den), rem = num % den, p = 0; while (!(!(rem !== 0) || !(p < 8))) { rem *= 10; q = q * 10 + Math.floor(rem / den); rem = rem % den; p++; } var r = dnorm(q, p); r.exact = rem === 0; return r; } function dstr(d) { return dfmt(d.v, d.p); } function padTo(d, P) { var s = dstr(d); if (P === 0) return s; if (s.indexOf('.') < 0) s += '.'; var have = s.length - s.indexOf('.') - 1; for (var i = have; i < P; i++) s += '0'; return s; } /* == /decimal arithmetic == */ function mulberry32(seed) { var t = seed >>> 0; return function () { t += 0x6D2B79F5; var z = Math.imul(t ^ (t >>> 15), t | 1); z ^= z + Math.imul(z ^ (z >>> 7), z | 61); return ((z ^ (z >>> 14)) >>> 0) / 4294967296; }; } function ri(rng, lo, hi) { return lo + Math.floor(rng() * (hi - lo + 1)); } function pick(rng, arr) { return arr[Math.floor(rng() * arr.length)]; } function D(v, p) { return dnorm(v, p); }
function hintAddSub(a, b, op) { var P = Math.max(a.p, b.p); var sym = op === 'add' ? '+' : '−'; return [ 'Line up the decimal points — the point is the anchor, not the right edge.', 'Pad with zeros so both numbers show ' + P + ' decimal place(s): ' + padTo(a, P) + ' ' + sym + ' ' + padTo(b, P) + '.', 'Now ' + (op === 'add' ? 'add' : 'subtract') + ' column by column, borrowing if needed. The point drops straight down.' ]; } function hintMul(a, b) { return [ 'Ignore the decimal points and multiply as whole numbers.', 'Count the total decimal places: ' + a.p + ' + ' + b.p + ' = ' + (a.p + b.p) + '.', 'Place the point so the answer has ' + (a.p + b.p) + ' decimal digit(s). Estimate first to check the placement.' ]; } function hintDiv(a, b) { return [ 'Make the divisor whole first: slide its point right ' + b.p + ' place(s).', 'Slide the dividend the same distance.', 'Now divide as whole numbers' + '.' ]; }
var TEMPLATES = [ { id: 'ADD_ALIGN', label: 'Add decimals', op: 'add', enumerate: function () { var rng = mulberry32(101); var out = []; for (var i = 0; i < 40; i++) { var a = D(ri(rng, 101, 9999), 2), b = D(ri(rng, 11, 999), pick(rng, [1, 2])); out.push({ a: a, b: b }); } return out; }, make: function (p) { var ans = dadd(p.a, p.b); return { kind: 'add', params: p, answer: { kind: 'dec', v: ans.v, p: ans.p }, stemHTML: 'Compute ' + dstr(p.a) + ' + ' + dstr(p.b) + '.', hints: hintAddSub(p.a, p.b, 'add'), whyHTML: '
Answer: ' + dstr(ans) + '.
' + padTo(p.a, Math.max(p.a.p, p.b.p)) + ' + ' + padTo(p.b, Math.max(p.a.p, p.b.p)) + ' = ' + dstr(ans) + ' — points lined up, then added column by column.
' }; } }, { id: 'SUB_ALIGN', label: 'Subtract decimals', op: 'sub', enumerate: function () { var rng = mulberry32(202); var out = []; for (var i = 0; i < 40; i++) { var a = D(ri(rng, 200, 999), 1), b = D(ri(rng, 11, 899), 2); if (dcmp(a, b) <= 0) { var t = a; a = D(b.v + 100, b.p); b = t; } out.push({ a: a, b: b }); } return out; }, make: function (p) { var ans = dsub(p.a, p.b); return { kind: 'sub', params: p, answer: { kind: 'dec', v: ans.v, p: ans.p }, stemHTML: 'Compute ' + dstr(p.a) + ' − ' + dstr(p.b) + '.', hints: hintAddSub(p.a, p.b, 'sub'), whyHTML: '
Answer: ' + dstr(ans) + '.
' + padTo(p.a, Math.max(p.a.p, p.b.p)) + ' − ' + padTo(p.b, Math.max(p.a.p, p.b.p)) + ' = ' + dstr(ans) + ' — borrow where needed, point drops straight down.
' }; } }, { id: 'MUL_COUNT', label: 'Multiply decimals', op: 'mul', enumerate: function () { var rng = mulberry32(303); var out = []; for (var i = 0; i < 40; i++) { out.push({ a: D(ri(rng, 11, 99), 1), b: D(ri(rng, 11, 99), 2) }); } return out; }, make: function (p) { var ans = dmul(p.a, p.b); var whole = (p.a.v * Math.pow(10, 0)) ; return { kind: 'mul', params: p, answer: { kind: 'dec', v: ans.v, p: ans.p }, stemHTML: 'Compute ' + dstr(p.a) + ' × ' + dstr(p.b) + '.', hints: hintMul(p.a, p.b), whyHTML: '
Answer: ' + dstr(ans) + '.
' + (p.a.v) + ' × ' + (p.b.v) + ' = ' + (p.a.v * p.b.v) + '; ' + p.a.p + ' + ' + p.b.p + ' = ' + (p.a.p + p.b.p) + ' decimal place(s) → ' + dstr(ans) + '.
' }; } }, { id: 'MUL_WHOLE', label: 'Decimal times whole number', op: 'mul', enumerate: function () { var rng = mulberry32(404); var out = []; for (var i = 0; i < 40; i++) { out.push({ a: D(ri(rng, 11, 999), 2), b: D(ri(rng, 2, 99), 0) }); } return out; }, make: function (p) { var ans = dmul(p.a, p.b); return { kind: 'mul', params: p, answer: { kind: 'dec', v: ans.v, p: ans.p }, stemHTML: 'Compute ' + dstr(p.a) + ' × ' + dstr(p.b) + '.', hints: hintMul(p.a, p.b), whyHTML: '
Answer: ' + dstr(ans) + '.
' + p.a.v + ' × ' + p.b.v + ' = ' + (p.a.v * p.b.v) + '; ' + (p.a.p + p.b.p) + ' decimal place(s) → ' + dstr(ans) + '.
' }; } }, { id: 'DIV_SLIDE', label: 'Divide decimals', op: 'div', enumerate: function () { var rng = mulberry32(505); var out = []; for (var i = 0; i < 40; i++) { var q = D(ri(rng, 2, 40), pick(rng, [0, 0, 1])); var dv = D(ri(rng, 2, 99), pick(rng, [1, 1, 2])); var dd = dmul(q, dv); out.push({ a: dd, b: dv }); } return out; }, make: function (p) { var ans = ddiv(p.a, p.b); return { kind: 'div', params: p, answer: { kind: 'dec', v: ans.v, p: ans.p }, stemHTML: 'Compute ' + dstr(p.a) + ' ÷ ' + dstr(p.b) + '.', hints: hintDiv(p.a, p.b), whyHTML: '
Answer: ' + dstr(ans) + '.
Slide ' + p.b.p + ' place(s): ' + dstr(p.a) + ' ÷ ' + dstr(p.b) + ' becomes a whole-number division giving ' + dstr(ans) + '.
' }; } }, { id: 'ADD_THREE', label: 'Add three decimals', op: 'add', enumerate: function () { var rng = mulberry32(606); var out = []; for (var i = 0; i < 40; i++) { out.push({ a: D(ri(rng, 11, 999), pick(rng, [1, 2])), b: D(ri(rng, 11, 999), pick(rng, [1, 2])), c: D(ri(rng, 11, 999), pick(rng, [1, 2])) }); } return out; }, make: function (p) { var ans = dadd(dadd(p.a, p.b), p.c); var P = Math.max(p.a.p, p.b.p, p.c.p); return { kind: 'add', params: p, answer: { kind: 'dec', v: ans.v, p: ans.p }, stemHTML: 'Compute ' + dstr(p.a) + ' + ' + dstr(p.b) + ' + ' + dstr(p.c) + '.', hints: ['Line up all three decimal points — every point in the same column.', 'Pad with zeros so all three show ' + P + ' decimal place(s).', 'Add column by column. The point drops straight down.'], whyHTML: '
Answer: ' + dstr(ans) + '.
All points lined up: ' + padTo(p.a, P) + ' + ' + padTo(p.b, P) + ' + ' + padTo(p.c, P) + ' = ' + dstr(ans) + '.
' }; } }, { id: 'SUB_MONEY', label: 'Money subtraction', op: 'sub', enumerate: function () { var rng = mulberry32(707); var out = []; var items = ['notebook', 'pen', 'ruler', 'eraser', 'pencil case', 'marker set']; for (var i = 0; i < 40; i++) { var price = D(ri(rng, 105, 2499), 2), paid = D(ri(rng, 1, 20), 0); if (dcmp(paid, price) <= 0) paid = D(price.v + 500, 2); out.push({ price: price, paid: paid, item: pick(rng, items) }); } return out; }, make: function (p) { var ans = dsub(p.paid, p.price); return { kind: 'sub', params: p, answer: { kind: 'dec', v: ans.v, p: ans.p }, stemHTML: 'A ' + p.item + ' costs $' + dstr(p.price) + '. You pay with $' + dstr(p.paid) + '. What is your change?', hints: ['Change = paid − price. Line up the points.', 'Pad: ' + padTo(p.paid, 2) + ' − ' + padTo(p.price, 2) + '.', 'Subtract column by column.'], whyHTML: '
Answer: $' + dstr(ans) + '.
$' + dstr(p.paid) + ' − $' + dstr(p.price) + ' = $' + dstr(ans) + '.
' }; } }, { id: 'WORD_MUL_MONEY', label: 'Money multiplication', op: 'mul', enumerate: function () { var rng = mulberry32(808); var out = []; var items = ['notebooks', 'pens', 'juice boxes', 'stickers', 'bus tickets']; for (var i = 0; i < 40; i++) { out.push({ price: D(ri(rng, 105, 899), 2), qty: ri(rng, 2, 9), item: pick(rng, items) }); } return out; }, make: function (p) { var ans = dmul(p.price, D(p.qty, 0)); return { kind: 'mul', params: p, answer: { kind: 'dec', v: ans.v, p: ans.p }, stemHTML: p.qty + ' ' + p.item + ' at $' + dstr(p.price) + ' each. What is the total?', hints: ['Total = ' + p.qty + ' × $' + dstr(p.price) + '.', 'Multiply as whole numbers, then count decimal places (' + p.price.p + ' + 0 = ' + p.price.p + ').', 'Estimate: ' + p.qty + ' × $' + Math.round(p.price.v / Math.pow(10, p.price.p)) + ' to check the point.'], whyHTML: '
Answer: $' + dstr(ans) + '.
' + p.qty + ' × $' + dstr(p.price) + ' = $' + dstr(ans) + '.
' }; } }, { id: 'DIV_WHOLE_Q', label: 'Division with whole answer', op: 'div', enumerate: function () { var rng = mulberry32(909); var out = []; for (var i = 0; i < 40; i++) { var q = D(ri(rng, 2, 25), 0); var dv = D(ri(rng, 2, 95), 1); out.push({ a: dmul(q, dv), b: dv }); } return out; }, make: function (p) { var ans = ddiv(p.a, p.b); return { kind: 'div', params: p, answer: { kind: 'dec', v: ans.v, p: ans.p }, stemHTML: 'Compute ' + dstr(p.a) + ' ÷ ' + dstr(p.b) + '.', hints: hintDiv(p.a, p.b), whyHTML: '
Answer: ' + dstr(ans) + '.
Slide ' + p.b.p + ' place(s) to make the divisor whole, then divide: ' + dstr(ans) + '.
' }; } }, { id: 'MIXED_REVIEW', label: 'Mixed review', op: 'mix', enumerate: function () { var rng = mulberry32(1010); var out = []; for (var i = 0; i < 40; i++) { var op = pick(rng, ['add', 'sub', 'mul']); var a = D(ri(rng, 11, 999), pick(rng, [1, 2])); var b = D(ri(rng, 11, 999), pick(rng, [1, 2])); if (!(!(op === 'sub') || !(dcmp(a, b) <= 0))) { var t = a; a = b; b = t; } out.push({ a: a, b: b, op: op }); } return out; }, make: function (p) { var ans = p.op === 'add' ? dadd(p.a, p.b) : (p.op === 'sub' ? dsub(p.a, p.b) : dmul(p.a, p.b)); var sym = p.op === 'add' ? '+' : (p.op === 'sub' ? '−' : '×'); var hints = p.op === 'mul' ? hintMul(p.a, p.b) : hintAddSub(p.a, p.b, p.op); return { kind: p.op, params: p, answer: { kind: 'dec', v: ans.v, p: ans.p }, stemHTML: 'Compute ' + dstr(p.a) + ' ' + sym + ' ' + dstr(p.b) + '.', hints: hints, whyHTML: '
Answer: ' + dstr(ans) + '.
' }; } }, ];
function parseDecInput(s) { return dparse(s); }
function checkAnswer(expected, got) { if (!got.ok) return false; if (expected.kind !== 'dec') return false; return dsame({ v: expected.v, p: expected.p }, { v: got.v, p: got.p }); }
function makeQuestion(rng) { var t = TEMPLATES[Math.floor(rng() * TEMPLATES.length)]; var pool = t.enumerate(); var p = pool[Math.floor(rng() * pool.length)]; var q = t.make(p); q.templateId = t.id; q.templateLabel = t.label; return q; }
function PracticeSession(seed) { this.rng = mulberry32(seed || 1); this.score = 0; this.streak = 0; this.best = 0; this.asked = {}; this.current = null; this.revealed = false; } PracticeSession.prototype.next = function () { var q, guard = 0; do { q = makeQuestion(this.rng); guard++; } while (!(!this.asked[q.templateId + '|' + q.stemHTML] || guard > 50)); this.asked[q.templateId + '|' + q.stemHTML] = 1; this.current = q; this.revealed = false; this.hintLevel = 0; return q; }; PracticeSession.prototype.hint = function () { if (!this.current) return ''; var h = this.current.hints[Math.min(this.hintLevel, this.current.hints.length - 1)]; this.hintLevel++; return h; }; PracticeSession.prototype.submit = function (raw) { var q = this.current; if (!q) return { status: 'none' }; var parsed = parseDecInput(raw); if (!parsed.ok) return { status: 'invalid', message: 'Type a number like 6.05.' }; if (checkAnswer(q.answer, parsed)) { this.score++; this.streak++; if (this.streak > this.best) this.best = this.streak; return { status: 'correct', score: this.score, streak: this.streak, firstTry: this.hintLevel === 0 }; } this.streak = 0; if (this.hintLevel === 0) { return { status: 'hint', hint: this.hint() }; } return { status: 'why', whyHTML: q.whyHTML, answerText: dstr({ v: q.answer.v, p: q.answer.p }) }; }; PracticeSession.prototype.reveal = function () { if (!this.current) return null; this.revealed = true; return this.current.whyHTML; };
return { PracticeSession: PracticeSession, TEMPLATES: TEMPLATES, parseDecInput: parseDecInput, checkAnswer: checkAnswer, dnorm: dnorm, dparse: dparse, dfmt: dfmt, dcmp: dcmp, dsame: dsame, dadd: dadd, dsub: dsub, dmul: dmul, ddiv: ddiv, dstr: dstr, padTo: padTo, makeQuestion: makeQuestion, mulberry32: mulberry32 }; });
/* ===== page glue: wires the tested core to the DOM ===== */
(function () { 'use strict'; var SP = window.DecOpsPractice; var session = new SP.PracticeSession((Date.now() % 2000000000) + 7); function el(id) { return document.getElementById(id); } var input = el('ansInput'), checkBtn = el('checkBtn'), anotherBtn = el('anotherBtn'), showSolBtn = el('showSolBtn'), promptTag = el('promptTag'), promptEl = el('promptEl'), fb = el('fb'), hintBox = el('hintBox'), whyBox = el('whyBox'), statsLine = el('statsLine'); var problemNum = 0;
function updateStats() { statsLine.innerHTML = 'Score ' + session.score + ' · Streak ' + session.streak + ' · Best streak ' + session.best + ' · Problem ' + problemNum + ''; } function clearPanels() { fb.className = 'fb'; fb.innerHTML = ''; hintBox.className = 'hintbox'; hintBox.innerHTML = ''; whyBox.className = 'whybox'; whyBox.innerHTML = ''; } function showFb(kind, html) { fb.className = 'fb show ' + kind; fb.innerHTML = html; } function newProblem() { var pr = session.next(); problemNum++; promptTag.textContent = pr.templateLabel; promptEl.innerHTML = pr.stemHTML; input.value = ''; input.placeholder = 'e.g. 6.05'; clearPanels(); showSolBtn.style.display = ''; updateStats(); input.focus(); } function showWhy(html) { whyBox.className = 'whybox show'; whyBox.innerHTML = '
' + html; showSolBtn.style.display = 'none'; } checkBtn.addEventListener('click', function () { var r = session.submit(input.value); updateStats(); if (r.status === 'invalid') { showFb('warn', 'Could not read that. Type a decimal like 6.05 — dollar signs and commas are fine.'); return; } if (r.status === 'none') return; if (r.status === 'correct') { if (r.firstTry) { showFb('ok', 'Correct! +1 point. Streak: ' + r.streak + '.'); } else { showFb('ok', 'Correct! Solved after a hint, so no point this time — but the streak grows.'); } showSolBtn.style.display = 'none'; return; } if (r.status === 'hint') { showFb('no', 'Not quite. Try again — here is a hint.'); hintBox.className = 'hintbox show'; hintBox.textContent = r.hint; input.focus(); input.select(); return; } if (r.status === 'why') { showFb('no', 'Not quite. Let us walk through it step by step.'); showWhy(r.whyHTML); return; } }); anotherBtn.addEventListener('click', newProblem); showSolBtn.addEventListener('click', function () { showWhy(session.reveal()); updateStats(); }); input.addEventListener('keydown', function (e) { if (e.key === 'Enter') checkBtn.click(); }); newProblem(); })();