Fast forward to 2008. I've decided to build a simple calculator in Cocoa, one that behaves pretty much like a real-life pocket calculator. Before doing that, I completed yet anoter basic tutorial (I had my reasons for doing that), and decided to take a stab at it.
There were some pretty basic and obvious things I had to re-learn. Maybe this time they will stick around in my head? We'll see. Such as: OK, I declare instance variables, and some of them are objects. They are also properties, with synthesized accessor methods. Cool. But then I still need to initialize them. Yeah, of course. Don't laugh. But that piece of obvious information just got lost somewhere for me.
Also, how about model objects? I have only a handful, and they are built-in Cocoa types. Where do I manage them? Well, in this simple case, I just had the controller object deal with them as instance variables.
OK, on to the calculator. It should be so simple, shouldn't it? Four basic operations, entering numbers, displaying results... But then somehow it didn't end up that very simple for me.
First of all, entering a number. If I'm entering 0.00001, for example, I want to see all the zeros appear before the 1 does. So at one point, the display will have to read "0," then "0.," then "0.0," and so on. Shall I play with number formatting? And how about "0."? It's not even a number. At this point, I decided to display the number being entered as a string, and also store it as such. Actually, two strings: one stores the integer part, and the other, the display string, takes the integer part, inserts commas as thousands separators if necessary, and after the decimal point has been used, it adds that as well, and keeps adding any further digits at the end (at this point, no further thousands separators will be needed).
The string will be converted into a double when necessary. (And that conversion involves removing the thousands separators, something that NSString's getDoubleValue method won't attempt. I wanted to subclass or extend NSMutableString for that, but eventually decided just to write a method in my controller class. Dealing with classes seemed as overkill to my untrained eyes, since there was only one instance of it all.)
But apart from that, a calculator should be simple, shouldn't it? We're entering a number, then a calculation symbol, which the program remembers, and knows that the next number to be entered will be the second operand. Then, pushing the "=" button simply performs the calculation, and that's it! Isn't it?
Well, not really.
In most calculators, if you push the equals button again and again, the last calculation will be repeated. The first operand keeps changing, but the second remains constant. (So, for example, "6 + 2 = = = = =" will yield "8 10 12 14 16.")
Also, if you do not enter a second operand for a calculation, most calculators will use the first operand as the second too. (So, for example, "4 * =" will display "16.")
Combining the two will make this work: "2 - = = = = =" yields "0 -2 -4 -6 -8 -10."
So how did I handle this all? This may not be the simplest way, but it works. The calculator has four modes: ResultDisplayed, FirstOperandEntry, OperationEntry, and SecondOperandEntry.
In ResultDisplayed mode, the result of the last calculation has just been displayed. We can repeat the last calculation (by pressing "="), we can start a new one by simply starting to enter a number (thereby setting the calculator to FirstOperandEntry mode), or we can enter an operation symbol (+, -, *, /) which will set the calculator to OperationEntry mode, recording the operation as well as the currently displayed number as the first operand.
In FirstOperandEntry mode, we can keep entering the number, or we can push an operation button to enter OperationEntry mode.
If we're in OperationEntry mode, we have just entered an operation. However, we can still change it. If we decide to use addition instead of subtraction, we can press "156 - + 2," and nothing special will happen: "156 + 2" will be recorded, the minus sign will be forgotten.
We can quit OperationEntry mode by either starting to type in the second operand (thus entering SecondOperandEntry mode), or by pressing the equals sign, which will use the currently displayed number as the second operand. (The first operand has already been set if we're here.)
Finally, in SecondOperandEntry mode, we're entering, well, the second operand. We can quit that mode by pressing "=," making the calculator perform the calculation and enter ResultDisplayed mode, or by entering an operation symbol, which will also perform the calculation, but it will also record a new operation, and set the mode to OperationEntry.
Performing the calculation will always store the result in the first operand. If we performed a division by zero, an error message will appear instead of the result, and an error flag will be set. With the error flag on, the operation buttons are ignored.
I found it pretty easy to get lost in all thes cases. I ended up drawing up a flowchart... and then realized that my flowchart couldn't be coded using if-else structures: it used weird loopbacks to other branches. I had to redraw my flowchart so that branches could only merge at their endpoints. Duh.
Of course, there's a Clear button, which clears various stuff (there's also a Clear All button, I haven't gotten around to differentiating between the two), plus the unary minus button isn't wired in yet. But these are small details. Everything else works now, all use cases have been tested extensively. And it's a wonderful feeling when it all falls into place. There were numerous bugs, partly arising from the fact that I coded first, then went back to the drawing board, and rewrote the whole thing, keeping some legacy code in it which caused problems. Then I used "==" instead of "=" on one occasion, that one had me almost pulling out my hair. I ended up NSLogging the hell out of everything before I found this one.
Now it works. Great. Am I too stupid to write software, or is a simple little calculator a damned thing to code, with all the gratification of a cheese grater, but the complexity of a a Rubik cube?
Monday, August 18, 2008
Subscribe to:
Posts (Atom)