[Push] division and tangent instructions

Maarten Keijzer mkeijzer at xs4all.nl
Sat Jun 26 17:36:18 EDT 2010


My take on this,

I would say that the 'float' datatype in Push should be defined as a single or 
double precision floating point value with IEEE-754 semantics. This means 
follow this standard (which is followed by practically all programming 
languages) for division by zero, overflows, propagation of nans and infs, etc. 
There's no need to invent something new, and any implementation of push will 
therefore implicitly use this.

I suggest using double precision.

If ratios or arbitrary precision floats are necessary, they are a separate 
type, thus use their own stack.

I would also say that the basic 'int' datetype in Push should be a 32-bit 
integer, but I don't feel nearly as strong about it as having a IEEE-754 
compliant semantics.

And note that Koza called 'his' division operator 'pdiv', p for protected. 
There's no reason Push can't support a pdiv, but a real division should be 
standard.

-Maarten-


On Saturday, June 26, 2010 05:34:07 pm Lee Spector wrote:
> 
> I don't think I personally have strong opinions how this is handled (as long 
as the rationale makes sense), and I agree that it'd be good for it to be 
documented more carefully whether or not it is standardized across 
implementations. That said, here's a little history.
> 
> The idea of instructions becoming no-ops when given arguments that produce 
exceptions is something that I built into Push from the start, and I like it 
for its clarity and uniformity across different kinds of possible exceptions. 
Maarten Keijzer has advocated an alternative in which stacks are left in 
intermediate states upon failure, and he has some interesting arguments for 
this, but for now I personally prefer the more consistent no-op behavior. 
> 
> The idea that division by zero is an exception comes (in my experience) from 
Koza, who defined "protected division" to return a specific value (0 or 1 -- I'd 
have to look it up) to avoid runtime errors. I don't why he didn't use Inf or 
NaN, but I think I was just following his lead. I followed him in considering 
division by zero "exceptional" but then did something different in response: 
rather than pushing a specific (incorrect) value I adopted the general Push no-
op strategy (which wasn't an option for Koza when evolving Lisp-style s-
expressions). 
> 
> I've thought of using Inf and/or NaN more recently but the details of 
generating and handling these values can be messy and depend on the 
implementation language, and I've generally proceeded by eliminating Inf/NaN 
one way or another rather than treating it as a legitimate value OR as an 
exception. For example, because I've often been working in languages that 
allow for arbitrary sized integers (which can grow big enough to consume all 
RAM), and also because I want to avoid underflow exceptions (which I can't 
easily detect until the implementation language triggers an exception, which 
I'd rather not have to deal with in different language contexts), I generally 
define some minimum and maximum number magnitudes and clamp values to these. 
(In several of my implementations there's a function called something like 
"keep-number-reasonable" that handles this.) Conceivably, for consistency, I 
should be making instructions become no-ops whenever the min/max magnitudes 
are hit, but in
>   fact I just clamp the values.
> 
> This could be done better. But it's a slippery slope. There's a lot of 
possible complexity in the design of a language's numeric type system. In 
several implementation languages it'd be easy to incorporate ratios and/or 
complex numbers, and there would be a lot of things to consider not only about 
exceptions but also about how the type system and the Push types interact 
(e.g. if there's auto-promotion to BigInts on the integer stack, or if there's 
instead a second stack for BigInts, etc.). When considering these design 
decisions you have to consider not only completeness and expressive power, 
etc., but also potential mutation/evolution dynamics, which is HARD.
> 
> I'm sure there are other interesting approaches to this, maybe involving the 
"option types" or monads that you mention... 
> 
> On your questions about specific effects of these choices: This will only 
affect symbolic regression of 1/x if you have a fitness case with x=0. If you 
do, what's the target y value? If it's inf then yes, you need a number type 
that can generate and manipulate inf. But if inf isn't in your data set then 
you'll never need to handle it in the interpreter in a correct program... 
unless you're doing transfinite symbolic regression, which would be VERY 
INTERESTING! But it would require additional redesign of the numeric 
instructions.
> 
> The same applies to the Tan example. If your input data contains inf then 
you need number instructions that deal with it, but since it usually doesn't 
you can usually clamp or handle the very big numbers in some other way.
> 
>  -Lee
> 
> 
> 
> On Jun 26, 2010, at 10:22 AM, PerPlex Ed wrote:
> 
> > Division and modulo instruction are defined as no-op when the top of the 
stack is zero.
> > 
> > While I understand some of the reason behind this choice, I don't feel at 
ease with it completely.
> > 
> > For floating point computation I guess it's common for most platform to be 
able to represent and perform conputation on the special values NaN, +Inf and 
-Inf following more or less strictly the some IEEE standard. Java and C# 
behaviours are described here:
> > 
> > http://www.itu.dk/people/sestoft/javaprecisely/java-floatingpoint.pdf
> > http://www.itu.dk/~sestoft/csharpprecisely/csharp-floatingpoint.pdf
> > 
> > I don't know about Lisp.
> > 
> > Now what if I want to perform symbolic regression of 1/x?
> > 
> > Because of the current Push definition, any genetic programming system 
based on Push won't be able to find an exact result. I don't have enough 
experience to tell if this problem will make harder for Push based system to 
find an aproximate solution.
> > 
> > What about integers? I see that modern languages use "option types" or the 
Maybe monad to represent computation that can possibly result an invalid 
result without causing any exceptional conditions or disruption in the program 
evaluation. Isn't this something that looks appropriate for the purposes of 
Push?
> > 
> > In any case, the tangent function goes to infinity periodically but there 
is nothing in the language specification about these cases. Should they be 
performed as no-op? Why can Float.Tan push Inf or NaN on the stack while the 
division is not allowed to do that?
> > In my latest Push run Float.Tan found 2.8097223026326946E+36 on the top of 
the stack and pushed NaN.
> > 
> > Thanks.
> > 
> > 
> > 
> > 
> > _______________________________________________
> > Push mailing list
> > Push at lists.hampshire.edu
> > https://lists.hampshire.edu/mailman/listinfo/push
> 
> --
> Lee Spector, Professor of Computer Science
> School of Cognitive Science, Hampshire College
> 893 West Street, Amherst, MA 01002-3359
> lspector at hampshire.edu, http://hampshire.edu/lspector/
> Phone: 413-559-5352, Fax: 413-559-5438
> 
> Check out Genetic Programming and Evolvable Machines:
> http://www.springer.com/10710 - http://gpemjournal.blogspot.com/
> 
> _______________________________________________
> Push mailing list
> Push at lists.hampshire.edu
> https://lists.hampshire.edu/mailman/listinfo/push
> 


More information about the Push mailing list