Skill: integer-operations
Integer Operations Practice
An endless supply of never-repeating integer problems with instant feedback. Add, subtract (including double negatives), multiply, and divide signed numbers — 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
- Type an integer like
-13,5, or0. A leading+is fine too. - Word problems ask for just the number — leave off the °F or the dollar sign.
2 What you’ll practice
Every problem is generated fresh from 12 templates — a problem never repeats until its whole pool is used up.
Adding integers
Same-sign sums, mixed signs where the bigger size wins, and walks that cross zero.
Subtracting integers
Walk left for subtraction, and rewrite double negatives: a − (−b) = a + b.
Multiply, divide, and more
The sign grid in action, exact division, temperature and money word problems, missing addends, and multi-step challenges.
3 Watch out for these
The three mistakes students make most often with integers.
Wrong: −5 − 3 = 8
Right: −5 − 3 = −8 — at −5, walk 3 more left.
Wrong: −4 − (−4) = −8
Right: −4 − (−4) = −4 + 4 = 0. Rewrite it first.
Wrong: (−3)(−3) = −9
Right: (−3)(−3) = 9 — same signs → positive.
/* ============================================================ Integer Operations Practice — core logic (pure JavaScript, no DOM) UMD: required by node tests as a module, inlined into practice.html as window.IntegerPractice. ============================================================ */ (function (root, factory) { 'use strict'; var api = factory(); if (typeof module === 'undefined') { root.IntegerPractice = api; } else { module.exports = api; } })(typeof self !== 'undefined' ? self : this, function () { 'use strict';
var MINUS = '\u2212'; function fmtI(n) { return String(n).replace(/-/g, MINUS); } function paren(n) { return n < 0 ? '(' + fmtI(n) + ')' : String(n); } /* ---------------- seeded PRNG ---------------- */ function mulberry32(seed) { var a = (seed >>> 0) || 1; return function () { a |= 0; a = (a + 0x6D2B79F5) | 0; var t = Math.imul(a ^ (a >>> 15), 1 | a); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } function shuffle(arr, rand) { for (var i = arr.length - 1; i > 0; i--) { var j = Math.floor(rand() * (i + 1)); var t = arr[i]; arr[i] = arr[j]; arr[j] = t; } return arr; } function ri(rand, lo, hi) { return lo + Math.floor(rand() * (hi - lo + 1)); }
/* ---------------- input parsing ---------------- */ function parseIntInput(raw) { var s = String(raw).replace(/\u2212/g, '-').replace(/[\s,]+/g, ''); if (!/^[+-]?\d+$/.test(s)) return null; var v = parseInt(s, 10); if (!isFinite(v)) return null; return v; }
/* ---------------- problem assembly ---------------- */ function finishProblem(prompt, v, hint, steps, tag) { var ansDisp = '' + fmtI(v) + ''; var why = '
- ' +
steps.map(function (s) { return '
- ' + s + '
'; }).join('') + '
' + '
' + '
'; return { promptHTML: prompt, tag: tag, answer: { kind: 'int', v: v }, hintHTML: hint, whyHTML: why, answerDisplayHTML: ansDisp, accept: [String(v)] }; }
var HINT_ADD = 'Hint: Think of the number line. Adding a positive walks ' + 'right; adding a negative walks left. The bigger size wins the sign.'; var HINT_SUB = 'Hint: a − b means "start at a, walk b left." ' + 'And a − (−b) is the same as a + b — minus a negative is plus a positive.'; var HINT_MD = 'Hint: Same signs → positive, different signs → negative. ' + 'Multiply or divide the sizes first, then set the sign.';
/* ---------------- the 12 templates ---------------- */ var TEMPLATES = [ { id: 'ADD_NEG', tag: 'Adding integers', enumerate: function (rand) { var out = []; for (var a = 2; a <= 12; a++) for (var b = 2; b <= 12; b++) out.push({ key: a + '+' + b, a: a, b: b }); return shuffle(out, rand); }, make: function (p) { var v = -(p.a + p.b); return finishProblem( 'Compute ' + fmtI(-p.a) + ' + ' + paren(-p.b) + '.', v, HINT_ADD, ['Start at ' + fmtI(-p.a) + ' on the number line.', 'Adding ' + paren(-p.b) + ' means walking ' + p.b + ' steps left.', 'You land on ' + fmtI(v) + '. Both numbers pulled the same way, so the sizes add: ' + p.a + ' + ' + p.b + ' = ' + (p.a + p.b) + ', and the answer stays negative.'], 'Adding integers'); } }, { id: 'ADD_MIX', tag: 'Adding integers', enumerate: function (rand) { var out = []; for (var a = 2; a <= 9; a++) for (var d = 1; d <= 8; d++) { out.push({ key: 'A' + a + '+' + d, a: a, d: d, variant: 'A' }); out.push({ key: 'B' + a + '+' + d, a: a, d: d, variant: 'B' }); } return shuffle(out, rand); }, make: function (p) { var b = p.a + p.d, v, prompt, steps; if (p.variant === 'A') { v = p.d; prompt = 'Compute ' + fmtI(-p.a) + ' + ' + b + '.'; steps = ['Walk it: start at ' + fmtI(-p.a) + ', walk ' + b + ' steps right.', 'The first ' + p.a + ' steps reach 0; the remaining ' + p.d + ' land on +' + p.d + '.', 'Positive wins because ' + b + ' > ' + p.a + ': ' + b + ' − ' + p.a + ' = ' + p.d + '.']; } else { v = -p.d; prompt = 'Compute ' + p.a + ' + ' + paren(-b) + '.'; steps = ['Walk it: start at ' + p.a + ', walk ' + b + ' steps left.', 'The first ' + p.a + ' steps reach 0; the remaining ' + p.d + ' land on ' + fmtI(-p.d) + '.', 'Negative wins because ' + b + ' > ' + p.a + ': ' + b + ' − ' + p.a + ' = ' + p.d + ', with a negative sign.']; } return finishProblem(prompt, v, HINT_ADD, steps, 'Adding integers'); } }, { id: 'SUB_PP', tag: 'Subtracting integers', enumerate: function (rand) { var out = []; for (var a = 2; a <= 12; a++) for (var d = 1; d <= 9; d++) out.push({ key: a + '-' + d, a: a, d: d }); return shuffle(out, rand); }, make: function (p) { var b = p.a + p.d, v = -p.d; return finishProblem( 'Compute ' + p.a + ' − ' + b + '.', v, HINT_SUB, ['a − b means: start at ' + p.a + ', walk ' + b + ' steps left.', 'Walking ' + p.a + ' left reaches 0; ' + p.d + ' more steps land on ' + fmtI(v) + '.', 'Check: ' + fmtI(v) + ' + ' + b + ' = ' + p.a + '.'], 'Subtracting integers'); } }, { id: 'SUB_NEG', tag: 'Subtracting integers', enumerate: function (rand) { var out = []; for (var a = 2; a <= 12; a++) for (var b = 2; b <= 12; b++) { out.push({ key: 'A' + a + '-' + b, a: a, b: b, variant: 'A' }); if (a !== b) out.push({ key: 'B' + a + '-' + b, a: a, b: b, variant: 'B' }); } return shuffle(out, rand); }, make: function (p) { var v, prompt, steps; if (p.variant === 'A') { v = p.a + p.b; prompt = 'Compute ' + p.a + ' − ' + paren(-p.b) + '.'; steps = ['Rewrite first: ' + p.a + ' − (' + fmtI(-p.b) + ') = ' + p.a + ' + ' + p.b + '.', 'Subtracting a negative is the same as adding its opposite.', 'Answer: ' + v + '.']; } else { v = p.b - p.a; prompt = 'Compute ' + fmtI(-p.a) + ' − ' + paren(-p.b) + '.'; steps = ['Rewrite first: ' + fmtI(-p.a) + ' − (' + fmtI(-p.b) + ') = ' + fmtI(-p.a) + ' + ' + p.b + '.', 'Now the bigger size wins: ' + p.b + ' − ' + p.a + ' = ' + Math.abs(v) + (v > 0 ? ', positive.' : v < 0 ? ', negative.' : '.'), 'Answer: ' + fmtI(v) + '.']; } return finishProblem(prompt, v, HINT_SUB, steps, 'Subtracting integers'); } }, { id: 'MUL_SAME', tag: 'Multiplying integers', enumerate: function (rand) { var out = []; for (var a = 2; a <= 9; a++) for (var b = 2; b <= 9; b++) out.push({ key: a + 'x' + b, a: a, b: b }); return shuffle(out, rand); }, make: function (p) { var v = p.a * p.b; return finishProblem( 'Compute (' + fmtI(-p.a) + ')(' + fmtI(-p.b) + ').', v, HINT_MD, ['Sign rule: (−)(−) → positive (same signs).', 'Multiply the sizes: ' + p.a + ' × ' + p.b + ' = ' + v + '.', 'Answer: ' + v + '.'], 'Multiplying integers'); } }, { id: 'MUL_DIFF', tag: 'Multiplying integers', enumerate: function (rand) { var out = []; for (var a = 2; a <= 9; a++) for (var b = 2; b <= 9; b++) { out.push({ key: 'A' + a + 'x' + b, a: a, b: b, variant: 'A' }); out.push({ key: 'B' + a + 'x' + b, a: a, b: b, variant: 'B' }); } return shuffle(out, rand); }, make: function (p) { var v = -(p.a * p.b), prompt; if (p.variant === 'A') prompt = 'Compute (' + fmtI(-p.a) + ')(' + p.b + ').'; else prompt = 'Compute (' + p.a + ')(' + fmtI(-p.b) + ').'; return finishProblem( prompt, v, HINT_MD, ['Sign rule: one negative and one positive → negative (different signs).', 'Multiply the sizes: ' + p.a + ' × ' + p.b + ' = ' + (p.a * p.b) + '.', 'Answer: ' + fmtI(v) + '.'], 'Multiplying integers'); } }, { id: 'DIV', tag: 'Dividing integers', enumerate: function (rand) { var out = []; for (var q = 2; q <= 9; q++) for (var d = 2; d <= 9; d++) { out.push({ key: 'A' + q + '/' + d, q: q, d: d, variant: 'A' }); out.push({ key: 'B' + q + '/' + d, q: q, d: d, variant: 'B' }); out.push({ key: 'C' + q + '/' + d, q: q, d: d, variant: 'C' }); } return shuffle(out, rand); }, make: function (p) { var dd = p.q * p.d, v, prompt, signNote; if (p.variant === 'A') { v = p.q; prompt = 'Compute ' + fmtI(-dd) + ' ÷ ' + paren(-p.d) + '.'; signNote = 'the same → positive'; } else if (p.variant === 'B') { v = -p.q; prompt = 'Compute ' + fmtI(-dd) + ' ÷ ' + p.d + '.'; signNote = 'different → negative'; } else { v = -p.q; prompt = 'Compute ' + dd + ' ÷ ' + paren(-p.d) + '.'; signNote = 'different → negative'; } return finishProblem( prompt, v, HINT_MD, ['Divide the sizes first: ' + dd + ' ÷ ' + p.d + ' = ' + p.q + '.', 'Sign rule: the signs are ' + signNote + '.', 'Answer: ' + fmtI(v) + '. Check: ' + fmtI(v) + ' × ' + paren(p.variant === 'A' ? -p.d : (p.variant === 'B' ? p.d : -p.d)) + ' = ' + (p.variant === 'A' ? fmtI(-dd) : (p.variant === 'B' ? fmtI(-dd) : dd)) + '.'], 'Dividing integers'); } }, { id: 'WORD_TEMP', tag: 'Word problems', enumerate: function (rand) { var out = []; for (var t = 2; t <= 15; t++) for (var d = 1; d <= 12; d++) out.push({ key: t + 'r' + d, t: t, d: d }); return shuffle(out, rand); }, make: function (p) { var r = p.t + p.d, v = p.d; return finishProblem( 'The temperature was ' + fmtI(-p.t) + '°F and rose ' + r + '°F. ' + 'What is the new temperature? (Answer with just the number.)', v, 'Hint: "Rose" means add: ' + fmtI(-p.t) + ' + ' + r + '. The bigger size wins.', ['New temperature = ' + fmtI(-p.t) + ' + ' + r + '.', 'Walk ' + r + ' right from ' + fmtI(-p.t) + ': ' + p.t + ' steps reach 0, then ' + p.d + ' more.', 'Answer: ' + v + ' (that is, ' + v + '°F).'], 'Word problems'); } }, { id: 'WORD_MONEY', tag: 'Word problems', enumerate: function (rand) { var out = []; for (var b = 5; b <= 40; b += 5) for (var d = 5; d <= 40; d += 5) out.push({ key: b + 's' + d, b: b, d: d }); return shuffle(out, rand); }, make: function (p) { var s = p.b + p.d, v = -p.d; return finishProblem( 'Your account balance is $' + p.b + '. You spend $' + s + '. ' + 'What is your new balance? (Answer with just the number; it will be negative.)', v, 'Hint: balance = ' + p.b + ' − ' + s + '. You spent more than you had.', ['New balance = ' + p.b + ' − ' + s + '.', 'You overspent by $' + p.d + ', so the balance goes negative.', 'Answer: ' + fmtI(v) + ' (a $' + p.d + ' overdraft).'], 'Word problems'); } }, { id: 'MULTI_STEP', tag: 'Multi-step', enumerate: function (rand) { var out = []; for (var a = -9; a <= 9; a++) for (var b = -9; b <= 9; b++) for (var c = -9; c <= 9; c++) { if (a === 0 || b === 0 || c === 0) continue; if (a + b === 0 || a + b - c === 0) continue; out.push({ key: a + ',' + b + ',' + c, a: a, b: b, c: c }); } return shuffle(out, rand); }, make: function (p) { var mid = p.a + p.b, v = mid - p.c; return finishProblem( 'Compute ' + fmtI(p.a) + ' + ' + paren(p.b) + ' − ' + paren(p.c) + '.', v, 'Hint: work left to right, one operation at a time.', ['First: ' + fmtI(p.a) + ' + ' + paren(p.b) + ' = ' + fmtI(mid) + '.', 'Then: ' + fmtI(mid) + ' − ' + paren(p.c) + ' = ' + fmtI(v) + '.', 'Answer: ' + fmtI(v) + '.'], 'Multi-step'); } }, { id: 'MISSING', tag: 'Missing addend', enumerate: function (rand) { var out = []; for (var a = 2; a <= 12; a++) for (var c = -9; c <= 9; c++) { var x = c + a; if (x === 0) continue; out.push({ key: a + 'm' + c, a: a, c: c }); } return shuffle(out, rand); }, make: function (p) { var x = p.c + p.a; return finishProblem( fmtI(-p.a) + ' + ___ = ' + fmtI(p.c) + '. What number goes in the blank?', x, 'Hint: undo the addition: the missing number is ' + fmtI(p.c) + ' − (' + fmtI(-p.a) + ').', ['Call the missing number n: ' + fmtI(-p.a) + ' + n = ' + fmtI(p.c) + '.', 'Subtract ' + fmtI(-p.a) + ' from both sides: n = ' + fmtI(p.c) + ' − ' + paren(-p.a) + ' = ' + fmtI(p.c) + ' + ' + p.a + ' = ' + fmtI(x) + '.', 'Check: ' + fmtI(-p.a) + ' + ' + fmtI(x) + ' = ' + fmtI(p.c) + '.'], 'Missing addend'); } }, { id: 'HARD_MIX', tag: 'Challenge', enumerate: function (rand) { var out = []; for (var a = 2; a <= 9; a++) for (var b = 2; b <= 9; b++) for (var c = 2; c <= 20; c++) { var v = a * b - c; if (v === 0) continue; out.push({ key: a + 'h' + b + 'h' + c, a: a, b: b, c: c }); } return shuffle(out, rand); }, make: function (p) { var prod = p.a * p.b, v = prod - p.c; return finishProblem( 'Compute (' + fmtI(-p.a) + ')(' + fmtI(-p.b) + ') + ' + paren(-p.c) + '.', v, 'Hint: multiply first (same signs → positive), then add the negative.', ['Multiply first: (' + fmtI(-p.a) + ')(' + fmtI(-p.b) + ') = +' + prod + ' (same signs → positive).', 'Then add: ' + prod + ' + ' + paren(-p.c) + ' = ' + fmtI(v) + '.', 'Answer: ' + fmtI(v) + '.'], 'Challenge'); } } ];
/* ---------------- session: scoring, streaks, no-repeat ---------------- */
function PracticeSession(seed) {
this._rand = mulberry32(seed >>> 0);
this.pools = {};
this.seen = {};
this.score = 0;
this.streak = 0;
this.problemNum = 0;
this.current = null;
this.wrongCount = 0;
this.revealed = false;
this._done = false;
}
PracticeSession.prototype._draw = function () {
for (var guard = 0; guard < 200; guard++) {
var t = TEMPLATES[Math.floor(this._rand() * TEMPLATES.length)];
var pool = this.pools[t.id];
if (!pool || pool.length === 0) {
pool = t.enumerate(this._rand).slice();
this.pools[t.id] = pool;
}
var p = pool.pop();
var key = t.id + '|' + p.key;
if (this.seen[key]) continue;
this.seen[key] = 1;
var prob = t.make(p);
prob.templateId = t.id;
prob.key = key;
return prob;
}
this.pools = {}; this.seen = {};
var t2 = TEMPLATES[0], p2 = t2.enumerate(this._rand)[0];
var pr = t2.make(p2);
pr.templateId = t2.id; pr.key = t2.id + '|' + p2.key;
return pr;
};
PracticeSession.prototype.next = function () {
this.current = this._draw();
this.wrongCount = 0;
this.revealed = false;
this._done = false;
this.problemNum++;
return this.current;
};
PracticeSession.prototype.submit = function (raw) {
var pr = this.current;
if (!pr) return { status: 'none' };
if (this._done) return { status: 'already' };
var parsed = parseIntInput(raw);
if (parsed === null) return { status: 'invalid' };
var ok = (parsed === pr.answer.v);
if (ok) {
this._done = true;
var firstTry = !(this.wrongCount !== 0 || this.revealed);
if (!this.revealed) {
this.streak++;
if (firstTry) this.score++;
}
return { status: 'correct', firstTry: firstTry, score: this.score, streak: this.streak, answerHTML: pr.answerDisplayHTML };
}
this.wrongCount++;
this.streak = 0;
if (this.wrongCount === 1) return { status: 'hint', hintHTML: pr.hintHTML, streak: 0 };
this.revealed = true;
return { status: 'why', whyHTML: pr.whyHTML, streak: 0 };
};
PracticeSession.prototype.reveal = function () {
if (!this.current) return null;
this.revealed = true;
return this.current.whyHTML;
};
return {
PracticeSession: PracticeSession,
TEMPLATES: TEMPLATES,
parseIntInput: parseIntInput,
fmtI: fmtI
};
});
/* Shared Fibo practice page glue. Config (set by an inline script before this file): window.FIBO_PRACTICE = { core: 'IntegerPractice', placeholder: '...', entryHelpHTML: '...' } The core must expose: PracticeSession, with .next()/.submit()/.reveal(). Problem objects must provide: tag, promptHTML, answer.kind, hintHTML, whyHTML, answerDisplayHTML. Answers are checked inside the core. */ (function () { 'use strict'; var CFG = window.FIBO_PRACTICE || {}; var CORE = window[CFG.core || '']; if (!CORE) { return; } var session = new CORE.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');
function updateStats() { statsLine.innerHTML = 'Score ' + session.score + ' · Streak ' + session.streak + ' · Problem ' + session.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 entryHint() { return CFG.entryHelpHTML || 'Type your answer and press Check answer.'; } function newProblem() { var pr = session.next(); promptTag.textContent = pr.tag; promptEl.innerHTML = pr.promptHTML; input.value = ''; if (CFG.placeholder) { input.placeholder = CFG.placeholder; } clearPanels(); showSolBtn.style.display = ''; updateStats(); input.focus(); } function showWhy() { whyBox.className = 'whybox show'; whyBox.innerHTML = '
' + session.current.whyHTML; 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. ' + entryHint()); return; } if (r.status === 'already' || r.status === 'none') { return; } if (r.status === 'correct') { if (r.firstTry) { showFb('ok', 'Correct! +1 point. The answer is ' + r.answerHTML + ''); } else if (session.revealed) { showFb('ok', 'That is right. You already saw the solution, so the streak is unchanged. ' + 'Hit Try another for a fresh problem.'); } else { showFb('ok', 'Correct! The answer is ' + r.answerHTML + '. ' + '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.innerHTML = r.hintHTML; input.focus(); input.select(); return; } if (r.status === 'why') { showFb('no', 'Not quite. Let us walk through it step by step.'); showWhy(); return; } }); anotherBtn.addEventListener('click', newProblem); showSolBtn.addEventListener('click', function () { session.reveal(); showWhy(); updateStats(); }); input.addEventListener('keydown', function (e) { if (e.key === 'Enter') { checkBtn.click(); } }); newProblem(); })();