Skip to content

Commit 5750b03

Browse files
JrSaintclaude
andcommitted
Switch to no-carry-in multiplication convention
Each pp iteration is now an independent digit × digit step: the ones digit goes in the white pp box and the tens digit goes in the yellow carry box (or as the overflow pp digit on the last step). Carries are no longer added into the next multiplication; instead they become extra addends when summing the columns at the bottom. - computeExpected uses prod = a*b (no prevCarry chained between iterations) - sum column collects pp digits AND carry digits as addends - detectMultiplyMistake updated to use new math; "forgot the carry" hint removed - Hint button explanations no longer mention "plus the carry" - Help text rewritten to describe the new model Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6a43d67 commit 5750b03

2 files changed

Lines changed: 46 additions & 48 deletions

File tree

app.js

Lines changed: 44 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -74,28 +74,30 @@
7474
for (let c = 0; c < M; c++) expected[makeId('multiplier', null, c)] = String(rD[c]);
7575

7676
const ppByCol = [];
77+
const carryByCol = [];
7778

7879
for (let k = 0; k < M; k++) {
7980
const d = rD[k];
80-
let carry = 0;
8181
const pp = new Array(cols).fill(null);
8282
const carryRow = new Array(cols).fill(null);
8383

8484
for (let i = 0; i < N; i++) {
85-
const prod = mD[i] * d + carry;
86-
const digit = prod % 10;
87-
const nextCarry = Math.floor(prod / 10);
85+
const prod = mD[i] * d;
86+
const onesDigit = prod % 10;
87+
const tensDigit = Math.floor(prod / 10);
8888
const col = i + k;
89-
pp[col] = digit;
90-
if (i < N - 1) {
91-
if (nextCarry > 0) carryRow[col + 1] = nextCarry;
92-
} else if (nextCarry > 0) {
93-
pp[col + 1] = nextCarry;
89+
pp[col] = onesDigit;
90+
if (tensDigit > 0) {
91+
if (i < N - 1) {
92+
carryRow[col + 1] = tensDigit;
93+
} else {
94+
pp[col + 1] = tensDigit;
95+
}
9496
}
95-
carry = nextCarry;
9697
}
9798

9899
ppByCol.push(pp);
100+
carryByCol.push(carryRow);
99101

100102
for (let c = k; c <= N + k; c++) {
101103
const key = makeId('pp', k, c);
@@ -112,8 +114,10 @@
112114
for (let c = 0; c < cols; c++) {
113115
let columnSum = addCarry;
114116
for (let k = 0; k < M; k++) {
115-
const v = ppByCol[k][c];
116-
if (v !== null) columnSum += v;
117+
const pv = ppByCol[k][c];
118+
if (pv !== null) columnSum += pv;
119+
const cv = carryByCol[k][c];
120+
if (cv !== null) columnSum += cv;
117121
}
118122
const digit = columnSum % 10;
119123
const nextAddCarry = Math.floor(columnSum / 10);
@@ -413,11 +417,7 @@
413417
const rD = digitsOf(state.multiplier, state.M);
414418
const a = mD[pair.i];
415419
const b = rD[pair.k];
416-
let prevCarry = 0;
417-
for (let j = 0; j < pair.i; j++) {
418-
prevCarry = Math.floor((mD[j] * b + prevCarry) / 10);
419-
}
420-
const product = a * b + prevCarry;
420+
const product = a * b;
421421
const correctOnes = product % 10;
422422
const correctTens = Math.floor(product / 10);
423423

@@ -428,7 +428,7 @@
428428
return `Looks like the two digits got swapped — try flipping them.`;
429429
}
430430

431-
const sumValue = a + b + prevCarry;
431+
const sumValue = a + b;
432432
const addOnes = sumValue % 10;
433433
const addTens = Math.floor(sumValue / 10);
434434
if (typedOnes === addOnes && typedTens === addTens &&
@@ -450,16 +450,21 @@
450450
if (!Number.isInteger(typedOnes) || !Number.isInteger(typedTens)) return null;
451451

452452
const c = pair.c;
453-
const ppVals = [];
453+
const addends = [];
454454
for (let k = 0; k < state.M; k++) {
455-
const v = state.expected[makeId('pp', k, c)];
456-
if (v !== '' && v !== undefined) {
457-
const n = parseInt(v, 10);
458-
if (Number.isInteger(n)) ppVals.push(n);
455+
const pv = state.expected[makeId('pp', k, c)];
456+
if (pv !== '' && pv !== undefined) {
457+
const n = parseInt(pv, 10);
458+
if (Number.isInteger(n)) addends.push(n);
459+
}
460+
const cv = state.expected[makeId('carry', k, c)];
461+
if (cv !== '' && cv !== undefined) {
462+
const n = parseInt(cv, 10);
463+
if (Number.isInteger(n)) addends.push(n);
459464
}
460465
}
461466
const addCarryVal = parseInt(state.expected[makeId('addcarry', null, c)] || '0', 10) || 0;
462-
const total = ppVals.reduce((acc, v) => acc + v, 0) + addCarryVal;
467+
const total = addends.reduce((acc, v) => acc + v, 0) + addCarryVal;
463468
const correctOnes = total % 10;
464469
const correctTens = Math.floor(total / 10);
465470

@@ -470,8 +475,8 @@
470475
return `Looks like the two digits got swapped — try flipping them.`;
471476
}
472477

473-
if (ppVals.length >= 2) {
474-
const product = ppVals.reduce((acc, v) => acc * v, 1);
478+
if (addends.length >= 2) {
479+
const product = addends.reduce((acc, v) => acc * v, 1);
475480
const mulOnes = product % 10;
476481
const mulTens = Math.floor(product / 10);
477482
if (typedOnes === mulOnes && typedTens === mulTens &&
@@ -647,44 +652,35 @@
647652
const mD = digitsOf(state.multiplicand, state.N);
648653
const rD = digitsOf(state.multiplier, state.M);
649654

650-
const carryBefore = (k, i) => {
651-
let c = 0;
652-
for (let j = 0; j < i; j++) {
653-
c = Math.floor((mD[j] * rD[k] + c) / 10);
654-
}
655-
return c;
656-
};
657-
658655
if (p.type === 'pp') {
659656
const k = p.k;
660657
const i = p.col - k;
661658
if (i < state.N) {
662659
const a = mD[i];
663660
const b = rD[k];
664-
const carry = carryBefore(k, i);
665661
const raw = a * b;
666-
const total = raw + carry;
667-
const write = total % 10;
668-
const out = Math.floor(total / 10);
669-
let msg = `${a} × ${b} = ${raw}`;
670-
if (carry > 0) msg += `, plus the carry ${carry} = ${total}`;
671-
msg += `. Write ${write}`;
662+
const write = raw % 10;
663+
const out = Math.floor(raw / 10);
664+
let msg = `${a} × ${b} = ${raw}. Write ${write}`;
672665
if (out > 0) {
673666
msg += i < state.N - 1
674-
? `, and carry ${out} into the next column.`
675-
: `, and put the ${out} in the next column on the left.`;
667+
? `, and write ${out} as the carry above the next column.`
668+
: `, and write ${out} in the next box to the left.`;
676669
} else {
677670
msg += '.';
678671
}
679672
return msg;
680673
} else {
681-
const finalCarry = carryBefore(k, state.N);
682-
return `Bring down the carry ${finalCarry} as the next digit.`;
674+
const lastA = mD[state.N - 1];
675+
const lastB = rD[k];
676+
const lastProd = lastA * lastB;
677+
const lastTens = Math.floor(lastProd / 10);
678+
return `From the last step: ${lastA} × ${lastB} = ${lastProd}. The ${lastTens} (tens digit) goes here.`;
683679
}
684680
}
685681

686682
if (p.type === 'carry') {
687-
return `That's the carry from the last multiplication. Write it above the next column.`;
683+
return `That's the tens digit of the last multiplication. Write it above the next column.`;
688684
}
689685

690686
if (p.type === 'sum') {
@@ -697,6 +693,8 @@
697693
for (let k = 0; k < state.M; k++) {
698694
const v = state.expected[makeId('pp', k, c)];
699695
if (v) { parts.push(v); total += parseInt(v, 10); }
696+
const cv = state.expected[makeId('carry', k, c)];
697+
if (cv) { parts.push(cv); total += parseInt(cv, 10); }
700698
}
701699
if (parts.length === 0) return 'This column has nothing to add — it stays blank.';
702700
const digit = total % 10;

index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ <h1>MathMaker</h1>
8282
<p><span class="swatch swatch-input"></span> <strong>White cells</strong> are where you write your partial products.</p>
8383
<p><span class="swatch swatch-sum"></span> <strong>Green cells</strong> at the bottom hold the final answer.</p>
8484
<p><span class="swatch swatch-blocked"></span> <strong>Grey cells</strong> are blocked — no digit ever goes there. They just keep your columns lined up.</p>
85-
<p>Each <strong>×</strong> partial-product row is where you write what you get when you multiply by one digit of the multiplier — the ones digit first, then tens, etc. The bottom <strong>+</strong> row is for the carries when you <em>add</em> the partial products to get the final answer.</p>
86-
<p><strong>Each partial product is one whole number</strong>, so even its leftmost digit (when the last multiplication step has a carry) goes in the white box on the same row — that's why the leftmost cell above each partial product stays grey.</p>
85+
<p>For each <strong>×</strong> row, multiply one digit at a time: write the ones digit in the white box, and if the result has a tens digit, write it in the yellow carry box above the next column. For the last step of each row, the tens digit goes in the next white box to the left instead of a yellow carry box.</p>
86+
<p>When you <strong>add</strong> the columns at the bottom, include every digit in that column — the white pp digits <em>and</em> the yellow carries above them, plus any addition carry from the column to the right.</p>
8787
<p>Start with the ones column on the right, then work left. Press <kbd>Check my work</kbd> to see how you did, or <kbd>Hint</kbd> to fill the next step.</p>
8888
<p><strong>Allow zeros</strong> only affects <kbd>New random</kbd> problems. When off, every digit of the random number is 1–9. When on, random numbers can include zeros in the middle (like <code>305</code> or <code>2070</code>), which adds tougher cases like "multiply by 0" and "carry through a zero." It does not change anything you type yourself in <em>Your problem from your book</em>.</p>
8989
<p><button type="button" id="btn-reset-score" class="link-button">Reset solved count</button></p>

0 commit comments

Comments
 (0)