the math wizard
|
on the web
|
|
|||||||||||||||||||
Contact and Disclaimer |
You
may reach the Math Wizard for questions or comments about this topic at
this email address: [
math_wizard44@hotmail.com
] Feel free to share with my thoughts, as long as these messages do not
constitute destructive criticism.
This code has been tested to work with near perfect accuracy in home computers running the QBasic 1.1 Interpreter from Microsoft. It has not been tested as part of any subsystem that handles mission-critical (or otherwise critical) data, nor has it been designed for use of that purpose in mind. The main purpose of this code is to demonstrate how QBasic can be used in a problem such as Checkese (see below). When using/changing this public domain code, keep in mind that I make no representation about its ability and/or fitness, and that you should test all public-domain code thoroughly before using it in your own programs. |
||||||||||||||||||
|
|||||||||||||||||||
Introduction |
I
myself, as a little boy, have always wondered why checks had to have the
English wording of the Amount of Currency included. I've always thought
it silly if someone who already knew how to read numbers would need this
extra system. Perhaps you are still wondering.
The reason is that people, because of their handwriting, may commit mistakes rendering a number amount unreadable; this opens the value to interpretation. A smudged "3" might look like an "8" to a banker with an eye problem. A writer of a check might misplace the decimal point, making the amount ten times as large or as small as intended. For this reason, wording currency values in English (from now on referred to as "Checkese") is a sort of check to see if the numeral value is indeed correct. This low level of technology might save avid check writers some money. |
||||||||||||||||||
|
|||||||||||||||||||
Writing the Code |
We
are going to write a program (or more properly, a "function") that returns
the input currency value in Checkese. So we write this into the environment
as such:
This does nothing but declare the function we are about to make. You will note some things about this function. One in particular is that it is designed to return a String value (as indicated by the $ in the function name). This is important because Checkese, which is a "word-for-word" representation of a value, will use letters and ASCII characters. Another is that the parameter "Amount" is also a string. This makes it easier to read in and manipulate the individual digits of the data, which we will be doing a lot of in this function. A longer explanation is provided further down. Now we make a new function named Checkese
and whose only parameter is a string named Amount. We then make the value
in Amount a local variable to that function.
We have now converted the value in Amount into a string which we can now read and manipulate sequentially. This is important as we will have the program "read", or make sense out of, the input, and convert these digits into Checkese. We also made variables "WholeValue" and "DecimValue" that will store parts of the finished string. The other variables are holders for temporary data. Now you might be wondering why our function
only takes strings of integers. This is due to a convention to the way
we are going to write down our currency values. In this convention, we
are going to get rid of the need to use decimals in our value. For example,
if we wanted the function to accept a value such as [ $ 5,236.23 ], we
will write it thus:
The last two digits of the input value
will always be after the decimal point (as is in most current systems).
If we wanted ten thousand dollars [ $ 10,000.00 ], we will write it thus:
We need to add two extra zeroes to stand for the cents value if we want a whole number. (Notice as well that we do not need the employment of commas; commas are a means to aid in the reading of values.) I will explain below how the function will
read the given string.
Notice that this procedure reads from the right of the value to its left. This is because it is predictable what the next translated value (or "chunk", if you will) would be when you start from the right. Consider the following example, wherein the computer reads the value sequentially from the left.
It would not know (without being told beforehand by some other means) that the first 6 is grouped by itself: [ six thousand ]. However, it knows (because from the logic of our numbering convention) that the last two digits are to be coded as [ 50/100 dollars ]. Once these are deleted, we can take the next three digits [787] and code them as [ seven hundred and eighty-seven ]. And once these are deleted, the computer can now clearly see that [6] is coded as [ Six thousand ]. Of course, for the computer to be able to translate a value to Checkese (which is in essence English), we must teach it a few English words. So we will build three arrays to store some words the computer needs to know to translate with.
The second would be a two-dimensional one called Numeral(). It would translate digit values (like "two" as opposed to "twenty"). Take note of the entry at Numeral(2, 1). It is the syllable "TEEN", and thus the number "11" would (at first) be translated as [ TEEN-one ]. The third array would contain the names of numbers from [10] through [19], the "TEEN" numbers. A special part of the translation subroutine would replace a string like [ TEEN-one ] to [ eleven ] according to this array. This array would be called Teen().
We now have to construct the "translator". There will be two: one that translates the last two digits, and another to handle the groups of three digits we have allowed for. We will first make the translator for the last two digits, since it is the most intuitive. We need a function (RIGHT$() in QBasic)
that will take the last digits from the Start String. Here is the code
to do Steps 1 to 3.
It is as simple as it looks, while the last statement might need some explanation. The equivalent of "deleting" the last two characters from the string is to keep the first [(Length of Amt) - 2] characters of Amt, and to discard the rest (the last two). What is left now is the most difficult
part of the procedure. We need to carry out Step 4 as a conditional loop.
The loop and part of its setup will look something like this.
Notice that NumGroups is set to 1. This value will tell the procedure what grouping symbol (thousand, million, and so on) to append to the result. Here is the code for Step 4 of our algorithm.
Note that while it takes the groups of three digits one at a time, it takes the digits themselves one at a time as well; in fact, there are separate lines of code for the RIGHTMOST character, the MIDDLE character, and the LEFTMOST character. This merely imitates the way humans read digits in. Notice also that on many of the places, attention is paid to formatting the WholeValue with commas, the word "and", and spacing. We wanted to keep the Numeral library as free of spaces as possible. (The obvious exception would be Numeral(2, 0); see above for details.) The last part of the algorithm is now ready (Steps 5 and 6). We are to clean up the WholeValue string, combine the two strings (DecimValue and WholeValue), format them so that they fit into each other, and then put them into the return value of the function. Here is the last part of the Checkese function
code.
And there you go. The resulting string is assigned to the Function itself, and is returned by the function to the program. |
||||||||||||||||||
|
|||||||||||||||||||
Quality Control |
Of
course, as with any imperfect theory, the translation subroutine needed
some "tweaking" around with before it would work out right.
For example, one thing I always encountered when I tested the routine were extra spaces and commas to the right of the output caused by extra formatting. I fixed this by using the LEFT$() code in the above code sample, which simply erased them. Earlier in this article, we have said that
"quadrillion" was enough for most professionals to work with. What if the
user enters a string that goes beyond "hundreds of quadrillions"? We can
trap for this incalculable error beforehand. A "hundred quadrillion" string,
plus the extra two decimals, is worth 20 characters. So after all the variable
declarations (before the procedure actually manipulates the string), we
insert this conditional.
As can be seen, the value of the function becomes the string "ERROR". The program then exits the function. (If this trap is not introduced, values with more than 20 digits would produce a "Subscript out of range" error message anyway.) What if this 20-digit stuff is not enough?
What if we needed to calculate quintillions, sextillions, and so on? All
we need to do is to add functionality to the program. We add an array value
to GroupingAppend() and increase the trap amount by three for every GroupingAppend
we add.
N.B. : This improvement was not made to the source code that comes with this program. |
||||||||||||||||||
|
|||||||||||||||||||
Some Improvement Challenges |
Here
is a list of things that you, the avid QBasic programmer, might want to
do to improve on the algorithm.
Is there any way to make the main translation subroutine take up less lines of code? I am bothered by this because most of the lines in this subroutine (Step 4) are "formatting" subroutines: they take out extra spaces, look up the TEEN array, and so on. Even if it were just an ingenious way to put all this formatting at the end of the subroutine, it would increase the readability of the code. Notice also on the translation subroutine, there are two line labels: EndChunk and AllZero. EndChunk is used to clean up all the temporary variables and AllZero is where the program goes when it wants to skip a three-digit chunk of zeroes. Is there any way of removing these line labels so that it can be more easily ported to C? Which takes me to the last challenge: can you port this procedure to C/C++? This time, it would be a function that takes a char array and that will return another char array. |
||||||||||||||||||
|
|||||||||||||||||||
Program Source |
Here's
the complete source code of the program, including a short demonstration
of its abilities. Notice that I have added a "bonus" function: CheckeseCurrency$().
This just formats a Checkese-ready string into proper currency notation.
(I think the code is self-explanatory; it is easy to figure out since it
is derived from the original Checkese function itself. Have fun with the
code. N.B.: The last DATA entry was supposed to demonstrate what happens
if the string were too long.
|
||||||||||||||||||
|
|||||||||||||||||||
That's
it for this article. If you would like to see me write more articles in
the future, you can do one of two things: a) Send
me an electronic message requesting me to write one, or b) post
a request to the fine people at the Basix Fanzine.
Have fun coding!
|