Difficult edge cases
Schwieriger wird die Sache, wenn Folgeberechnungen gemacht werden. Schauen wir uns die Sache an:
Calculating with operators
Section titled “Calculating with operators”Wenn wir 9 - 5 - in einen Taschenrechner eintippen, rechnet er und zeigt als Ergebnis 4 an. Warum? Die Antwort ist einfach, hier wird angenommen, dass der Benutzer die Berechnung fortführen möchte. Der Rechner zeigt einfach nur ein Zwischenergebnis an. Unser Rechner macht – gar nichts. Machen wir sicherheitshalber noch einen Test (der fehlschlagen wird):
const tests = [ // ... { message: 'Operator Calculation', keys: ['9', 'minus', '5', 'minus'], result: '4', },];Wir können nun einfach den Code aus dem Istgleich-Abschnitt in den Operator-Abschnitt kopieren:
calculatorButtonsDiv.addEventListener('click', e => { // ... if (buttonType === 'operator') { button.classList.add('is-pressed');
const firstValue = parseFloat(calculator.dataset.firstValue); const operator = calculator.dataset.operator; const secondValue = parseFloat(result);
// Makes a calculation // skips the calculation if there's no 'firstValue' and 'operator' if (firstValue && operator) { let newResult; if (operator === 'plus') newResult = firstValue + secondValue; if (operator === 'minus') newResult = firstValue - secondValue; if (operator === 'times') newResult = firstValue * secondValue; if (operator === 'divide') newResult = firstValue / secondValue;
display.textContent = newResult; }
calculator.dataset.firstValue = result; calculator.dataset.operator = button.dataset.key; }});Der Test läuft durch, allerdings funktioniert jetzt die Kombination Ziffer -> Operator -> Operator nicht mehr (die wir noch nicht getestet haben)!
Number -> Operator -> Operator
Section titled “Number -> Operator -> Operator”Wenn Tim einen Operator nach einem Operator drückt, können wir annehmen, dass er zuerst den falschen Operator gedrückt hat. Wir wollen in diesem Fall also nicht die erste Rechenoperation ausführen, sondern nur die zweite. Schreiben wir kurz für diesen Fall auch einen Test:
const tests = [ // ... { message: 'Number Operator Operator', keys: ['9', 'times', 'divide'], result: '9', },]Die Reparatur ist einfach: wenn der vorhergehende Button ein Operator war, dann wird die Kalkulation gestrichen.
calculatorButtonsDiv.addEventListener('click', e => { // ... if (buttonType === 'operator') { // ...
if (previousButtonType === 'operator') { // do nothing } else { const firstValue = parseFloat(calculator.dataset.firstValue); // ... display.textContent = newResult; } } // ...});Allerdings sieht der do nothing Kommentar in der if-Abfrage komisch aus. Wir machen den Code etwas schöner:
calculatorButtonsDiv.addEventListener('click', e => { // ... if (buttonType === 'operator') { // ...
// Reverses the if condition if (previousButtonType !== 'operator') { const firstValue = parseFloat(calculator.dataset.firstValue); const operator = calculator.dataset.operator; const secondValue = parseFloat(result);
// makes a calculation if (firstValue && operator) { let newResult; if (operator === 'plus') newResult = firstValue + secondValue; if (operator === 'minus') newResult = firstValue - secondValue; if (operator === 'times') newResult = firstValue * secondValue; if (operator === 'divide') newResult = firstValue / secondValue;
display.textContent = newResult; } } // ... } // ...});Es geht aber noch besser! Da wir die Berechnung nur dann ausführen, wenn beide Bedingungen erfüllt sind, also der vorherige Button kein Operator war und wenn es ein firstValue und einen operator gibt, können wir diese in einer Bedingung zusammenfassen:
calculatorButtonsDiv.addEventListener('click', e => { // ... if (buttonType === 'operator') { // ... const firstValue = parseFloat(calculator.dataset.firstValue); const operator = calculator.dataset.operator; const secondValue = parseFloat(result);
// makes a calculation and combines all conditions into one if (previousButtonType !== 'operator' && firstValue && operator) { let newResult; if (operator === 'plus') newResult = firstValue + secondValue; if (operator === 'minus') newResult = firstValue - secondValue; if (operator === 'times') newResult = firstValue * secondValue; if (operator === 'divide') newResult = firstValue / secondValue;
display.textContent = newResult; } // ... } // ...});Follow up calculations
Section titled “Follow up calculations”Wenn User einen Operator klicken, können sie weitere Berechnungen sowohl mit dem Operator als auch dem Istgleich-Zeichen machen. Das macht die Sache etwas komplizierter.
Hier sind die möglichen Kombinationen:
- Ziffer -> Operator -> Istgleich -> Istgleich
- Ziffer -> Operator -> Ziffer -> Istgleich -> Istgleich
- Ziffer -> Operator -> Ziffer -> Operator -> Ziffer -> Operator ->
Follow up calculations with equal
Section titled “Follow up calculations with equal”Zwei weiterführende Berechnungen benutzen die Istgleich-Taste:
- Ziffer -> Operator -> Istgleich -> Istgleich
- Ziffer -> Operator -> Ziffer -> Istgleich -> Istgleich
Im ersten Beispiel drückt Tim 9 - =. Wir interpretieren das als 9 - 9. Der Rechner zeigt also 0. Tim drückt nochmal =. Also rechnen wir 0 - 9 und erhalten -9.
Im zweiten Beispiel drückt Tim 8 - 5 =. Der Rechner zeigt 3. Tim drückt nochmal auf =. Wir rechnen 3 - 5, der Rechner zeigt also -2.
Fügen wir die entsprechenden Tests hinzu:
const tests = [ // ... { message: 'Number Operator Equal Equal', keys: ['9', 'minus', 'equal', 'equal'], result: '-9', }, { message: 'Number Operator Number Equal Equal', keys: ['8', 'minus', '5', 'equal', 'equal'], result: '-2', },];Diese Tests schlagen fehl.
Im ersten Fall zeigt der Rechner 9. Um herauszufinden, was der Rechner macht, loggen wir firstValue, operator und secondValue in die Konsole:
calculatorButtonsDiv.addEventListener('click', event => { // ... if (buttonType === 'equal') { // ... if (firstValue && operator) { let newResult; if (operator === 'plus') newResult = firstValue + secondValue; if (operator === 'minus') newResult = firstValue - secondValue; if (operator === 'times') newResult = firstValue * secondValue; if (operator === 'divide') newResult = firstValue / secondValue; display.textContent = newResult;
// console.log values. console.log(firstValue, operator, secondValue, '=', newResult); } else { display.textContent = parseFloat(result) * 1; } } // ...});Im ersten Schritt sind firstValue und secondValue gleich 9, das ist soweit richtig. Im zweiten Schritt bleibt firstValue allerdings auch 9, weil das immer noch so als custom attribute gespeichert ist. secondValue ist gleich 0, weil wir dafür den angezeigten Wert (also result) definiert haben. Beides ist falsch. Beide Werte müssen nach der ersten Berechnung aktualisiert werden.
firstValue kann einfach nach der Berechnung dem result zugewiesen werden:
calculatorButtonsDiv.addEventListener('click', e => { // ... if (buttonType === 'equal') { const firstValue = parseFloat(calculator.dataset.firstValue); const operator = calculator.dataset.operator; const secondValue = parseFloat(result);
if (firstValue && operator) { let newResult; if (operator === 'plus') newResult = firstValue + secondValue; if (operator === 'minus') newResult = firstValue - secondValue; if (operator === 'times') newResult = firstValue * secondValue; if (operator === 'divide') newResult = firstValue / secondValue;
display.textContent = newResult;
// assign results to firstValue calculator.dataset.firstValue = newResult; } else { display.textContent = parseFloat(result) * 1; } } // ...});Für secondValue müssen wir das neue Ergebnis speichern. Das ist ein bisschen schwierigier, weil es aus dem Display verschwindet. Wir behelfen uns, indem wir ein neues custom attribute setzen:
calculatorButtonsDiv.addEventListener('click', e => { // ... if (buttonType === 'equal') { // ... if (firstValue && operator) { // ...
// assign results to firstValue calculator.dataset.firstValue = newResult; // store second value as modifier for followup calculations calculator.dataset.modifierValue = secondValue; } else { display.textContent = parseFloat(result) * 1; } } // ...});Bevor wir eine weitere Berechnung durchführen, müssen wir prüfen, ob ein modifierValue vorhanden ist. Wenn ja, setzen wir das modifierValue gleich dem secondValue. Wenn nein, nutzen wir, wie gehabt, das angezeigte Resultat als secondValue:
calculatorButtonsDiv.addEventListener('click', e => { // ... if (buttonType === 'equal') { const firstValue = parseFloat(calculator.dataset.firstValue); const operator = calulator.dataset.operator; // finds modifier value const modifierValue = parseFloat(calculator.dataset.modifierValue); // if present, use it as second value const secondValue = modifierValue || parseFloat(result);
if (firstValue && operator) { let newResult; if (operator === 'plus') newResult = firstValue + secondValue; if (operator === 'minus') newResult = firstValue - secondValue; if (operator === 'times') newResult = firstValue * secondValue; if (operator === 'divide') newResult = firstValue / secondValue;
display.textContent = newResult; calculator.dataset.firstValue = newResult; calculator.dataset.modifierValue = secondValue; } else { display.textContent = parseFloat(result) * 1; } } // ...});So, jetzt schlagen alle Tests fehl! Das ist aber normal, wenn man Features einbaut. Wir haben modifierValue benutzt, aber es wird nicht zurückgesetzt, wenn wir den Rechner zurücksetzen. Fügen wir das also ein:
calculatorButtonsDiv.addEventListener('click', e => { // ... if (buttonType === 'clear') { if (button.textContent === 'AC') { delete calculator.dataset.firstValue; delete calculator.dataset.operator; // clearing the modifier value delete calculator.dataset.modifierValue; }
display.textContent = '0'; button.textContent = 'AC'; } // ...});Wir fügen außerdem resetCalculator() noch eine Zeile hinzu, um sicherzustellen, dass modifierValue auch zurückgesetzt wird:
function resetCalculator() { // ... console.assert(!calculator.dataset.modifierValue, 'No modifier value');}Wenn wir jetzt testen, funktionieren die alten Tests wieder! Auch der neue Test Number -> Operator -> Number -> Equal -> Equal funktioniert. Number -> Operator -> Equal -> Equal wirft aber weiterhin eine Fehlermeldung. Warum? Loggen wir wieder firstValue, operator, secondValue und newResult und schauen wir uns die Sache an:
calculatorButtonsDiv.addEventListener('click', e => { // ... if (buttonType === 'equal') { // ... if (firstValue && operator) { // ... // console.log values console.log(firstValue, operator, secondValue, '=', newResult); } else { display.textContent = parseFloat(result)* 1; } }});Der Rechner führt die erste Berechnung aus, aber die zweite nicht mehr, egal, wie oft wir auf Istgleich drücken. Warum? Schauen wir auf die Konsole:
firstValue=9operator=minussecondValue=9newResult=0
Für die zweite Berechnung wird firstValue der Wert von newResult aus der ersten Berechnung zugewiesen. Damit wird firstValue zu 0 und damit falsey – und somit wird auch unsere Bedingung nicht mehr erfüllt:
if (firstValue && operator) { // ...}// Example of how typeof worksconsole.log(typeof 42); // numberconsole.log(typeof 42 === number); // trueBauen wir nun die richtige Prüfung ein:
calculatorButtonsDiv.addEventListener('click', e => { // ... if (buttonType === 'equal') { // ...
if (typeof firstValue === 'number' && operator) { // make calculation } else { // ... } } // ...});Follow up calculations with operators
Section titled “Follow up calculations with operators”Wir können folgende Berechnung mit Operatoren machen:
- Ziffer -> Operator -> Ziffer -> Operator Wir sollten aber auch das ermöglichen:
- Ziffer -> Operator -> Ziffer -> Operator -> Ziffer -> Operator etc.
Angenommen, Tim drückt folgende Tasten: 1 + 2 + 3 + 4 + 5 +.
Das sollte passieren:
1 + 2 +-> Der Rechner zeigt 33 +-> Der Rechner zeigt 64 +-> Der Rechner zeigt 105 +-> Der Rechner zeigt 15
Machen wir den Test:
const tests = [ // ... { message: 'Operator follow-up calculation', keys: ['1', 'plus', '2', 'plus', '3', 'plus', '4', 'plus', '5', 'plus'], result: '15', },]Der Test schlägt natürlich fehl.
Wir müssen
firstValuemit dem berechneten Ergebnis ersetzensecondValueaus dem Display holen
Letzteres tun wir schon, ersteres geht so:
calculator.buttonsDiv.addEventListener('click', e => { // ... if (buttonType === 'operator') { // ...
if ( previousButtonType !== 'operator' && typeof firstValue === 'number' && operator ) { // ...
display.textContent = newResult;
// if there's a calculation, we change firstValue calculator.dataset.firstValue = newResult; } else { // otherwise, we set the displayed result to firstValue calculator.dataset.firstValue = result; }
calculator.dataset.operator = key; } // ...});Jetzt sollten wir follow-up Berechnungen mit Operatoren machen können.
Blöderweise schlägt jetzt wieder ein anderer Test fehl: Equal Operator.
Fixing the Equal Operator test
Section titled “Fixing the Equal Operator test”Was passiert hier? Schauen wir nochmal kurz den Test an:
const tests = [ // ... { message: 'Equal Operator', keys: ['1', 'plus', '1', 'equal', 'plus', '1', 'equal'], result: '3', },]Der Rechner rechnet, wenn wir Equal -> Operator klicken. Das sollte nicht passieren. Wir können das ganz einfach lösen, indem wir prüfen, ob der vorherige Button equal war.
calculatorButtonsDiv.addEventListener('click', e => { // ... if (buttonType === 'operator') { // ...
if ( previousButtonType !== 'operator' && // checks if previous button is equal key previousButtonType !== 'equal' && typeof firstValue === 'number' && operator ) { // ... } else { // ... } calculator.dataset.operator = key; }});