Notice that the MONEY package builds on the concepts introduced in the previous section. We don't want to multiply dollars times dollars to get dollars squared, and if we did we certainly wouldn't want to assign it to an object of type dollars. Therefore we should use a dimensioned data type to represent monetary values. The question is, which one?
Fixed-point is a logical choice to represent dollars, but I explained why that isn't a good idea when I told why I never wrote a FIXED_UNITS package. A floating-point data type could be used to represent dollars. If we do that, the accuracy will vary with the size of the amount because there will be a fixed number of bits to represent the whole number of dollars plus the fractional dollars (cents). Therefore the dollar figures would be accurate to a certain percentage, but not to a certain number of cents. The accuracy is much worse than expected if calculations compute a small difference between large sums of money. Floating- point isn't a good choice for representing money.
Practical arguments aside, the real reason you shouldn't use a fixed- or floating-point data type to represent money is that money is a discrete quantity rather than a continuous one. If money was specified in terms of ounces of gold instead of individual pennies, then a real variable would appropriate for money. When working with money we are really dealing with a finite number of individual, countable things, so an integer representation is the philosophically correct one.
If we decide to use an integer data type to represent cents, then we must decide how many bits to use. Sixteen bits would allow us to represent values from -$327.68 to $327.67. That's far too small. Thirty-two bits range from -$21,474,836.48 to $21,474,836.47. While this isn't sufficient range to represent national budgets, it is certainly large enough to figure the mortgage on any house I am likely to buy.
Having made this decision, the MONEY package declares a data type Cents which is a dimensioned 32-bit integer. It also provides Image and Value functions to convert between strings and Cents. Notice that even if I had written the ASCII_UTILITIES.Image and ASCII_UTILITIES.Value as generic functions, I could not have instantiated them for type Cents because I want to be able to use a decimal point, a dollar sign, and commas in the string.
The Width function tells the size string required to represent the value. This is essential for declaring strings of the proper size.
Figure 20 is a simple program that demonstrates how to use the money package. It prompts the user to enter the price of an item, the sales tax rate, and prints the total cost to the customer. This isn't a terribly useful program, but it does show how to declare objects of type Cents, and shows how to input and output them.
Cents is derived from Units in INTEGER_UNITS. I can multiply Cents by dimensionless integers, but I can't multiply them by dimensionless real types because there isn't a multiply operator for Units times float in INTEGER_UNITS. That wasn't an oversight. I once had a floating-point multiply in INTEGER_UNITS, but I decided to take it out. I wanted to force myself to think about how to handle the fractional results that are likely to occur.
When PRICE is multiplied by RATE, the resulting TAX may include fractional pennies. In California, sales tax is rounded to the nearest cent. If I lived in a state that insisted on rounding any fraction of a cent of sales tax to the next higher cent, I could have written that into the multiplication algorithm shown in Figure 20.
The body of Figure 20 shows how I used the Value function in MONEY to convert the user's entry from a character string to the PRICE in Cents. If you compile and run this example you might have some fun trying experiments. You will find that you can enter a price of $5.00, $5, 5, 5.0, $05.00, or several other ways you might imaging. The MONEY.Value function lets you embed dollar signs and commas in the price. If you try to enter a a tax rate with a dollar sign in it, you will get a CONSTRAINT_ERROR. That's because the rate is a pure number, interpreted by ASCII_UTILITIES.Value, not MONEY.Value.
I didn't want to make this example too complicated and distract from the use of the MONEY package, so I didn't check for errors (like negative tax rates). If you want to fool around with this example, you might try detecting the presence of a percent sign in the TEXT string, and dividing the RATE by 100 only if the user entered a percent sign.
I put a block structure in the middle of the Sales_Tax program just to show you how you could use MONEY.Width to declare a string like TAX_STRING. I don't normally do that. I usually create the strings on the fly, like I did when I printed COST. (There I didn't bother to create COST_STRING. I just put the MONEY.Image function inside the put procedure.)
I used TEXT_IO as a user interface in this example. I followed every get_line with a new_line. That may or may not cause a blank line after each input, depending on your operating system. If you leave the new_line calls out, you may (or may not) have superimposed input prompts. This is just one of many problem you will discover if you try to use TEXT_IO as a user interface. Fortunately, you don't have to use TEXT_IO if you don't want to. That's our next topic.