Numbers up.
Hopefully by now you’re feeling pretty confident with puts and for statements. While it’s important to be able to work with strings, computers are best at working with numbers. If you think back to your junior high math classes, you’ll remember (or maybe not) some discussions on different types of numbers: real numbers, rational numbers, integers, whole numbers, and imaginary numbers. In programming, we’ve given slightly different names to these different types of numbers. Real and rational numbers are called floating point. Both whole numbers and integers are called integer. In other languages, we also have to worry about how much memory each type of number uses. In C, a short integer uses 1 byte and can only be between –128 and 127. If the number is unsigned, it can be between 0 and 255. An integer is 2 bytes, and a long integer is 4 bytes. Normal floating point numbers are 2 bytes, and double-precision floating point numbers are 4 bytes. When variables are first created in these languages, you must first decide what type of number you plan to use. Then you need to use special statements that only work with that type of number, or use a special conversion statement to make it work.
In Euphoria, all of these numbers can be safely defined as atom. An atom in Euphoria is 4 bytes. One bit is used to define whether or not the variable part of a sequence or atom, so that means an atom can be any number between –1073741824 and +1073741823. Floating point numbers are accurate to 14 decimal places. If you need to work with larger numbers, you should look for one of the big number libraries on the “Recent User Contributions” page of the Euphoria website. “Big Number Arithmetic” by Craig Gilbert has been around for quite a while, and “Big Math” by Matthew Lewis boasts a great many features.
Let’s do some number crunching of our own in a program named numbers1.ex. Type, save, and run the following program:
constant
TAXRATE = 0.07
atom
aftertax
printf(1,”The
tax rate is %5.2f%%.\n\n”,
TAXRATE * 100)
for
dollar = 1 to 20 do
aftertax = dollar * TAXRATE
printf(1,”The tax on $%2d is $%04.2f.\n”,
{dollar,aftertax})
end
for
If you entered the program correctly, you should have seen a line with the tax rate, and 20 lines with the tax calculated. The first new thing in the program is the use of the constant. A constant is not a new data type like atom or sequence, but it is a reference to an unchangable piece of data. Constants are always initialized in the definition, while variables are never initialized in the definition. You’ll also notice that we entered the constant name in all capitals. This is a common practice when naming constants to make it clear to anyone reading page 500 that the name defined on page 1 is a constant. It is not necessary to, nor necessarily poor style if you don’t, capitalize your constants; but it does make it easier in many programs to differentiate.
There are three main reasons for us to use a constant instead of a variable. First, it prevents the value from changing during the course of the program. Second, it allows us to change the value in a single line rather than searching through thousands of lines to change each the value each time it’s used. Third, it makes it easier to tell what the program is doing if the constant is clearly named.
The next new statement is printf. This statement comes directly from C and has almost the exact same syntax as its C counterpart. There are three parameters in printf: the file pointer, a format string, and the data. As we mentioned with puts, we use file pointer 1 to print to the screen. Remember that puts can only print strings. We use formatting codes in the printf string to tell Euphoria what the number is supposed to look like. These format codes always begin with a percent sign. The following explanation of format codes is taken directly from the Euphoria manual (library.htm):
%d - print an atom
as a decimal integer
%x - print an atom as a hexadecimal integer
%o - print an atom as an octal integer
%s - print a sequence as a string of characters, or print an atom as a single
character
%e - print an atom as a floating point number with exponential notation
%f - print an atom as a floating-point number with a decimal point but no exponent
%g - print an atom as a floating point number using either the %f or %e format,
whichever seems more appropriate
%% - print the '%' character itself
Field widths can
be added to the basic formats, e.g. %5d, %8.2f, %10.4s. The number before the
decimal point is the minimum field width to be used. The number after the
decimal point is the precision to be used.
If the field width
is negative, e.g. %-5d then the value will be left-justified within the field.
Normally it will be right-justified. If the field width starts with a leading
0, e.g. %08d then leading zeros will be supplied to fill up the field. If the
field width starts with a '+' e.g. %+7d then a plus sign will be printed for
positive values.
Based on this, we can see that the first line is supposed to print the tax rate as a percent (including the percent sign) using a maximum of five characters and two decimal places. The five characters come from 2 digits before the decimal plus the decimal itself (1 character) plus the 2 decimal places. Since we didn’t include a zero before the 5, and we didn’t include a minus sign before the 5, the value will be printed with a single leading space since we only need 1 digit before the decimal.
We only need one item of data in this statement. Since TAXRATE is a decimal and we want to print it as a percent, we need to use an expression to convert it. Very simply, we multiply the decimal by 100 to get the percentage value. However, if you’ve never programmed before, you might expect the letter ‘x’ to represent multiplication. Sorry, folks, it doesn’t. Addition and subtraction are represented by plus and minus (+ and -) as you would expect, but multiplication is represented by the asterisk (*) and division by the slash (/).
The next expression we see is inside the for loop. This expression calculates the tax on the current dollar amount and assigns it to our variable aftertax. Because we want to line up our results, we’ll again use printf to display them. We want our dollar amount to be displayed as an integer, so we use %d allowing two places. Since want the tax to be displayed with two decimal places, we choose %f as our format code.
Take special note of the data parameter in our second printf. Notice the curly braces ({}) that surround our two variables. This tells us the parameter is a sequence instead of an atom. Remember from last chapter that we said a sequence is a list of objects. When we used strings, we used quotations marks to deliniate the sequence because we were referring to a list of characters. When we’re referring to a list of numbers, or a list of mixed objects, we need to use the curly braces to show the sequence.
We’ve covered quite a lot of material in this short program. Take some time now to play with it a little bit. Change the format codes and see what happens. Change the value of TAXRATE and see how easily the program adjusts itself.
We’re going to start getting a little more complicated now. Don’t worry – if you’ve managed to keep up this far, you’ve already crossed the hardest barriers. Type in this next program, save it as number2.ex, and run it.
constant CUSTNUM = 174
-- Customer number
constant ITEM1 = "Coat" -- First item purchased
constant ITEM2 = "Umbrella" -- Second item purchased
constant PRICE1 = 24.99
-- Price of first item
constant PRICE2 = 8.95
-- Price of second item
sequence filename --
Name of output file
atom fp --
File pointer for output file
sequence today -- Current date & time information
atom total --
Total payment due
filename = sprintf("i%07d",CUSTNUM)&".txt"
fp = open(filename,"w")
today = date()
printf(fp,"Invoice for customer %d on %2d/%2d/%4d at
%2d:%02d:\n\n",
{CUSTNUM,today[2],today[3],
today[1]+1900,today[4],today[5]})
printf(fp,"%-15s: $%6.2f\n",{ITEM1,PRICE1})
printf(fp,"%-15s: $%6.2f\n",{ITEM2,PRICE2})
total = PRICE1 + PRICE2
printf(fp,"%-15s: $%6.2f\n",{"TOTAL",total})
close(fp)
puts(1,"Invoice written to file: " & filename &
"\n")
By now the
concept of constants should be pretty clear.
You’ll notice that since we’re keeping track of a lot more information
than in previous programs, we’re using more comments to keep things clear. Clear variable names by themselves aren’t
going to cut it in a larger program.
Notice also the use of white space to separate major sections. Proper use of white space in indentation is
just as important to good style as good comments and clear variable names.
We have two blocks
of variable definitions. The first
block is for our file operations, the second is for use in printing the
invoice. We repeat the same block
separation with our statements. Take a
look at how we create our filename. We
could use a string literal, which would be perfectly acceptable. However, in this case, we are using the
customer number (CUSTNUM) to create the file name so we can have a
unique invoice file for each customer.
To do this, we will use the sprintf function. A function is a statement that returns
a value. We use functions in the same
places that we would use variables or constants. In contrast, statements like printf and puts are
called procedures. Procedures
must be used by themselves at the beginning of a line, where functions cannot
be used by themselves at the beginning of a line.
The sprintf
function is exactly like the printf procedure except for two
things: sprintf must be used as
part of an assignment or part of another procedure, and sprintf does not
use a file pointer. That means there
are only two parameters to sprintf: the format string and data. In creating the file name, we are using the
letter ‘i’ to mean “Invoice”, a 7-digit customer number with leading zeroes,
and an extension. We could have easily
included the extension as part of the format string, or used concatenation to
join the letter ‘i’ with the customer number; but this method shows clearly the
break between filename and extension.
The next line
opens our file for writing. It may
surprise you that open is a function rather than a procedure. This is because it is possible for file
operations to fail. The most likely
places for file operations to fail are when opening the file and when reading
from the file. Therefore, these
operations are functions. In a true
user-friendly program, we would test the success of open immediately
after the function; however, that is beyond the scope of this chapter. We’ll get to that a couple of chapters from
now. In the meantime, if there is a
problem, the program will stop with an error message.
The second
parameter of open may also be confusing. The second parameter must be a string with one of the following
meanings:
“w” -
open the file for writing text,
overwriting any existing file
“wb” - open the file for writing binary,
overwriting any existing file
“r” -
open the file for reading text
“rb” - open the file for reading binary
“a” -
open the file for writing, appending to
any existing file
“u” -
open the file for both reading and
writing
If you try to
read from a file that is opened in write mode or vice versa, your program will
crash with an error. So why not open
every file in update mode? First, if
the file doesn’t exist, it will generate an error. Second, you can easily overwrite existing data in the middle of
the file and corrupt the whole thing.
That would not be good. We’ll
discuss reading and updating more in the next chapter when we talk about input. For now, let’s get back to writing our
output to a file.
The statements printf
and sprintf lend themselves very nicely to printing out date and time
information. Euphoria has a nice
selection of built-in functions, which can all be found in “library.htm” of the
Euphoria manual. One of those is date,
which returns, if you can’t guess, the current date and time. The date function takes no
parameters, but the parentheses are still needed. It returns a sequence of eight elements. In Euphoria, as with many other languages,
we reference each element by using straight brackets ([]) and an index
number, starting with 1. Each
element returned by date is an atom.
The sequence returned is this: {year, month, day, hour, minute, second,
day of week, day of year}. The only
unintuitive element is year, which is actually the number of years since
1900. To get the current year, we need
to add 1900 to today[1].
In order to get
the lines to print to the file instead of the screen, we simply use our fp
variable instead of 1. The same thing
works for puts or any other statement that displays information on the
screen. In this program, we want to
line up the numbers even though the descriptions (ITEM1, ITEM2, and total) are
of different lengths. We can use the
format code %s to handle this. We used
the minus sign to left justify the text, and we allowed for 15 characters. If you had played around with the first
program, you might have noticed that data longer that the space allows simply
expands the string so it fits. The same
thing will happen here, which will push our numbers out of alignment. Another way of aligning numbers might look
something like this:
puts(fp,ITEM1)
for dot =
length(ITEM1)+1 to 15 do
puts(fp,”.”)
end for
printf(fp,”:
$%6.2f\n”,PRICE1)
Try inserting
these lines just before the line that prints ITEM1 and PRICE1, and commenting
out the full printf line (by adding two hyphens to the beginning of the
line). Re-run the program and look at
the results in the output file. Notice
now that the spaces have been replaced by dots. Also notice that it took 5 lines and a loop to do the same thing
that was done in one line. This is
obviously not a very concise solution.
Since we can figure out how many dots we need by taking the difference
between length( stringname ) and 15 spaces in this case. The length function tells us how long
any sequence is, not just a string.
Let’s use a more concise solution by replacing the above 5 lines with
the following one:
puts(fp,ITEM1
& repeat(‘.’,15-length(ITEM1)) &
sprintf(“: $%6.2f\n”,PRICE1))
By using a
combination of concatenation, repeat, length, and sprintf,
we’ve now been able to create a clear, concise, and correct means of aligning
our numbers using dots as fillers instead of spaces. The repeat function takes two parameters: the object to repeat,
and the number of times to repeat it.
It then returns the sequence we asked for. Notice that we used single-quotes around the dot. That is how Euphoria tells the difference
between a character (an atom) and a string (a sequence). If you had used double-quotes, you would
have gotten an error saying something about a sequence inside a string.
At the end of the
program, we do a little cleaning up.
Before ending a program, you should always close any open files. The close statement takes only the
file pointer variable as a parameter.
Be careful not to use the file name as you will get an error. In most languages, failure to close files
before ending the program will result in data being lost and file
corruption. However, Euphoria will be
kind and close any files you forget for you.
Even though this safety feature is built in, it is still no excuse for
sloppy programming. Make sure you
always close all files before ending your program.
The last bit of
clean up we do is print a message on the screen telling the user where the
output is located. Even though the
program would do everything we wanted without this message, the user would be
left with a blank screen or an immediate return to the command prompt. It would almost appear that the program
didn’t do anything. This simple message
informs the user that the operation completed successfully. The “no news is good news” philosophy may be
appropriate in some applications; but that is rarely the case in programs run
by end users.
There is one last
function I’d like to discuss. Let’s
change the date that’s printed to a two-digit date. Change the line that prints the header to the following:
printf(fp,”Invoice
for customer %d on “ &
“%d/%2d/%02d at %2d:%02d:\n\n”,
{CUSTNUM,today[2],today[3],remainder(
today[1],100),remainder(today[4],12),
today[5]})
If you’ve been
following, you’ll notice that it’s quite possible to have one statement
spanning several lines. The only caveat
with that is that strings cannot span to the next line. If I need to break a string into several
lines as in this example, I need to close the string and concatenate it to a
new string on the next line. Failing to
do that will generate a syntax error.
Otherwise, I can break a statement at any punctuation mark (usually a
comma or parenthesis).
The first thing I
did in this statement was remove the leading space from the date. Whether the month has one digit or two, the
date will only have one space separating it from the word “on”. The next thing I did was change the year
from 4 digits to 2 digits and zero-fill it.
I’m not zero-filling the day, because it’s perfectly natural to
space-fill the month and day, but not so the year. The remainder function returns the remainder after
dividing the first parameter by the second.
We applied the remainder of year / 100 to get our 2-digit year, and we
applied the remainder of hour / 12 to get a normal 12-hour time. If we were really adventurous, we could use
a sequence containing {“am”,”pm”}, a %s format string, and a data element of ampm[floor(today[4]/12)+1]
to get our time of day text to print after the time. I leave it as an exercise for the reader to figure out how to fit
these pieces into our program ;-).
Review.
We’ve been through quite a lot this
chapter. We’ve covered different types
of numbers, constants, the printf and sprintf statements with
their format strings, writing to files, sequences, and a number of neat
functions to help us get the job done faster.
Keep the numbers2.ex
file handy. We’ll be building onto it
in the next chapter. My challenge to
you at the end of this chapter is to look in the Euphoria manual and study some
of the math and miscellaneous functions available. Don’t worry if you can’t figure them all out, we’ll be covering a
lot more of them in later chapters. If
you’re looking for some direction, try printing a list of birthdays or
appointments. Or print a list of
perfect squares. Or sines of a series
of angles. Or….
Next chapter….Need Input…