Original source downloaded from Vintage Basic
Converted to D by Bastiaan Veelo.
Running the code
Assuming the reference dmd compiler:
dmd -dip1000 -run threedeeplot.d
Other compilers also exist.
On rounding floating point values to integer values
The D equivalent of Basic INT is floor,
which rounds towards negative infinity. If you change occurrences of floor to
lrint, you'll see that the plots show a bit more detail,
as is done in the bonus below.
Bonus: Self-writing programs
With a small modification to the source, the program can be extended to plot a random function, and print its formula.
rdmd -dip1000 threedeeplot_random.d
(rdmd caches the executable, which results in speedy execution when the source does not change.)
Example output
3D Plot
(After Creative Computing Morristown, New Jersey)
f(z) = 30 * sin(z / 10.0)
*
* * * *
* * * * *
* * * * *
* * * * * *
* * * * * *
* * * * * **
* * * * * * *
* * * * * * **
* * * * * * * *
* * * * * * * *
* * * * * * * **
* * * * * * * * *
* * * * ** * * * *
* * * * ** * * *
* * * * * * * *
* * * * * * * *
* * * ** * * **
* * * ** * **
* * * * * **
* * * * * **
* * * * * **
* * * ** * **
* * * ** * * **
* * * * * * * *
* * * * * * * *
* * * * ** * * *
* * * * ** * * * *
* * * * * * * * *
* * * * * * * **
* * * * * * * *
* * * * * * * *
* * * * * * **
* * * * * * *
* * * * * **
* * * * * *
* * * * * *
* * * * *
* * * * *
* * * *
*
Breakdown of differences
Have a look at the relevant differences between threedeeplot.d and threedeeplot_random.d.
This is the original function with the single expression that is evaluated for the plot:
static float fna(float z)
{
return 30.0 * exp(-z * z / 100.0);
}
Here static means that the nested function does not need acces to its enclosing scope.
Now, by inserting the following:
enum functions = ["30.0 * exp(-z * z / 100.0)",
"sqrt(900.01 - z * z) * .9 - 2",
"30 * (cos(z / 16.0) + .5)",
"30 - 30 * sin(z / 18.0)",
"30 * exp(-cos(z / 16.0)) - 30",
"30 * sin(z / 10.0)"];
size_t index = uniform(0, functions.length);
writeln(center("f(z) = " ~ functions[index], width), "\n");
and changing the implementation of fna to
float fna(float z)
{
final switch (index)
{
static foreach (i, f; functions)
case i:
mixin("return " ~ f ~ ";");
}
}
we unlock some very special abilities of D. Let's break it down:
enum functions = ["30.0 * exp(-z * z / 100.0)", /*...*/];
This defines an array of strings, each containing a mathematical expression. Due to the enum keyword, this is an
array that really only exists at compile-time.
size_t index = uniform(0, functions.length);
This defines a random index into the array. functions.length is evaluated at compile-time, due to D's compile-time
function evaluation (CTFE).
writeln(center("f(z) = " ~ functions[index], width), "\n");
Unmistakenly, this prints the formula centered on a line. What happens behind the scenes is that functions (which
only existed at compile-time before now) is pasted in, so that an instance of that array actually exists at run-time
at this spot, and is instantly indexed.
float fna(float z)
{
final switch (index)
{
// ...
}
}
static has been dropped from the nested function because we want to evaluate index inside it. The function contains
an ordinary switch, with final providing some extra robustness. It disallows a default case and produces an error
when the switch doesn't handle all cases. The switch body is where the magic happens and consists of these three
lines:
static foreach (i, f; functions)
case i:
mixin("return " ~ f ~ ";");
The static foreach iterates over functions at compile-time, producing one case for every element in functions.
mixin takes a string, which is constructed at compile-time, and pastes it right into the source.
In effect, the implementation of float fna(float z) unrolls itself into
float fna(float z)
{
final switch (index)
{
case 0:
return 30.0 * exp(-z * z / 100.0);
case 1:
return sqrt(900.01 - z * z) * .9 - 2;
case 2:
return 30 * (cos(z / 16.0) + .5);
case 3:
return 30 - 30 * sin(z / 18.0);
case 4:
return 30 * exp(-cos(z / 16.0)) - 30;
case 5:
return 30 * sin(z / 10.0)";
}
}
So if you feel like adding another function, all you need to do is append it to the functions array, and the rest of
the program rewrites itself...