Numbers And Calculation

Sometimes it's necessary just to work with numbers rather than with geometric figures. For example, if you want to make a circle that has 1.72 the radius of another, you may be able to figure out a way to do it geometrically, but it's a heck of a lot easier if you can just use numbers. For example, here's a chunk of Geometer code that will make a circle centered at v1 and passing through v2 and will, in addition, draw a circle centered at v3 having a radius of π times the radius of the first circle:

c1 = .c.vv(v1, v2);
rad = .f.vv(v1, v2);
newrad = .f.rpn(rad, 3.14159265, .mul);
c2 = .c.vf(v3, newrad);

The first line is standard, but the second line says to make a number called "rad" whose value is the distance between the points v1 and v2. The third line says to take the value of rad, as a number, and to multiply it by 3.14159265, and to store the value in newrad. Finally, the last line says to produce a circle of radius newrad centered at the point v3. The "f" in the commands above is short for "flt" which you can think of as a floating point number.

There is a whole series of commands that obtain, manipulate, and use floating point numbers. The only access to them is via the editor interface—i.e. there is no way to use them via the graphical user interface.

These numbers are usually used as coordinates, or as sizes in transformations, so before you can use them, you have to have some idea of Geometer's coordinate system. Earlier, we said that it goes roughly from -1.0 to 1.0 in both dimensions. This is exactly true for a square display area, but if the display area is a little longer than wide or wider than tall, the images would appear distorted with these coordinates. So what Geometer does is to determine the minimum of the width and the height, and makes that minimum length run from -1 to 1. Thus if the display window size were 500 pixels wide and 400 high, the x coordinates would run from -1.25 to 1.25, and the y coordinates from -1.0 to 1.0.

The numbers can also be used to describe angles, like 90°. Most people measure angles in degrees, mathematicians and computer programmers almost always measure them in radians, where 2π radians is equal to 360°. Geometer goes both ways, but if you do nothing, it assumes you're working with degrees. You can add a line to your file like this:

.radianmode;
and if that line appears, all angles in your file will be expressed in radians. The default setting is .degreemode;, but since it's the default, it need never appear in your file. In some of the examples that follow, I will include ".degreemode" explicitly just to remind you that angles are measured that way. There's no need to include it in a normal diagram (although it is legal to include it, and Geometer will cheerfully eliminate it from any output file it creates).

Here is a list of the all the commands involving flts that do not involve transformations. Transformations are covered in the next section. In the command names, the appearance of an "f" is used to indicate that a number (a "f"loating point number) is used or produced. In the parameter list, a flt is represented by [F], [P] is a polygon, and [RPN] stands for "rpn-expression", which we'll talk about later:

[P] = .v.ff([F], [F]);
[C] = .c.vf([P], [F]);
[F] = .f.vvvratio([P], [P], [P]);
[P] = .v.vvf([P], [P], [F]);
[F] = .f.vv([P], [P]);
[F] = .f.vxcoord([P]);
[F] = .f.vycoord([P]);
[A] = .a.f([F]);
[F] = .f.area([P]);
[F] = .f.rpn([RPN]);

Most of these are fairly straight-forward. .v.ff makes a point using the two numbers as its x and y coordinates. .c.vf produces a circle centered at the point, and having a radius given by the number.

.f.vvvratio makes a number that is the ratio of the distances between the three points. Typically, the points are on a line, and if they are called A, B, and C, then the number produced is the ratio AB/AC. The points don't have to be on a line, however.

Use .v.vvf to do the opposite -- to produce a point with the given ratio of distances relative to two other points. This command will produce the new point on the line connecting the other two.

.f.vv produces a number that's the distance between the two points, and .f.vxcoord and .f.vycoord put the x and y coordinates into a number.

.a.f produces an angle of the given magnitude. The magnitude depends on whether you're in degree mode or radian mode (see above), so

.degreemode;
ang = .a.f(180);
and
.radianmode;
ang = .a.f(3.14159265);
have exactly the same net effect.

Finally, .f.area calculates the area of the polygon and puts it in the number. If the polygon's edges cross each other, you'll get consistent results, but perhaps difficult to interpret. Geometer simply treats the polygon as a series of triangles and adds those together with signed areas.

So far we've just made and used numbers, but have no way to calculate with them. And exactly what is it that you can put in place of an "f" in the examples above?

In every case, you can use a defined number, or a literal number. For example, if you wanted to have a point whose x coordinate was locked at 0.1, but whose y coordinate was the same as the distance between two other points, the following code would work:

v1 = .free(0.372188, 0.255624, "A");
v2 = .free(0.609407, 0.247444, "B");
ycoord = .f.vv(v1, v2);
v3 = .v.ff(0.100000, ycoord);

Geometer can also do more or less arbitrary calculations with these numbers, and all of these are achieved by means of the .f.rpn command. The "rpn" refers to "reverse Polish notation", and within that command, Geometer supports a tiny PostScript-like or Fourth-like language. The instructions consist of a series of command tokens, number variables, angles, and number literals. They are evaluated from left to right, as follows:

A number literal, a number variable, or an angle will simply have its value pushed on the execution stack. If it's an angle, the actual number pushed on the stack will depend on whether Geometer is in degree mode or in radian mode.

Every other possible entry is a Geometer reserved word, and each has some effect on the contents of the stack. After all the entries are evaluated, the result is whatever is left on the top of the stack.

Here's a list of all the Geometer stack operators, and what each one does to the contents of the stack:

.add
The top two numbers are removed from the stack and are added. The result is returned to the stack.
.sub
The top two numbers are removed from the stack and subtracted. The result is returned to the stack.
.mul
The top two numbers are removed from the stack and multiplied together. The result is returned to the stack.
.div
The top two numbers are removed from the stack and divided. The result is returned to the stack.
.mod
The "modulo" function is applied to the top two numbers on the stack which are then replaced by the result. For example, the sequence 17, 5, .mod will result in a 2 on top of the stack, since 17 (mod 5) = 2.
.neg
The top number on the stack is negated.
.sin
The trigonometric function sine is applied to the top number on the stack. The number on the top of the stack is interpreted as an angle in degrees or radians, depending on Geometer's mode.
.cos
The trigonometric function cosine is applied to the top number on the stack. The number on the top of the stack is interpreted as an angle in degrees or radians, depending on Geometer's mode.
.tan
The trigonometric tangent is taken of the top number on the stack. The number on the top of the stack is interpreted as an angle in degrees or radians, depending on Geometer's mode.
.atan2
If the top two elements on the stack are x and y, the trigonometric arctangent function is taken of x/y if y ≠ 0. If y = 0 it's like taking the arctangent of an infinite number, positive or negative, depending on the sign of x. The angle produced will be in degrees or radians, depending on Geometer's mode.
.abs
The top number on the stack is replaced by its absolute value.
.exp
If x is the top number on the stack, it is replaced by e=2.7218281828 to the power x.
.log
If x is the top number on the stack, it is replaced by log x—the natural logarithm of x.
.rand
A random number between 0.0 and 1.0 is placed on the top of the stack.
.dup
The top number on the stack is duplicated.
.clear
The entire stack is cleared to empty.
.pop
The top element on the stack is removed.
.roll
The top two numbers on the stack are removed and are used to determine a rotation of the stack. If the stack's top two numbers are n and j, the top n numbers remaining on the stack are rolled j positions. j can be positive or negative. For example, if the stack consists originally of a, b, c, d, e, 4, 1, where the "1" is on the right of the stack, then the top four elements are rotated by one position, yielding a stack that looks like this: a, e, b, c, d.
.copy
The top number on the stack is removed and is used as the number of items to copy. Thus, an original stack that looks like this: a, b, c, d, e, 3 after the copy operation would look like this: a, b, c, d, e, c, d, e.
.exch
This operation exchanges the top two items on the stack.
.eq
The top two numbers on the stack are removed and compared for equality. If they are equal, a 1 is placed on the stack. If they're unequal, a 0 is placed there.
.ne
Just like .eq, except the comparison is for inequality.
.lt
Same as .eq, except less-than.
.le
Same as .eq, except less-than or equal.
.gt
Same as .eq, except greater-than.
.ge
Same as .eq, except greater-than or equal.
.round
Rounds the top number on the stack to the nearest integer. Round basically adds 0.5 and then does the .floor operation, described above.
.ceiling
Replaces the top number on the stack with the smallest integer greater than it. The ceiling is always an integer and it is always greater than or equal to the number. It is only equal to the number if the number itself is an integer.
.floor
Replaces the top number on the stack with the largest integer smaller than it. It is always less than or equal to the number, and is equal to it only if the number is an integer.
.truncate
Truncates the top number on the stack to the integer closest to zero. Truncate is like .floor (see above) for positive numbers and like .ceiling (see above) for negative numbers.

The above doesn't seem like much if you've never worked with reverse Polish notation, but you can do just about any calculation you like using it. For example, here's a little Geometer program that will draw a graph of the function sin x + cos 2x:

.geometry "version 0.2";
v1 = .free(-1.00204, -0.398773, "A");
v2 = .free(0.130879, -0.366053, "B");
xval = .f.vv(v1, v2);
yval = .f.rpn(xval, .sin, xval, 2.000000, .mul, 
        .cos, .add);
v3 = .v.ff(xval, yval, .smear);

It isn't very interesting, because the values mostly lie outside the drawing window, and need to be better scaled to make a reasonable diagram, but it does behave as advertised. The x coordinate is simply taken to be the distance between the points v1 and v2. The y coordinate is calculated using the rpn expression. Follow along to see what happens. First the xval is placed on the stack and its sine is taken. Another copy of it is put on the top of the stack (above sin x), then a 2 is added to the stack, the top two elements are multiplied, yielding 2x on top, whose cosine is taken, and that result is added to sin x below, yielding sin x + cos 2x. Those values are then used as the coordinates of v3, which has a .smear color, so as you drag around v1 or v2, the point v3 will leave a smeared track along the curve y = sin x + cos 2x.

After each .f.rpn expression is evaluated, the stack is cleared, so you can't pass information on to other commands, although this may occur in the future, so it's a good idea to leave only the final result you're interested in on the stack when you're done, for compatibility with future versions of Geometer.